/** * Dog Calorie Calculator Widget * Embeddable JavaScript widget for calculating dog's daily calorie requirements * by Canine Nutrition and Wellness - https://caninenutritionandwellness.com * * Usage: * *
*/ (function() { 'use strict'; // Widget styles - Using Shadow DOM for better isolation where supported const widgetStyles = ` .dog-calc-widget { max-width: 600px; margin: 0 auto; padding: 24px; font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.5; color: #6f3f6d; box-sizing: border-box; } .dog-calc-widget *, .dog-calc-widget *::before, .dog-calc-widget *::after { box-sizing: border-box; } .dog-calc-section { background: #fdfcfe; border: 1px solid #e8e3ed; border-radius: 8px 8px 0 0; padding: 24px; margin-bottom: 0; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); } .dog-calc-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; } .dog-calc-section h2 { margin: 0; color: #6f3f6d; font-size: 1.5rem; font-weight: 600; } /* Unit Switch */ .dog-calc-unit-switch { display: flex; align-items: center; gap: 12px; } .dog-calc-unit-label { font-size: 0.9rem; font-weight: 500; color: #635870; transition: color 0.2s ease; } .dog-calc-unit-label.active { color: #6f3f6d; font-weight: 600; } .dog-calc-switch { position: relative; display: inline-block; width: 48px; height: 24px; } .dog-calc-switch input { opacity: 0; width: 0; height: 0; } .dog-calc-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e8e3ed; transition: 0.3s; border-radius: 24px; } .dog-calc-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: 0.3s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .dog-calc-switch input:checked + .dog-calc-slider { background-color: #f19a5f; } .dog-calc-switch input:checked + .dog-calc-slider:before { transform: translateX(24px); } .dog-calc-form-group { margin-bottom: 20px; } .dog-calc-form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: #6f3f6d; font-size: 1rem; } .dog-calc-form-group select, .dog-calc-form-group input[type="number"], .dog-calc-form-group input[type="text"] { width: 100%; padding: 12px 16px; border: 1px solid #e8e3ed; border-radius: 6px; font-size: 1rem; font-family: inherit; background-color: #ffffff; color: #6f3f6d; transition: all 0.2s ease; -webkit-appearance: none; -moz-appearance: none; appearance: none; background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236f3f6d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right 12px center; background-size: 20px; padding-right: 40px; } .dog-calc-form-group select option { background-color: #ffffff; color: #6f3f6d; } .dog-calc-form-group input[type="number"], .dog-calc-form-group input[type="text"] { background-image: none; padding-right: 16px; } .dog-calc-form-group select:focus, .dog-calc-form-group input[type="number"]:focus, .dog-calc-form-group input[type="text"]:focus { outline: none; border-color: #f19a5f; background-color: #ffffff; box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1); } .dog-calc-form-group input[readonly] { background-color: #f8f5fa; cursor: not-allowed; color: #635870; } .dog-calc-results { 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-radius: 6px; padding: 20px; margin-top: 24px; } .dog-calc-result-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .dog-calc-result-item:last-child { margin-bottom: 0; } .dog-calc-result-label { font-weight: 500; color: #6f3f6d; font-size: 0.95rem; } .dog-calc-result-value { font-weight: 600; color: #6f3f6d; font-size: 1.1rem; padding: 4px 12px; background: rgba(241, 154, 95, 0.15); border-radius: 4px; } .dog-calc-collapsible { background: #ffffff; border: 1px solid #e8e3ed; border-top: none; margin-bottom: 0; overflow: hidden; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); } .dog-calc-collapsible-header { background: #f8f5fa; padding: 20px 24px; border-bottom: 1px solid #e8e3ed; } .dog-calc-collapsible-header h3 { margin: 0; font-size: 1.25rem; color: #6f3f6d; font-weight: 600; } .dog-calc-collapsible-content { display: block; } .dog-calc-collapsible-inner { padding: 24px; } /* Action Buttons */ .dog-calc-action-buttons { display: flex; justify-content: center; gap: 16px; padding: 20px; background: #f8f5fa; border-left: 1px solid #e8e3ed; border-right: 1px solid #e8e3ed; margin-top: -1px; } .dog-calc-btn { padding: 8px 16px; border: 1px solid #e8e3ed; border-radius: 6px; font-size: 0.9rem; font-weight: 500; font-family: inherit; cursor: pointer; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 6px; background: white; color: #6f3f6d; } .dog-calc-btn:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .dog-calc-btn-share:hover { border-color: #9f5999; color: #9f5999; } .dog-calc-btn-embed:hover { border-color: #7fa464; color: #7fa464; } .dog-calc-input-group { display: flex; gap: 16px; align-items: flex-end; } .dog-calc-input-group .dog-calc-form-group { flex: 1; margin-bottom: 0; } .dog-calc-unit-select { min-width: 120px; } .dog-calc-error { color: #e87159; font-size: 0.875rem; margin-top: 6px; font-weight: 500; } .dog-calc-hidden { display: none !important; } .dog-calc-footer { text-align: center; padding: 20px; background: #fdfcfe; border: 1px solid #e8e3ed; border-radius: 0 0 8px 8px; border-top: none; margin-top: -1px; } .dog-calc-footer a { color: #9f5999; text-decoration: none; font-size: 0.9rem; font-weight: 500; transition: color 0.2s ease; } .dog-calc-footer a:hover { color: #f19a5f; text-decoration: underline; } @media (max-width: 576px) { .dog-calc-widget { padding: 16px; } .dog-calc-section, .dog-calc-collapsible-inner { padding: 20px; } .dog-calc-section h2, .dog-calc-collapsible-header h3 { font-size: 1.3rem; } .dog-calc-input-group { flex-direction: column; gap: 20px; } .dog-calc-input-group .dog-calc-form-group { margin-bottom: 20px; } .dog-calc-result-item { flex-direction: column; align-items: flex-start; gap: 8px; } .dog-calc-result-value { align-self: stretch; text-align: center; } .dog-calc-collapsible-header { padding: 16px 20px; } .dog-calc-section-header { flex-direction: column; align-items: stretch; gap: 12px; } .dog-calc-section h2 { text-align: center; } .dog-calc-unit-switch { justify-content: center; } .dog-calc-action-buttons { flex-direction: column; padding: 16px; } .dog-calc-btn { width: 100%; justify-content: center; } } @media (prefers-color-scheme: dark) { .dog-calc-widget { color: #f5f3f7; } .dog-calc-section, .dog-calc-collapsible { background: #24202d; border-color: #433c4f; } .dog-calc-collapsible-header { background: #312b3b; border-color: #433c4f; } .dog-calc-collapsible-header:hover { background: #3a3446; } .dog-calc-section h2, .dog-calc-collapsible-header h3, .dog-calc-form-group label, .dog-calc-result-label { color: #f5f3f7; } .dog-calc-form-group select, .dog-calc-form-group input[type="number"], .dog-calc-form-group input[type="text"] { background-color: #312b3b; border-color: #433c4f; color: #f5f3f7; background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f5f3f7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); } .dog-calc-form-group select option { background-color: #312b3b; color: #f5f3f7; } .dog-calc-form-group select:focus, .dog-calc-form-group input[type="number"]:focus, .dog-calc-form-group input[type="text"]:focus { background-color: #312b3b; border-color: #f19a5f; } .dog-calc-form-group input[readonly] { background-color: #433c4f; color: #b8b0c2; } .dog-calc-results { background: linear-gradient(135deg, rgba(241, 154, 95, 0.15) 0%, rgba(241, 154, 95, 0.08) 100%); border-color: rgba(241, 154, 95, 0.3); } .dog-calc-result-value { color: #f5f3f7; background: rgba(241, 154, 95, 0.2); } .dog-calc-footer { background: #24202d; border-color: #433c4f; } .dog-calc-unit-label { color: #b8b0c2; } .dog-calc-unit-label.active { color: #f5f3f7; } .dog-calc-slider { background-color: #433c4f; } .dog-calc-action-buttons { background: #312b3b; border-color: #433c4f; } .dog-calc-btn { background: #433c4f; border-color: #433c4f; color: #f5f3f7; } .dog-calc-btn:hover { background: #524a5f; border-color: #524a5f; } .dog-calc-btn-share:hover { border-color: #9f5999; color: #f19a5f; } .dog-calc-btn-embed:hover { border-color: #7fa464; color: #7fa464; } } `; // Widget HTML template const widgetTemplate = `

Dog's Characteristics

Metric Imperial
Please enter a valid weight (minimum 0.1 kg)

How much should I feed?

Please enter a valid energy content (minimum 1 kcal/100g)
Please enter a valid number of days (minimum 1)
`; // Calculator class class DogCalorieCalculatorWidget { constructor(container) { this.container = container; this.currentMER = 0; this.isImperial = false; this.init(); } init() { this.injectStyles(); this.injectHTML(); this.bindEvents(); this.updateUnitLabels(); } injectStyles() { if (!document.getElementById('dog-calc-widget-styles')) { const style = document.createElement('style'); style.id = 'dog-calc-widget-styles'; style.textContent = widgetStyles; document.head.appendChild(style); } } injectHTML() { this.container.innerHTML = widgetTemplate; } bindEvents() { const weightInput = this.container.querySelector('#weight'); const dogTypeSelect = this.container.querySelector('#dogType'); const foodEnergyInput = this.container.querySelector('#foodEnergy'); const daysInput = this.container.querySelector('#days'); const unitSelect = this.container.querySelector('#unit'); const unitToggle = this.container.querySelector('#unitToggle'); if (weightInput) { weightInput.addEventListener('input', () => this.updateCalorieCalculations()); weightInput.addEventListener('blur', () => this.validateWeight()); } if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations()); if (foodEnergyInput) { foodEnergyInput.addEventListener('input', () => this.updateFoodCalculations()); foodEnergyInput.addEventListener('blur', () => this.validateFoodEnergy()); } if (daysInput) { daysInput.addEventListener('input', () => this.updateFoodCalculations()); daysInput.addEventListener('blur', () => this.validateDays()); } if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations()); if (unitToggle) unitToggle.addEventListener('change', () => this.toggleUnits()); } toggleUnits() { const toggle = this.container.querySelector('#unitToggle'); this.isImperial = toggle.checked; this.updateUnitLabels(); this.convertExistingValues(); this.updateCalorieCalculations(); } updateUnitLabels() { const metricLabel = this.container.querySelector('#metricLabel'); const imperialLabel = this.container.querySelector('#imperialLabel'); const weightLabel = this.container.querySelector('#weightLabel'); const foodEnergyLabel = this.container.querySelector('#foodEnergyLabel'); const weightInput = this.container.querySelector('#weight'); const foodEnergyInput = this.container.querySelector('#foodEnergy'); const unitSelect = this.container.querySelector('#unit'); if (metricLabel && imperialLabel) { metricLabel.classList.toggle('active', !this.isImperial); imperialLabel.classList.toggle('active', this.isImperial); } if (this.isImperial) { if (weightLabel) weightLabel.textContent = "Dog's Weight (lbs):"; if (weightInput) { weightInput.placeholder = "Enter weight in lbs"; weightInput.min = "0.2"; weightInput.step = "0.1"; } if (foodEnergyLabel) foodEnergyLabel.textContent = "Food Energy Content (kcal/oz):"; if (foodEnergyInput) { foodEnergyInput.placeholder = "Enter kcal per oz"; foodEnergyInput.min = "0.03"; foodEnergyInput.step = "0.01"; } if (unitSelect) { unitSelect.innerHTML = ` `; } } else { if (weightLabel) weightLabel.textContent = "Dog's Weight (kg):"; if (weightInput) { weightInput.placeholder = "Enter weight in kg"; weightInput.min = "0.1"; weightInput.step = "0.1"; } if (foodEnergyLabel) foodEnergyLabel.textContent = "Food Energy Content (kcal/100g):"; if (foodEnergyInput) { foodEnergyInput.placeholder = "Enter kcal per 100g"; foodEnergyInput.min = "1"; foodEnergyInput.step = "1"; } if (unitSelect) { unitSelect.innerHTML = ` `; } } } convertExistingValues() { const weightInput = this.container.querySelector('#weight'); const foodEnergyInput = this.container.querySelector('#foodEnergy'); if (weightInput && weightInput.value) { const currentWeight = parseFloat(weightInput.value); if (!isNaN(currentWeight)) { if (this.isImperial) { weightInput.value = this.formatNumber(currentWeight * 2.20462, 1); } else { weightInput.value = this.formatNumber(currentWeight / 2.20462, 1); } } } if (foodEnergyInput && foodEnergyInput.value) { const currentEnergy = parseFloat(foodEnergyInput.value); if (!isNaN(currentEnergy)) { if (this.isImperial) { foodEnergyInput.value = this.formatNumber(currentEnergy / 3.52739, 2); } else { foodEnergyInput.value = this.formatNumber(currentEnergy * 3.52739, 0); } } } } getWeightInKg() { const weightInput = this.container.querySelector('#weight'); if (!weightInput || !weightInput.value) return null; const weight = parseFloat(weightInput.value); if (isNaN(weight)) return null; return this.isImperial ? weight / 2.20462 : weight; } getFoodEnergyPer100g() { const foodEnergyInput = this.container.querySelector('#foodEnergy'); if (!foodEnergyInput || !foodEnergyInput.value) return null; const energy = parseFloat(foodEnergyInput.value); if (isNaN(energy)) return null; return this.isImperial ? energy * 3.52739 : energy; } calculateRER(weightKg) { return 70 * Math.pow(weightKg, 0.75); } calculateMER(rer, factor) { return rer * factor; } validateInput(value, min = 0, isInteger = false) { const num = parseFloat(value); if (isNaN(num) || num < min) return false; if (isInteger && !Number.isInteger(num)) return false; return true; } showError(elementId, show = true) { const errorElement = this.container.querySelector('#' + elementId); if (errorElement) { if (show) { errorElement.classList.remove('dog-calc-hidden'); } else { errorElement.classList.add('dog-calc-hidden'); } } } convertUnits(grams, unit) { switch (unit) { case 'kg': return grams / 1000; case 'oz': return grams / 28.3495; case 'lb': return grams / 453.592; default: return grams; } } formatNumber(num, decimals = 0) { return num.toFixed(decimals).replace(/\.?0+$/, ''); } validateWeight() { const weight = this.container.querySelector('#weight')?.value; if (weight && !this.validateInput(weight, 0.1)) { this.showError('weightError', true); } else { this.showError('weightError', false); } } validateFoodEnergy() { const energy = this.container.querySelector('#foodEnergy')?.value; if (energy && !this.validateInput(energy, 1)) { this.showError('foodEnergyError', true); } else { this.showError('foodEnergyError', false); } } validateDays() { const days = this.container.querySelector('#days')?.value; if (days && !this.validateInput(days, 1, true)) { this.showError('daysError', true); } else { this.showError('daysError', false); } } updateCalorieCalculations() { const dogTypeSelect = this.container.querySelector('#dogType'); const calorieResults = this.container.querySelector('#calorieResults'); const rerValue = this.container.querySelector('#rerValue'); const merValue = this.container.querySelector('#merValue'); if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) { return; } const weightKg = this.getWeightInKg(); const dogTypeFactor = dogTypeSelect.value; this.showError('weightError', false); if (!weightKg || weightKg < 0.1) { const weightInput = this.container.querySelector('#weight'); if (weightInput && weightInput.value) this.showError('weightError', true); calorieResults.style.display = 'none'; return; } if (!dogTypeFactor) { calorieResults.style.display = 'none'; return; } const factor = parseFloat(dogTypeFactor); const rer = this.calculateRER(weightKg); const mer = this.calculateMER(rer, factor); this.currentMER = mer; rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day'; merValue.textContent = this.formatNumber(mer, 0) + ' cal/day'; calorieResults.style.display = 'block'; this.updateFoodCalculations(); } updateFoodCalculations() { if (this.currentMER === 0) return; const daysInput = this.container.querySelector('#days'); const unitSelect = this.container.querySelector('#unit'); const dailyFoodResults = this.container.querySelector('#dailyFoodResults'); const dailyFoodValue = this.container.querySelector('#dailyFoodValue'); const totalFoodDisplay = this.container.querySelector('#totalFoodDisplay'); if (!daysInput || !unitSelect || !dailyFoodResults || !dailyFoodValue || !totalFoodDisplay) { return; } const energyPer100g = this.getFoodEnergyPer100g(); const days = daysInput.value; const unit = unitSelect.value; this.showError('foodEnergyError', false); this.showError('daysError', false); if (!energyPer100g || energyPer100g < 1) { const foodEnergyInput = this.container.querySelector('#foodEnergy'); if (foodEnergyInput && foodEnergyInput.value) this.showError('foodEnergyError', true); dailyFoodResults.style.display = 'none'; totalFoodDisplay.value = ''; return; } if (!days || !this.validateInput(days, 1, true)) { if (days) this.showError('daysError', true); totalFoodDisplay.value = ''; return; } const numDays = parseInt(days); const dailyFoodGrams = (this.currentMER / energyPer100g) * 100; const totalFoodGrams = dailyFoodGrams * numDays; dailyFoodValue.textContent = this.formatNumber(dailyFoodGrams, 1) + ' g/day'; dailyFoodResults.style.display = 'block'; const convertedAmount = this.convertUnits(totalFoodGrams, unit); const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb'; const decimals = unit === 'g' ? 0 : unit === 'kg' ? 2 : 1; totalFoodDisplay.value = this.formatNumber(convertedAmount, decimals) + ' ' + unitLabel; } } // Auto-initialize widget function initDogCalorieCalculator() { const containers = document.querySelectorAll('#dog-calorie-calculator, .dog-calorie-calculator'); containers.forEach(container => { if (!container.dataset.initialized) { new DogCalorieCalculatorWidget(container); container.dataset.initialized = 'true'; } }); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initDogCalorieCalculator); } else { initDogCalorieCalculator(); } // Expose for manual initialization window.DogCalorieCalculatorWidget = DogCalorieCalculatorWidget; })();