/** * Dog Calorie Calculator Widget * Embeddable JavaScript widget for websites * * THIS CODE IS AUTO-GENERATED FROM iframe.html - DO NOT EDIT MANUALLY * Edit iframe.html and run 'node build.js' to update this file * * Usage: * *
* * Or with options: *
* * By Canine Nutrition and Wellness * https://caninenutritionandwellness.com */ (function() { 'use strict'; // Inject widget styles const CSS_STYLES = `/* Sundog Dog Food Calorie Calculator Styles */ /* CSS Variables for theming */ :root { --bg-primary: #fdfcfe; --bg-secondary: #ffffff; --border-color: #e8e3ed; --text-primary: #6f3f6d; --text-secondary: #8f7a8e; --accent-color: #f19a5f; --text-label: #635870; /* For form labels, secondary UI text */ --success-color: #7fa464; /* Green for success states */ --bg-tertiary: #f8f5fa; /* Light background variant */ --error-color: #e87159; /* Error states and messages */ } body { margin: 0; padding: 0; background: transparent; overflow-x: hidden; font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.5; color: var(--text-primary); } .dog-calculator-container { max-width: 600px; margin: 0 auto; padding: 24px; box-sizing: border-box; opacity: 0; transition: opacity 0.3s ease; } .dog-calculator-container.loaded { opacity: 1; } .dog-calculator-container *, .dog-calculator-container *::before, .dog-calculator-container *::after { box-sizing: border-box; } .dog-calculator-section { background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 8px 8px 0 0; padding: 24px; margin-bottom: 0; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); } .dog-calculator-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; } .dog-calculator-section h2 { margin: 0; color: var(--text-primary); font-size: 1.5rem; font-weight: 600; } /* Unit Switch */ .dog-calculator-unit-switch { display: flex; align-items: center; gap: 12px; } .dog-calculator-unit-label { font-size: 0.9rem; font-weight: 500; color: var(--text-label); transition: color 0.2s ease; } .dog-calculator-unit-label.active { color: var(--text-primary); font-weight: 600; } .dog-calculator-switch { position: relative; display: inline-block; width: 48px; height: 24px; } .dog-calculator-switch input { opacity: 0; width: 0; height: 0; } .dog-calculator-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--border-color); transition: 0.3s; border-radius: 24px; } .dog-calculator-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-calculator-switch input:checked + .dog-calculator-slider { background-color: #f19a5f; } .dog-calculator-switch input:checked + .dog-calculator-slider:before { transform: translateX(24px); } .dog-calculator-form-group { margin-bottom: 20px; } .dog-calculator-form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: var(--text-primary); font-size: 1rem; } .dog-calculator-form-group select, .dog-calculator-form-group input[type="number"], .dog-calculator-form-group input[type="text"] { width: 100%; padding: 12px 16px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 1rem; font-family: inherit; background-color: var(--bg-secondary); color: var(--text-primary); 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-calculator-form-group select option { background-color: var(--bg-secondary); color: var(--text-primary); } .dog-calculator-form-group input[type="number"], .dog-calculator-form-group input[type="text"] { background-image: none; padding-right: 16px; } .dog-calculator-form-group select:focus, .dog-calculator-form-group input[type="number"]:focus, .dog-calculator-form-group input[type="text"]:focus { outline: none; border-color: #f19a5f; background-color: var(--bg-secondary); box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1); } .dog-calculator-form-group input[readonly] { background-color: var(--bg-tertiary); cursor: not-allowed; color: var(--text-label); } .dog-calculator-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-calculator-result-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .dog-calculator-result-item:last-child { margin-bottom: 0; } .dog-calculator-result-label { font-weight: 500; color: var(--text-primary); font-size: 0.95rem; } .dog-calculator-result-value { font-weight: 600; color: var(--text-primary); font-size: 1.1rem; padding: 4px 12px; background: rgba(241, 154, 95, 0.15); border-radius: 4px; } .dog-calculator-collapsible { background: var(--bg-primary); border: 1px solid var(--border-color); border-top: none; margin-bottom: 0; overflow: hidden; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); } .dog-calculator-collapsible-header { background: var(--bg-tertiary); padding: 20px 24px; border-bottom: 1px solid var(--border-color); } .dog-calculator-collapsible-header h3 { margin: 0; font-size: 1.25rem; color: var(--text-primary); font-weight: 600; } .dog-calculator-collapsible-content { display: block; } .dog-calculator-collapsible-inner { padding: 24px; } .dog-calculator-input-group { display: flex; gap: 16px; align-items: flex-end; } .dog-calculator-input-group .dog-calculator-form-group { flex: 1; margin-bottom: 0; } .dog-calculator-unit-select { min-width: 120px; } .dog-calculator-error { color: var(--error-color); font-size: 0.875rem; margin-top: 6px; font-weight: 500; } .dog-calculator-hidden { display: none; } /* Action Buttons */ .dog-calculator-action-buttons { display: flex; justify-content: center; gap: 16px; padding: 20px; background: var(--bg-tertiary); border-left: 1px solid var(--border-color); border-right: 1px solid var(--border-color); margin-top: -1px; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); } .dog-calculator-btn { padding: 8px 16px; border: 1px solid var(--border-color); 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: var(--text-primary); } .dog-calculator-btn:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .dog-calculator-btn-share:hover { border-color: #9f5999; color: #9f5999; } .dog-calculator-btn-embed:hover { border-color: var(--success-color); color: var(--success-color); } .dog-calculator-footer { text-align: center; padding: 20px; background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 0 0 8px 8px; border-top: none; margin-top: -1px; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); } .dog-calculator-footer a { color: #9f5999; text-decoration: none; font-size: 0.9rem; font-weight: 500; transition: color 0.2s ease; } .dog-calculator-footer a:hover { color: #f19a5f; text-decoration: underline; } /* Mobile Responsive Design */ @media (max-width: 576px) { .dog-calculator-container { padding: 16px; } .dog-calculator-section, .dog-calculator-collapsible-inner { padding: 20px; } .dog-calculator-section h2, .dog-calculator-collapsible-header h3 { font-size: 1.3rem; } .dog-calculator-section-header { flex-direction: column; align-items: stretch; gap: 12px; } .dog-calculator-section h2 { text-align: center; } .dog-calculator-unit-switch { justify-content: center; } .dog-calculator-action-buttons { flex-direction: column; padding: 16px; } .dog-calculator-btn { width: 100%; justify-content: center; } .dog-calculator-input-group { flex-direction: row; gap: 12px; align-items: flex-end; } .dog-calculator-input-group .dog-calculator-form-group { margin-bottom: 0; } /* First form group takes 55%, second takes 40% with some flex */ .dog-calculator-input-group .dog-calculator-form-group:first-child { flex: 0 0 55%; } .dog-calculator-input-group .dog-calculator-form-group:last-child { flex: 1 1 40%; min-width: 100px; } /* Make sure number inputs don't get too wide */ .dog-calculator-input-group input[type="number"] { max-width: 100%; } /* Ensure dropdowns don't overflow their containers */ .dog-calculator-input-group select { max-width: 100%; overflow: hidden; text-overflow: ellipsis; } .dog-calculator-result-item { flex-direction: column; align-items: flex-start; gap: 8px; } .dog-calculator-result-value { align-self: stretch; text-align: center; } .dog-calculator-collapsible-header { padding: 16px 20px; } } /* Dark theme - manual override */ .dog-calculator-container.theme-dark { --bg-primary: #24202d; --bg-secondary: #312b3b; --border-color: #433c4f; --text-primary: #f5f3f7; --text-secondary: #b8b0c2; color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-section, .dog-calculator-container.theme-dark .dog-calculator-collapsible { background: var(--bg-primary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-collapsible-header { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-collapsible-header:hover { background: #3a3446; } .dog-calculator-container.theme-dark .dog-calculator-section h2, .dog-calculator-container.theme-dark .dog-calculator-collapsible-header h3, .dog-calculator-container.theme-dark .dog-calculator-form-group label, .dog-calculator-container.theme-dark .dog-calculator-result-label { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-unit-label { color: var(--text-secondary) } .dog-calculator-container.theme-dark .dog-calculator-unit-label.active { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-slider { background-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-form-group select, .dog-calculator-container.theme-dark .dog-calculator-form-group input[type="number"], .dog-calculator-container.theme-dark .dog-calculator-form-group input[type="text"] { background-color: var(--bg-secondary); border-color: var(--border-color); color: var(--text-primary); 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-calculator-container.theme-dark .dog-calculator-form-group select option { background-color: var(--bg-secondary); color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-form-group select:focus, .dog-calculator-container.theme-dark .dog-calculator-form-group input[type="number"]:focus, .dog-calculator-container.theme-dark .dog-calculator-form-group input[type="text"]:focus { background-color: var(--bg-secondary); border-color: #f19a5f; } .dog-calculator-container.theme-dark .dog-calculator-form-group input[readonly] { background-color: var(--border-color); color: var(--text-secondary) } .dog-calculator-container.theme-dark .dog-calculator-inline-unit { background-color: var(--bg-secondary); border-color: rgba(241, 154, 95, 0.5); color: var(--text-primary); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); 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-calculator-container.theme-dark .dog-calculator-inline-unit:hover { border-color: #f19a5f; box-shadow: 0 2px 6px rgba(241, 154, 95, 0.3); } .dog-calculator-container.theme-dark .dog-calculator-inline-unit:focus { border-color: #f19a5f; box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.15); } .dog-calculator-container.theme-dark .dog-calculator-inline-unit option { background-color: var(--bg-secondary); color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-unit-btn { background-color: var(--bg-secondary); border-color: var(--border-color); color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-unit-btn:hover { border-color: #f19a5f; background: rgba(241, 154, 95, 0.2); } .dog-calculator-container.theme-dark .dog-calculator-unit-btn.active { border-color: #f19a5f; background: #f19a5f; color: white; } .dog-calculator-container.theme-dark .dog-calculator-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-calculator-container.theme-dark .dog-calculator-result-value { color: var(--text-primary); background: rgba(241, 154, 95, 0.2); } .dog-calculator-container.theme-dark .dog-calculator-footer { background: var(--bg-primary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-action-buttons { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-btn { background: var(--border-color); border-color: var(--border-color); color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-btn:hover { background: #524a5f; border-color: #524a5f; } .dog-calculator-container.theme-dark .dog-calculator-btn-share:hover { border-color: #9f5999; color: #f19a5f; } .dog-calculator-container.theme-dark .dog-calculator-btn-embed:hover { border-color: var(--success-color); color: var(--success-color); } /* System theme - follows user's OS preference */ @media (prefers-color-scheme: dark) { .dog-calculator-container.theme-system { --bg-primary: #24202d; --bg-secondary: #312b3b; --border-color: #433c4f; --text-primary: #f5f3f7; --text-secondary: #b8b0c2; color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-section, .dog-calculator-container.theme-system .dog-calculator-collapsible { background: var(--bg-primary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-collapsible-header { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-collapsible-header:hover { background: #3a3446; } .dog-calculator-container.theme-system .dog-calculator-section h2, .dog-calculator-container.theme-system .dog-calculator-collapsible-header h3, .dog-calculator-container.theme-system .dog-calculator-form-group label, .dog-calculator-container.theme-system .dog-calculator-result-label { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-unit-label { color: var(--text-secondary) } .dog-calculator-container.theme-system .dog-calculator-unit-label.active { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-slider { background-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-form-group select, .dog-calculator-container.theme-system .dog-calculator-form-group input[type="number"], .dog-calculator-container.theme-system .dog-calculator-form-group input[type="text"] { background-color: var(--bg-secondary); border-color: var(--border-color); color: var(--text-primary); 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-calculator-container.theme-system .dog-calculator-form-group select option { background-color: var(--bg-secondary); color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-form-group select:focus, .dog-calculator-container.theme-system .dog-calculator-form-group input[type="number"]:focus, .dog-calculator-container.theme-system .dog-calculator-form-group input[type="text"]:focus { background-color: var(--bg-secondary); border-color: #f19a5f; } .dog-calculator-container.theme-system .dog-calculator-form-group input[readonly] { background-color: var(--border-color); color: var(--text-secondary) } .dog-calculator-container.theme-system .dog-calculator-inline-unit { background-color: var(--bg-secondary); border-color: rgba(241, 154, 95, 0.5); color: var(--text-primary); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); 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-calculator-container.theme-system .dog-calculator-inline-unit:hover { border-color: #f19a5f; box-shadow: 0 2px 6px rgba(241, 154, 95, 0.3); } .dog-calculator-container.theme-system .dog-calculator-inline-unit:focus { border-color: #f19a5f; box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.15); } .dog-calculator-container.theme-system .dog-calculator-inline-unit option { background-color: var(--bg-secondary); color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-unit-btn { background-color: var(--bg-secondary); border-color: var(--border-color); color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-unit-btn:hover { border-color: #f19a5f; background: rgba(241, 154, 95, 0.2); } .dog-calculator-container.theme-system .dog-calculator-unit-btn.active { border-color: #f19a5f; background: #f19a5f; color: white; } .dog-calculator-container.theme-system .dog-calculator-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-calculator-container.theme-system .dog-calculator-result-value { color: var(--text-primary); background: rgba(241, 154, 95, 0.2); } .dog-calculator-container.theme-system .dog-calculator-footer { background: var(--bg-primary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-action-buttons { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-btn { background: var(--border-color); border-color: var(--border-color); color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-btn:hover { background: #524a5f; border-color: #524a5f; } .dog-calculator-container.theme-system .dog-calculator-btn-share:hover { border-color: #9f5999; color: #f19a5f; } .dog-calculator-container.theme-system .dog-calculator-btn-embed:hover { border-color: var(--success-color); color: var(--success-color); } } /* Modal Styles */ .dog-calculator-modal { display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .dog-calculator-modal-content { position: relative; background-color: var(--bg-secondary); margin: 5% auto; padding: 30px; border: 1px solid var(--border-color); border-radius: 12px; width: 90%; max-width: 500px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); animation: slideIn 0.3s ease; } .dog-calculator-modal-embed { max-width: 700px; } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .dog-calculator-modal-close { position: absolute; right: 20px; top: 20px; font-size: 28px; font-weight: 300; color: var(--text-primary); cursor: pointer; transition: color 0.2s ease; } .dog-calculator-modal-close:hover { color: #f19a5f; } .dog-calculator-modal h3 { margin: 0 0 24px 0; color: var(--text-primary); font-size: 1.5rem; } /* Share Modal */ .dog-calculator-share-buttons { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; margin-bottom: 20px; } .dog-calculator-share-btn { padding: 12px 16px; border: none; border-radius: 6px; font-size: 0.9rem; font-weight: 500; color: white; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; gap: 8px; font-family: inherit; } .dog-calculator-share-facebook { background: #1877f2; } .dog-calculator-share-facebook:hover { background: #1664d1; transform: translateY(-1px); } .dog-calculator-share-twitter { background: #1da1f2; } .dog-calculator-share-twitter:hover { background: #1991da; transform: translateY(-1px); } .dog-calculator-share-linkedin { background: #0a66c2; } .dog-calculator-share-linkedin:hover { background: #084d95; transform: translateY(-1px); } .dog-calculator-share-email { background: var(--text-primary); } .dog-calculator-share-email:hover { background: #5a3357; transform: translateY(-1px); } .dog-calculator-share-copy { background: #f19a5f; } .dog-calculator-share-copy:hover { background: #e87741; transform: translateY(-1px); } .dog-calculator-share-url { display: flex; width: 100%; } .dog-calculator-share-url input { flex: 1; width: 100%; padding: 10px 16px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 0.9rem; font-family: monospace; background: var(--bg-tertiary); color: var(--text-primary); } /* Embed Modal */ .dog-calculator-embed-options { display: flex; flex-direction: column; gap: 24px; } .dog-calculator-embed-option { border: 1px solid var(--border-color); border-radius: 8px; padding: 20px; background: #fcfafd; } .dog-calculator-embed-option h4 { margin: 0 0 8px 0; color: var(--text-primary); font-size: 1.1rem; } .dog-calculator-embed-option p { margin: 0 0 16px 0; color: var(--text-label); font-size: 0.9rem; } /* Default (light theme) code containers */ .dog-calculator-code-container { position: relative; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 6px; overflow: hidden; } .dog-calculator-code-container pre { margin: 0; padding: 16px 60px 16px 16px; overflow-x: auto; } .dog-calculator-code-container code { color: var(--bg-secondary); font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 0.85rem; line-height: 1.4; } .dog-calculator-copy-btn { position: absolute; top: 8px; right: 8px; padding: 6px 10px; background: #f19a5f; color: white; border: none; border-radius: 4px; font-size: 0.8rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; font-family: inherit; z-index: 1; } .dog-calculator-copy-btn:hover { background: #e87741; } .dog-calculator-copy-btn.copied { background: var(--success-color); } .dog-calculator-copy-btn.copied:hover { background: var(--success-color); } /* Dark theme modal styles */ .dog-calculator-container.theme-dark .dog-calculator-modal-content { background-color: var(--bg-primary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-modal h3 { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-modal-close { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-modal-close:hover { color: #f19a5f; } .dog-calculator-container.theme-dark .dog-calculator-share-url input { background: var(--bg-secondary); border-color: var(--border-color); color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-embed-option { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-embed-option h4 { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-embed-option p { color: var(--text-secondary) } /* Dark theme code containers - different from embed option background */ .dog-calculator-container.theme-dark .dog-calculator-code-container { background: #1a1621; border-color: #2a2330; } .dog-calculator-container.theme-dark .dog-calculator-code-container code { color: var(--text-primary); } /* System theme modal styles */ @media (prefers-color-scheme: dark) { .dog-calculator-container.theme-system .dog-calculator-modal-content { background-color: var(--bg-primary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-modal h3 { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-modal-close { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-modal-close:hover { color: #f19a5f; } .dog-calculator-container.theme-system .dog-calculator-share-url input { background: var(--bg-secondary); border-color: var(--border-color); color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-embed-option { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-embed-option h4 { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-embed-option p { color: var(--text-secondary) } /* System theme code containers - different from embed option background */ .dog-calculator-container.theme-system .dog-calculator-code-container { background: #1a1621; border-color: #2a2330; } .dog-calculator-container.theme-system .dog-calculator-code-container code { color: var(--text-primary); } } /* Multi-Food Source Styles */ .dog-calculator-food-sources { display: flex; flex-direction: column; gap: 16px; } .dog-calculator-food-source-card { background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 8px; padding: 20px; position: relative; transition: all 0.2s ease; } .dog-calculator-food-source-card:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .dog-calculator-food-source-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } .dog-calculator-food-source-title { font-weight: 600; color: var(--text-primary); font-size: 1.1rem; margin: 0; } .dog-calculator-remove-food-btn { background: var(--error-color); color: white; border: none; border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.2s ease; line-height: 1; } .dog-calculator-remove-food-btn:hover { background: #d65a47; transform: scale(1.1); } .dog-calculator-percentage-group { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border-color); } .dog-calculator-percentage-label { display: block; margin-bottom: 8px; font-weight: 500; color: var(--text-primary); font-size: 1rem; } .dog-calculator-percentage-input-group { display: flex; align-items: center; gap: 12px; } .dog-calculator-percentage-slider { flex: 1; height: 6px; border-radius: 3px; background: var(--border-color); outline: none; transition: all 0.2s ease; -webkit-appearance: none; appearance: none; } .dog-calculator-percentage-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #f19a5f; cursor: pointer; border: 2px solid white; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: all 0.2s ease; } .dog-calculator-percentage-slider::-webkit-slider-thumb:hover { background: #e87741; transform: scale(1.1); } .dog-calculator-percentage-slider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: #f19a5f; cursor: pointer; border: 2px solid white; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: all 0.2s ease; } .dog-calculator-percentage-input { width: 70px; padding: 8px 12px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 0.9rem; text-align: center; background-color: var(--bg-secondary); color: var(--text-primary); } .dog-calculator-percentage-input:focus { outline: none; border-color: #f19a5f; box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1); } .dog-calculator-add-food-btn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 16px; border: 2px dashed var(--border-color); border-radius: 8px; background: transparent; color: var(--text-label); font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; font-family: inherit; margin-top: 16px; } .dog-calculator-add-food-btn:hover { border-color: #f19a5f; color: #f19a5f; background: rgba(241, 154, 95, 0.05); } .dog-calculator-add-food-btn:disabled { opacity: 0.5; cursor: not-allowed; border-color: var(--border-color); color: var(--text-label); background: transparent; } .dog-calculator-add-food-btn:disabled:hover { border-color: var(--border-color); color: var(--text-label); background: transparent; } .dog-calculator-food-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: 16px; margin-top: 20px; } .dog-calculator-food-result-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 0.9rem; } .dog-calculator-food-result-item:last-child { margin-bottom: 0; } .dog-calculator-food-result-label { font-weight: 500; color: var(--text-primary); } .dog-calculator-food-result-value { font-weight: 600; color: var(--text-primary); padding: 2px 8px; background: rgba(241, 154, 95, 0.15); border-radius: 3px; font-size: 0.85rem; } /* Dark theme support for food sources */ .dog-calculator-container.theme-dark .dog-calculator-food-source-card { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-food-source-title { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-percentage-label { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-percentage-slider { background: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-percentage-input { background: var(--border-color); border-color: #524a5f; color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-percentage-group { border-color: var(--border-color); } .dog-calculator-container.theme-dark .dog-calculator-add-food-btn { border-color: var(--border-color); color: var(--text-secondary) } .dog-calculator-container.theme-dark .dog-calculator-add-food-btn:hover { border-color: #f19a5f; color: #f19a5f; background: rgba(241, 154, 95, 0.1); } .dog-calculator-container.theme-dark .dog-calculator-food-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-calculator-container.theme-dark .dog-calculator-food-result-label { color: var(--text-primary); } .dog-calculator-container.theme-dark .dog-calculator-food-result-value { color: var(--text-primary); background: rgba(241, 154, 95, 0.2); } /* System theme support for food sources */ @media (prefers-color-scheme: dark) { .dog-calculator-container.theme-system .dog-calculator-food-source-card { background: var(--bg-secondary); border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-food-source-title { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-percentage-label { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-percentage-slider { background: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-percentage-input { background: var(--border-color); border-color: #524a5f; color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-percentage-group { border-color: var(--border-color); } .dog-calculator-container.theme-system .dog-calculator-add-food-btn { border-color: var(--border-color); color: var(--text-secondary) } .dog-calculator-container.theme-system .dog-calculator-add-food-btn:hover { border-color: #f19a5f; color: #f19a5f; background: rgba(241, 154, 95, 0.1); } .dog-calculator-container.theme-system .dog-calculator-food-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-calculator-container.theme-system .dog-calculator-food-result-label { color: var(--text-primary); } .dog-calculator-container.theme-system .dog-calculator-food-result-value { color: var(--text-primary); background: rgba(241, 154, 95, 0.2); } } /* Mobile responsive design for food sources */ @media (max-width: 576px) { .dog-calculator-food-source-card { padding: 16px; } .dog-calculator-food-source-header { flex-direction: column; align-items: flex-start; gap: 12px; } .dog-calculator-remove-food-btn { align-self: flex-end; margin-top: -8px; } .dog-calculator-percentage-input-group { flex-direction: column; gap: 8px; align-items: stretch; } .dog-calculator-percentage-input { width: 100%; } .dog-calculator-add-food-btn { padding: 12px; font-size: 0.9rem; } .dog-calculator-food-result-item { flex-direction: column; align-items: flex-start; gap: 4px; } .dog-calculator-food-result-value { align-self: stretch; text-align: center; } } /* Lock Icon Styles */ .dog-calculator-lock-icon { display: inline-block; width: 16px; height: 16px; margin-left: 8px; cursor: pointer; font-size: 14px; line-height: 1; vertical-align: middle; transition: all 0.2s ease; user-select: none; opacity: 0.6; } .dog-calculator-lock-icon:hover { opacity: 1; transform: scale(1.1); } .dog-calculator-lock-icon.locked { color: #f19a5f; opacity: 1; font-weight: bold; } .dog-calculator-lock-icon.unlocked { color: var(--text-label); } .dog-calculator-lock-icon.disabled { opacity: 0.3; cursor: not-allowed; } .dog-calculator-lock-icon.disabled:hover { opacity: 0.3; transform: none; } /* Dark theme support for lock icons */ .dog-calculator-container.theme-dark .dog-calculator-lock-icon.unlocked { color: var(--text-secondary) } .dog-calculator-container.theme-dark .dog-calculator-lock-icon.locked { color: #f19a5f; } /* System theme support for lock icons */ @media (prefers-color-scheme: dark) { .dog-calculator-container.theme-system .dog-calculator-lock-icon.unlocked { color: var(--text-secondary) } .dog-calculator-container.theme-system .dog-calculator-lock-icon.locked { color: #f19a5f; } } /* Disabled slider and input styles */ .dog-calculator-percentage-slider:disabled { opacity: 0.5; cursor: not-allowed; background: #f0f0f0; pointer-events: none; } .dog-calculator-percentage-slider:disabled::-webkit-slider-thumb { background: #ccc; cursor: not-allowed; } .dog-calculator-percentage-slider:disabled::-webkit-slider-thumb:hover { background: #ccc; transform: none; } .dog-calculator-percentage-slider:disabled::-moz-range-thumb { background: #ccc; cursor: not-allowed; } .dog-calculator-percentage-input:disabled { opacity: 0.5; cursor: not-allowed; background-color: #f8f8f8; border-color: #ddd; pointer-events: none; } /* Dark theme disabled styles */ .dog-calculator-container.theme-dark .dog-calculator-percentage-slider:disabled { background: #2a2530; } .dog-calculator-container.theme-dark .dog-calculator-percentage-input:disabled { background-color: #2a2530; border-color: #3a3442; color: #8a8a8a; } /* System theme disabled styles */ @media (prefers-color-scheme: dark) { .dog-calculator-container.theme-system .dog-calculator-percentage-slider:disabled { background: #2a2530; } .dog-calculator-container.theme-system .dog-calculator-percentage-input:disabled { background-color: #2a2530; border-color: #3a3442; color: #8a8a8a; } } /* Food Amount Breakdown Styling */ .dog-calculator-food-amounts-section { margin-top: 1.5rem; padding: 1rem; background: var(--bg-secondary); border-radius: 8px; border: 1px solid var(--border-color); } .dog-calculator-section-title { margin: 0 0 1rem 0; font-size: 1rem; font-weight: 600; color: var(--text-primary); } .dog-calculator-food-amounts-list { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem; } .dog-calculator-food-amount-item { display: flex; justify-content: space-between; align-items: center; padding: 0.75rem; background: var(--bg-primary); border-radius: 6px; border: 1px solid var(--border-color); } .dog-calculator-food-amount-label { font-weight: 500; color: var(--text-primary); display: flex; align-items: center; gap: 0.5rem; } .dog-calculator-food-percentage { background: var(--primary-color); color: white; padding: 0.2rem 0.5rem; border-radius: 12px; font-size: 0.8rem; font-weight: 500; } .dog-calculator-lock-indicator { font-size: 0.8rem; opacity: 0.7; } .dog-calculator-food-amount-value { font-weight: 600; color: var(--text-primary); font-size: 1rem; } /* Warning styles for missing energy content */ .dog-calculator-warning { color: #e11d48; font-weight: 500; font-size: 1.2rem; text-align: left; cursor: help; } /* Inline unit selector in results */ .dog-calculator-inline-unit { margin-left: 12px; min-width: 110px; padding: 4px 8px; background: var(--bg-primary); border: 1px solid rgba(241, 154, 95, 0.4); border-radius: 6px; color: var(--text-primary); font-size: 0.9rem; font-weight: 500; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 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 8px center; background-size: 16px; padding-right: 32px; } .dog-calculator-inline-unit:hover { border-color: #f19a5f; box-shadow: 0 2px 6px rgba(241, 154, 95, 0.2); } .dog-calculator-inline-unit:focus { outline: none; border-color: #f19a5f; box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1); } /* Inline days input in breakdown header */ .dog-calculator-inline-days { width: 60px; padding: 2px 6px; border: 1px solid var(--border-color); border-radius: 4px; text-align: center; font-size: inherit; font-family: inherit; margin: 0 4px; } /* Unit selection buttons */ .dog-calculator-unit-buttons { display: flex; justify-content: center; gap: 16px; margin: 24px auto; flex-wrap: wrap; width: fit-content; } .dog-calculator-unit-btn { padding: 8px 14px; border: 2px solid var(--border-color); border-radius: 6px; background: var(--bg-primary); color: var(--text-primary); font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; min-width: 50px; text-align: center; } .dog-calculator-unit-btn:hover { border-color: #f19a5f; background: rgba(241, 154, 95, 0.1); } .dog-calculator-unit-btn.active { border-color: #f19a5f; background: #f19a5f; color: white; } /* Mobile responsive adjustments for inline unit selector */ @media (max-width: 576px) { .dog-calculator-result-item { display: flex; flex-direction: column; align-items: center; gap: 8px; } .dog-calculator-result-label { width: 100%; text-align: center; margin-bottom: 4px; } .dog-calculator-result-value { display: inline-block; } .dog-calculator-inline-unit { display: inline-block; margin-left: 8px; min-width: 90px; vertical-align: middle; } /* Center the breakdown header on mobile */ .dog-calculator-section-title { text-align: center; } /* Ensure food breakdown items stay on one line */ .dog-calculator-food-amount-item { display: flex; justify-content: space-between; align-items: center; flex-wrap: nowrap; text-align: left; } .dog-calculator-food-amount-label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; } .dog-calculator-food-amount-value { flex-shrink: 0; margin-left: 8px; text-align: right; } } .dog-calculator-total-row { display: flex; justify-content: space-between; align-items: center; padding: 1rem; background: var(--primary-color); color: white; border-radius: 6px; font-weight: 600; margin-top: 0.5rem; } .dog-calculator-total-label { font-size: 1rem; } .dog-calculator-total-value { font-size: 1.1rem; font-weight: 700; } .dog-calculator-full-width { flex: 1; } /* Editable Food Source Name Styling */ .dog-calculator-food-source-name-input { background: transparent; border: 2px solid transparent; color: var(--text-primary); font-size: 1.1rem; font-weight: 600; font-family: inherit; padding: 0.5rem 0; border-radius: 4px; width: 100%; outline: none; transition: all 0.2s ease; cursor: text; } .dog-calculator-food-source-name-input:hover { border-color: var(--border-color); background: var(--bg-secondary); padding: 0.5rem; } .dog-calculator-food-source-name-input:focus { border-color: var(--primary-color); background: var(--bg-primary); box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1); padding: 0.5rem; } .dog-calculator-food-source-name-input::placeholder { color: var(--text-secondary); opacity: 0.7; } /* Dark theme adjustments */ .dog-calculator-container.theme-dark .dog-calculator-food-source-name-input:hover { background: #2a2530; } .dog-calculator-container.theme-dark .dog-calculator-food-source-name-input:focus { background: #1e1a24; } /* System theme adjustments */ @media (prefers-color-scheme: dark) { .dog-calculator-container.theme-system .dog-calculator-food-source-name-input:hover { background: #2a2530; } .dog-calculator-container.theme-system .dog-calculator-food-source-name-input:focus { background: #1e1a24; } } /* Responsive adjustments */ @media (max-width: 576px) { .dog-calculator-food-amount-item { flex-direction: row !important; gap: 0.5rem; text-align: left !important; justify-content: space-between !important; align-items: center !important; flex-wrap: nowrap !important; } .dog-calculator-food-amount-label { justify-content: flex-start !important; text-align: left !important; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .dog-calculator-food-amount-value { flex-shrink: 0; margin-left: 8px; text-align: right !important; } .dog-calculator-food-source-name-input { font-size: 1rem; } }`; function injectStyles() { if (document.getElementById('dog-calculator-styles')) return; const style = document.createElement('style'); style.id = 'dog-calculator-styles'; style.textContent = CSS_STYLES; document.head.appendChild(style); } // ACTUAL JavaScript from iframe.html (transformed for widget use) /** * Dog Calorie Calculator - iframe version * by Canine Nutrition and Wellness */ class DogCalorieCalculatorWidget { constructor(container, options = {}) { this.container = container; this.options = { theme: options.theme || this.getThemeFromURL() || 'system', scale: options.scale || this.getScaleFromURL() || 1.0, ...options }; this.theme = this.options.theme; this.scale = this.options.scale; this.currentMER = 0; this.isImperial = false; this.foodSources = []; this.maxFoodSources = 5; this.init(); } init() { // Inject the calculator HTML into the container this.container.innerHTML = `

Dog's Characteristics

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

How much should I feed?

`; // Apply widget-specific settings this.applyTheme(); this.applyScale(); // Continue with original init logic this.applyTheme(); this.applyScale(); this.initializeFoodSources(); this.bindEvents(); this.updateUnitLabels(); this.setupIframeResize(); // Show the calculator with fade-in const container = this.container.querySelector('#dogCalculator'); container.classList.add('loaded'); } getThemeFromURL() { const urlParams = new URLSearchParams(window.location.search); const theme = urlParams.get('theme'); return ['light', 'dark', 'system'].includes(theme) ? theme : null; } getScaleFromURL() { const urlParams = new URLSearchParams(window.location.search); const scale = parseFloat(urlParams.get('scale')); return (!isNaN(scale) && scale >= 0.5 && scale <= 2.0) ? scale : null; } applyTheme() { const container = this.container.querySelector('#dogCalculator'); container.classList.remove('theme-light', 'theme-dark', 'theme-system'); container.classList.add('theme-' + this.theme); } applyScale() { const container = this.container.querySelector('#dogCalculator'); if (!container) return; // Clamp scale between 0.5 and 2.0 for usability const clampedScale = Math.max(0.5, Math.min(2.0, this.scale)); if (clampedScale !== 1.0) { container.style.transform = `scale(${clampedScale})`; container.style.transformOrigin = 'top center'; // Adjust container to account for scaling setTimeout(() => { const actualHeight = container.offsetHeight * clampedScale; container.style.marginBottom = `${(clampedScale - 1) * container.offsetHeight}px`; this.sendHeightToParent(); }, 100); } } // Food Source Management Methods initializeFoodSources() { this.addFoodSource(); this.updateAddButton(); } addFoodSource() { if (this.foodSources.length >= this.maxFoodSources) { return; } const id = this.generateFoodSourceId(); const foodSource = { id: id, name: `Food Source ${this.foodSources.length + 1}`, energy: '', energyUnit: this.isImperial ? 'kcalcup' : 'kcal100g', percentage: this.foodSources.length === 0 ? 100 : 0, isLocked: false }; this.foodSources.push(foodSource); this.redistributePercentages(); this.renderFoodSource(foodSource); this.updateAddButton(); this.updateRemoveButtons(); this.refreshAllPercentageUI(); } removeFoodSource(id) { if (this.foodSources.length <= 1) { return; // Cannot remove the last food source } const index = this.foodSources.findIndex(fs => fs.id === id); if (index === -1) return; this.foodSources.splice(index, 1); // Remove the DOM element const element = document.getElementById(`foodSource-${id}`); if (element) { element.remove(); } // Redistribute percentages among remaining sources this.redistributePercentages(); this.updateFoodSourceNames(); this.updateAddButton(); this.updateRemoveButtons(); this.refreshAllPercentageUI(); } generateFoodSourceId() { return 'fs_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5); } redistributePercentages() { const count = this.foodSources.length; if (count === 0) return; // Only redistribute among unlocked sources const unlockedSources = this.foodSources.filter(fs => !fs.isLocked); const lockedSources = this.foodSources.filter(fs => fs.isLocked); // Calculate total locked percentage const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0); // Available percentage for unlocked sources const availablePercentage = 100 - totalLockedPercentage; if (unlockedSources.length > 0) { const equalPercentage = Math.floor(availablePercentage / unlockedSources.length); const remainder = availablePercentage - (equalPercentage * unlockedSources.length); unlockedSources.forEach((fs, index) => { fs.percentage = equalPercentage + (index < remainder ? 1 : 0); }); } // Update the UI sliders and inputs this.refreshAllPercentageUI(); } // OBSOLETE METHODS - Replaced by new validation system // Keeping for reference but these are no longer used /* updatePercentageInputs() { this.foodSources.forEach(fs => { const slider = document.getElementById(`percentage-slider-${fs.id}`); const input = document.getElementById(`percentage-input-${fs.id}`); const display = document.getElementById(`percentage-display-${fs.id}`); if (slider) slider.value = fs.percentage; if (input) input.value = fs.percentage; if (display) display.textContent = `${fs.percentage}%`; }); // Update constraints after values are set this.updatePercentageConstraints(); } updatePercentageConstraints() { this.foodSources.forEach(fs => { const slider = document.getElementById(`percentage-slider-${fs.id}`); const input = document.getElementById(`percentage-input-${fs.id}`); if (!slider || !input) return; // Always keep full 0-100 scale for all sliders slider.max = 100; input.max = 100; if (fs.isLocked) { // Locked sources can't be changed slider.disabled = true; input.disabled = true; } else { // Calculate the maximum this source can have const lockedSources = this.foodSources.filter(other => other.id !== fs.id && other.isLocked); const totalLockedPercentage = lockedSources.reduce((sum, other) => sum + other.percentage, 0); const maxAllowed = 100 - totalLockedPercentage; // Re-enable slider.disabled = false; input.disabled = false; // Store max allowed for validation (we'll check this in event handlers) slider.dataset.maxAllowed = maxAllowed; input.dataset.maxAllowed = maxAllowed; // If current value exceeds max, adjust it if (fs.percentage > maxAllowed) { fs.percentage = maxAllowed; slider.value = maxAllowed; input.value = maxAllowed; document.getElementById(`percentage-display-${fs.id}`).textContent = `${maxAllowed}%`; } } }); } adjustPercentages(changedId, newPercentage) { const changedIndex = this.foodSources.findIndex(fs => fs.id === changedId); if (changedIndex === -1) return; const oldPercentage = this.foodSources[changedIndex].percentage; const difference = newPercentage - oldPercentage; this.foodSources[changedIndex].percentage = newPercentage; // Only redistribute among unlocked sources (excluding the changed one) const otherUnlockedSources = this.foodSources.filter((fs, index) => index !== changedIndex && !fs.isLocked ); // If this is the only unlocked source, force it to fill remaining percentage if (otherUnlockedSources.length === 0) { const lockedSources = this.foodSources.filter((fs, index) => index !== changedIndex && fs.isLocked ); const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0); const requiredPercentage = 100 - totalLockedPercentage; // Force the changed source to the required percentage this.foodSources[changedIndex].percentage = requiredPercentage; this.updatePercentageInputs(); this.updateFoodCalculations(); return; } // Calculate total locked percentage (excluding the changed source) const lockedSources = this.foodSources.filter((fs, index) => index !== changedIndex && fs.isLocked ); const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0); // Available percentage for unlocked sources const availablePercentage = 100 - newPercentage - totalLockedPercentage; const totalUnlockedPercentage = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0); if (totalUnlockedPercentage === 0) { // If all other unlocked sources are 0, distribute equally const equalShare = Math.floor(availablePercentage / otherUnlockedSources.length); const remainder = availablePercentage - (equalShare * otherUnlockedSources.length); otherUnlockedSources.forEach((fs, index) => { fs.percentage = equalShare + (index < remainder ? 1 : 0); }); } else { // Distribute proportionally among unlocked sources const scale = availablePercentage / totalUnlockedPercentage; let distributedTotal = 0; otherUnlockedSources.forEach((fs, index) => { if (index === otherUnlockedSources.length - 1) { // Last item gets the remainder to ensure exact 100% fs.percentage = availablePercentage - distributedTotal; } else { fs.percentage = Math.round(fs.percentage * scale); distributedTotal += fs.percentage; } }); } this.updatePercentageInputs(); this.updateFoodCalculations(); } */ // New validation system methods validatePercentageChange(sourceId, requestedValue) { // Find the source being changed const changedSource = this.foodSources.find(fs => fs.id === sourceId); if (!changedSource) { return { isValid: false, reason: 'Source not found' }; } // If the source is locked, no change allowed if (changedSource.isLocked) { return { isValid: false, reason: 'Source is locked' }; } // Ensure requested value is within bounds const clampedValue = Math.max(0, Math.min(100, requestedValue)); // Calculate locked and other unlocked totals const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked); const otherUnlockedSources = this.foodSources.filter(fs => fs.id !== sourceId && !fs.isLocked); const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0); // Check if the only unlocked source if (otherUnlockedSources.length === 0) { // This is the only unlocked source, must fill remaining percentage const requiredPercentage = 100 - totalLocked; return { isValid: true, actualValue: requiredPercentage, affectedSources: [{ id: sourceId, newPercentage: requiredPercentage }], reason: 'Only unlocked source, forced to fill remainder' }; } // Calculate available percentage for redistribution const availableForOthers = 100 - clampedValue - totalLocked; // Check if redistribution is possible if (availableForOthers < 0) { // Cannot accommodate this value const maxAllowed = 100 - totalLocked; return { isValid: true, actualValue: maxAllowed, affectedSources: this.calculateRedistribution(sourceId, maxAllowed, otherUnlockedSources), reason: 'Value clamped to maximum allowed' }; } // Calculate redistribution const affectedSources = this.calculateRedistribution(sourceId, clampedValue, otherUnlockedSources); return { isValid: true, actualValue: clampedValue, affectedSources: affectedSources, reason: 'Valid change' }; } calculateRedistribution(sourceId, newValue, otherUnlockedSources) { const result = [{ id: sourceId, newPercentage: newValue }]; if (otherUnlockedSources.length === 0) { return result; } // Calculate total locked percentage const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked); const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0); // Available percentage for other unlocked sources const availableForOthers = 100 - newValue - totalLocked; // Current total of other unlocked sources const currentOtherTotal = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0); if (currentOtherTotal === 0 || availableForOthers === 0) { // Distribute equally among other unlocked sources const equalShare = Math.floor(availableForOthers / otherUnlockedSources.length); const remainder = availableForOthers - (equalShare * otherUnlockedSources.length); otherUnlockedSources.forEach((fs, index) => { const newPercentage = equalShare + (index < remainder ? 1 : 0); result.push({ id: fs.id, newPercentage }); }); } else { // Distribute proportionally const scale = availableForOthers / currentOtherTotal; let distributedTotal = 0; otherUnlockedSources.forEach((fs, index) => { let newPercentage; if (index === otherUnlockedSources.length - 1) { // Last item gets remainder to ensure exact total newPercentage = availableForOthers - distributedTotal; } else { newPercentage = Math.round(fs.percentage * scale); distributedTotal += newPercentage; } result.push({ id: fs.id, newPercentage }); }); } return result; } applyValidatedChanges(validationResult) { if (!validationResult.isValid) { return false; } // Apply all percentage changes validationResult.affectedSources.forEach(change => { const source = this.foodSources.find(fs => fs.id === change.id); if (source) { source.percentage = change.newPercentage; } }); return true; } refreshAllPercentageUI() { this.foodSources.forEach(fs => { // Update all UI elements from single source of truth const slider = document.getElementById(`percentage-slider-${fs.id}`); const input = document.getElementById(`percentage-input-${fs.id}`); const display = document.getElementById(`percentage-display-${fs.id}`); if (slider) slider.value = fs.percentage; if (input) input.value = fs.percentage; if (display) display.textContent = `${fs.percentage}%`; // Update constraints and disabled states this.updateSliderConstraints(fs); }); // Update food calculations this.updateFoodCalculations(); } updateSliderConstraints(foodSource) { const slider = document.getElementById(`percentage-slider-${foodSource.id}`); const input = document.getElementById(`percentage-input-${foodSource.id}`); if (!slider || !input) return; // Always keep 0-100 scale slider.max = 100; input.max = 100; if (foodSource.isLocked) { slider.disabled = true; input.disabled = true; } else { // Calculate maximum allowed and store for validation const maxAllowed = this.calculateMaxAllowed(foodSource.id); slider.disabled = (maxAllowed <= 0); input.disabled = (maxAllowed <= 0); slider.dataset.maxAllowed = maxAllowed; input.dataset.maxAllowed = maxAllowed; } } calculateMaxAllowed(sourceId) { const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked); const otherUnlockedSources = this.foodSources.filter(fs => fs.id !== sourceId && !fs.isLocked); const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0); // If this is the only unlocked source, it must take up the remainder if (otherUnlockedSources.length === 0) { return 100 - totalLocked; } // Otherwise, maximum is 100 minus locked percentages return Math.max(0, 100 - totalLocked); } updateFoodSourceNames() { this.foodSources.forEach((fs, index) => { // Only update if the name is still the default pattern if (fs.name.match(/^Food Source \d+$/)) { fs.name = `Food Source ${index + 1}`; const titleElement = document.getElementById(`food-title-${fs.id}`); if (titleElement) { titleElement.value = fs.name; } } }); } updateAddButton() { const addBtn = this.container.querySelector('#addFoodBtn'); if (addBtn) { const remaining = this.maxFoodSources - this.foodSources.length; const buttonText = addBtn.querySelector('span:last-child'); if (remaining <= 0) { // Disable button and show max reached message addBtn.disabled = true; if (buttonText) { buttonText.textContent = `Maximum ${this.maxFoodSources} sources reached`; } } else { // Enable button with normal text addBtn.disabled = false; if (buttonText) { buttonText.textContent = 'Add another food source'; } } } } updateRemoveButtons() { // Show/hide remove buttons based on whether we have more than one source const hasMultipleSources = this.foodSources.length > 1; this.foodSources.forEach(fs => { const removeBtn = document.getElementById(`remove-${fs.id}`); if (removeBtn) { removeBtn.style.display = hasMultipleSources ? 'block' : 'none'; } }); } renderFoodSource(foodSource) { const container = this.container.querySelector('#foodSources'); if (!container) return; const cardHTML = `
Please enter a valid energy content
`; container.insertAdjacentHTML('beforeend', cardHTML); // Bind events for the new food source this.bindFoodSourceEvents(foodSource.id); } bindFoodSourceEvents(id) { // Name input events const nameInput = document.getElementById(`food-title-${id}`); // Energy input events const energyInput = document.getElementById(`energy-${id}`); const energyUnitSelect = document.getElementById(`energy-unit-${id}`); const percentageSlider = document.getElementById(`percentage-slider-${id}`); const percentageInput = document.getElementById(`percentage-input-${id}`); const removeBtn = document.getElementById(`remove-${id}`); const lockBtn = document.getElementById(`lock-${id}`); if (nameInput) { nameInput.addEventListener('input', () => { 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 }); nameInput.addEventListener('blur', () => { // If field is empty, restore default name if (!nameInput.value.trim()) { const defaultName = `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`; nameInput.value = defaultName; this.updateFoodSourceData(id, 'name', defaultName); this.updateFoodCalculations(); } }); } if (energyInput) { energyInput.addEventListener('input', () => { this.updateFoodSourceData(id, 'energy', energyInput.value); this.updateFoodCalculations(); }); energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id)); } if (energyUnitSelect) { energyUnitSelect.addEventListener('change', () => { this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value); this.updateFoodCalculations(); }); } if (percentageSlider) { percentageSlider.addEventListener('input', () => { const requestedValue = parseInt(percentageSlider.value); const result = this.validatePercentageChange(id, requestedValue); if (result.isValid) { this.applyValidatedChanges(result); } // Always refresh to ensure valid state this.refreshAllPercentageUI(); }); } if (percentageInput) { percentageInput.addEventListener('change', () => { const requestedValue = parseInt(percentageInput.value) || 0; const result = this.validatePercentageChange(id, requestedValue); if (result.isValid) { this.applyValidatedChanges(result); } this.refreshAllPercentageUI(); }); } if (removeBtn) { removeBtn.addEventListener('click', () => this.removeFoodSource(id)); } if (lockBtn) { lockBtn.addEventListener('click', () => this.toggleLock(id)); } } toggleLock(id) { const foodSource = this.foodSources.find(fs => fs.id === id); if (!foodSource) return; // Check if we're trying to lock the last unlocked source const unlockedSources = this.foodSources.filter(fs => !fs.isLocked); if (unlockedSources.length === 1 && unlockedSources[0].id === id) { // Cannot lock the last unlocked source alert('At least one food source must remain flexible for percentage adjustments.'); return; } // Toggle lock state foodSource.isLocked = !foodSource.isLocked; this.updateLockIcon(id); this.updateLockStates(); this.refreshAllPercentageUI(); } updateLockIcon(id) { const foodSource = this.foodSources.find(fs => fs.id === id); const lockIcon = document.getElementById(`lock-${id}`); if (!lockIcon || !foodSource) return; if (foodSource.isLocked) { lockIcon.classList.remove('unlocked'); lockIcon.classList.add('locked'); lockIcon.title = 'Unlock this percentage'; } else { lockIcon.classList.remove('locked'); lockIcon.classList.add('unlocked'); lockIcon.title = 'Lock this percentage'; } } updateLockStates() { const unlockedSources = this.foodSources.filter(fs => !fs.isLocked); // Update lock icon states - disable lock for last unlocked source this.foodSources.forEach(fs => { const lockIcon = document.getElementById(`lock-${fs.id}`); if (lockIcon) { if (!fs.isLocked && unlockedSources.length === 1) { lockIcon.classList.add('disabled'); lockIcon.title = 'Cannot lock - at least one source must remain flexible'; } else { lockIcon.classList.remove('disabled'); lockIcon.title = fs.isLocked ? 'Unlock this percentage' : 'Lock this percentage'; } } }); // Update percentage constraints based on lock states this.refreshAllPercentageUI(); } updateFoodSourceData(id, field, value) { const foodSource = this.foodSources.find(fs => fs.id === id); if (foodSource) { foodSource[field] = value; } } validateFoodSourceEnergy(id) { const energyInput = document.getElementById(`energy-${id}`); const energyUnitSelect = document.getElementById(`energy-unit-${id}`); const errorElement = document.getElementById(`energy-error-${id}`); if (!energyInput || !energyUnitSelect || !errorElement) return; const energy = parseFloat(energyInput.value); const unit = energyUnitSelect.value; let minValue = 1; switch (unit) { case 'kcal100g': minValue = 1; break; case 'kcalkg': minValue = 10; break; case 'kcalcup': minValue = 50; break; case 'kcalcan': minValue = 100; break; } if (!this.validateInput(energy, minValue)) { errorElement.classList.remove('dog-calculator-hidden'); } else { errorElement.classList.add('dog-calculator-hidden'); } } bindEvents() { const weightInput = this.container.querySelector('#weight'); const dogTypeSelect = this.container.querySelector('#dogType'); const daysInput = this.container.querySelector('#days'); const unitSelect = this.container.querySelector('#unit'); const unitToggle = this.container.querySelector('#unitToggle'); const addFoodBtn = this.container.querySelector('#addFoodBtn'); if (weightInput) { weightInput.addEventListener('input', () => this.updateCalorieCalculations()); weightInput.addEventListener('blur', () => this.validateWeight()); } if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations()); if (daysInput) { daysInput.addEventListener('input', () => { this.updateDayLabel(); this.updateFoodCalculations(); }); daysInput.addEventListener('blur', () => this.validateDays()); } if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations()); // Unit button event listeners const unitButtons = this.container.querySelectorAll('.dog-calculator-unit-btn'); unitButtons.forEach(button => { button.addEventListener('click', (e) => { const selectedUnit = e.target.dataset.unit; this.setActiveUnitButton(selectedUnit); // Update hidden select to trigger existing logic if (unitSelect) { unitSelect.value = selectedUnit; this.updateFoodCalculations(); } }); }); if (unitToggle) unitToggle.addEventListener('change', () => this.toggleUnits()); if (addFoodBtn) addFoodBtn.addEventListener('click', () => this.addFoodSource()); // Modal event listeners const shareBtn = this.container.querySelector('#shareBtn'); const embedBtn = this.container.querySelector('#embedBtn'); const shareModalClose = this.container.querySelector('#shareModalClose'); const embedModalClose = this.container.querySelector('#embedModalClose'); if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal()); if (embedBtn) embedBtn.addEventListener('click', () => this.showEmbedModal()); if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal()); if (embedModalClose) embedModalClose.addEventListener('click', () => this.hideEmbedModal()); // Share buttons const shareFacebook = this.container.querySelector('#shareFacebook'); const shareTwitter = this.container.querySelector('#shareTwitter'); const shareLinkedIn = this.container.querySelector('#shareLinkedIn'); const shareEmail = this.container.querySelector('#shareEmail'); const shareCopy = this.container.querySelector('#shareCopy'); if (shareFacebook) shareFacebook.addEventListener('click', () => this.shareToFacebook()); if (shareTwitter) shareTwitter.addEventListener('click', () => this.shareToTwitter()); if (shareLinkedIn) shareLinkedIn.addEventListener('click', () => this.shareToLinkedIn()); if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail()); if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink()); // Copy buttons const copyWidget = this.container.querySelector('#copyWidget'); const copyIframe = this.container.querySelector('#copyIframe'); if (copyWidget) copyWidget.addEventListener('click', () => this.copyEmbedCode('widget')); if (copyIframe) copyIframe.addEventListener('click', () => this.copyEmbedCode('iframe')); // Close modals on outside click const shareModal = this.container.querySelector('#shareModal'); const embedModal = this.container.querySelector('#embedModal'); if (shareModal) { shareModal.addEventListener('click', (e) => { if (e.target === shareModal) this.hideShareModal(); }); } if (embedModal) { embedModal.addEventListener('click', (e) => { if (e.target === embedModal) this.hideEmbedModal(); }); } } 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 weightInput = this.container.querySelector('#weight'); 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 (unitSelect) { unitSelect.innerHTML = '' + '' + '' + ''; unitSelect.value = 'oz'; // Auto-select ounces for imperial this.setActiveUnitButton('oz'); // Sync unit buttons } // Update energy units for all food sources to kcal/cup for imperial this.foodSources.forEach(fs => { if (fs.energyUnit === 'kcal100g') { fs.energyUnit = 'kcalcup'; const energyUnitSelect = document.getElementById(`energy-unit-${fs.id}`); if (energyUnitSelect) { energyUnitSelect.value = 'kcalcup'; } } }); } 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 (unitSelect) { unitSelect.innerHTML = '' + '' + '' + ''; unitSelect.value = 'g'; // Auto-select grams for metric this.setActiveUnitButton('g'); // Sync unit buttons } // Update energy units for all food sources to kcal/100g for metric this.foodSources.forEach(fs => { if (fs.energyUnit === 'kcalcup') { fs.energyUnit = 'kcal100g'; const energyUnitSelect = document.getElementById(`energy-unit-${fs.id}`); if (energyUnitSelect) { energyUnitSelect.value = 'kcal100g'; } } }); } } convertExistingValues() { const weightInput = this.container.querySelector('#weight'); 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); } } } } 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; } 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 = document.getElementById(elementId); if (errorElement) { if (show) { errorElement.classList.remove('dog-calculator-hidden'); } else { errorElement.classList.add('dog-calculator-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) { if (decimals === 0) { return Math.round(num).toString(); } return num.toFixed(decimals).replace(/\.?0+$/, ''); } validateWeight() { const weightKg = this.getWeightInKg(); if (weightKg !== null && weightKg < 0.1) { this.showError('weightError', true); } else { this.showError('weightError', 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); } } updateDayLabel() { const days = this.container.querySelector('#days')?.value; const dayLabel = this.container.querySelector('#dayLabel'); if (dayLabel && days) { const numDays = parseInt(days); dayLabel.textContent = numDays === 1 ? 'day' : 'days'; } } setActiveUnitButton(unit) { const unitButtons = this.container.querySelectorAll('.dog-calculator-unit-btn'); unitButtons.forEach(button => { button.classList.remove('active'); if (button.dataset.unit === unit) { button.classList.add('active'); } }); } 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(); this.sendHeightToParent(); } 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 foodAmountsSection = this.container.querySelector('#foodAmountsSection'); const foodAmountsList = this.container.querySelector('#foodAmountsList'); const totalAmountDisplay = this.container.querySelector('#totalAmountDisplay'); const foodBreakdownResults = this.container.querySelector('#foodBreakdownResults'); const foodBreakdownList = this.container.querySelector('#foodBreakdownList'); if (!daysInput || !unitSelect || !dailyFoodResults || !dailyFoodValue || !foodAmountsSection) { return; } const days = daysInput.value; const unit = unitSelect.value; const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb'; const decimals = unit === 'g' ? 0 : unit === 'kg' ? 2 : 1; // Clear all food source errors first this.foodSources.forEach(fs => { this.showError(`energy-error-${fs.id}`, false); }); this.showError('daysError', false); // Validate days input if (!days || !this.validateInput(days, 1, true)) { if (days) this.showError('daysError', true); foodAmountsSection.style.display = 'none'; dailyFoodResults.style.display = 'none'; if (foodBreakdownResults) foodBreakdownResults.style.display = 'none'; // Hide unit buttons when validation fails const unitButtons = this.container.querySelector('#unitButtons'); if (unitButtons) unitButtons.style.display = 'none'; return; } const numDays = parseInt(days); // Calculate per-food breakdown const foodBreakdowns = []; let totalDailyGrams = 0; let hasValidFoods = false; this.foodSources.forEach(fs => { const energyPer100g = this.getFoodSourceEnergyPer100g(fs); if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) { const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100; const dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100; foodBreakdowns.push({ name: fs.name, percentage: fs.percentage, dailyGrams: dailyGramsForThisFood, calories: dailyCaloriesForThisFood, isLocked: fs.isLocked, hasEnergyContent: true }); totalDailyGrams += dailyGramsForThisFood; hasValidFoods = true; } else if (fs.percentage > 0) { // Include food sources without energy content but show them as needing energy content foodBreakdowns.push({ name: fs.name, percentage: fs.percentage, dailyGrams: 0, calories: 0, isLocked: fs.isLocked, hasEnergyContent: false }); } }); if (!hasValidFoods) { // Show errors for invalid food sources this.foodSources.forEach(fs => { const energyInput = document.getElementById(`energy-${fs.id}`); if (energyInput && energyInput.value && (!this.getFoodSourceEnergyPer100g(fs) || this.getFoodSourceEnergyPer100g(fs) <= 0.1)) { this.showError(`energy-error-${fs.id}`, true); } }); dailyFoodResults.style.display = 'none'; if (foodBreakdownResults) foodBreakdownResults.style.display = 'none'; // Hide unit buttons when no valid foods const unitButtons = this.container.querySelector('#unitButtons'); if (unitButtons) unitButtons.style.display = 'none'; // If we have any food sources without energy content, still show the breakdown section if (foodBreakdowns.length > 0) { // Show food amounts section with warnings for missing energy content const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb'; const foodAmountsHTML = foodBreakdowns.map(breakdown => { const lockIndicator = breakdown.isLocked ? '🔒' : ''; return `
${breakdown.name} ${breakdown.percentage}% ${lockIndicator}
⚠️
`; }).join(''); if (foodAmountsList) { foodAmountsList.innerHTML = foodAmountsHTML; } if (totalAmountDisplay) { totalAmountDisplay.textContent = "Enter energy content for all foods"; } foodAmountsSection.style.display = 'block'; this.sendHeightToParent(); } else { foodAmountsSection.style.display = 'none'; } return; } // Update daily food results (total) - will be updated with proper units later dailyFoodResults.style.display = 'block'; // Show unit buttons when daily results are shown const unitButtons = this.container.querySelector('#unitButtons'); if (unitButtons) unitButtons.style.display = 'flex'; // Update per-food breakdown if (foodBreakdownList && foodBreakdowns.length > 1) { const breakdownHTML = foodBreakdowns.map(breakdown => { const valueContent = breakdown.hasEnergyContent ? `${this.formatNumber(this.convertUnits(breakdown.dailyGrams, unit), decimals)} ${unitLabel}/day` : `⚠️`; return `
${breakdown.name} (${breakdown.percentage}%${breakdown.isLocked ? ' - locked' : ''}): ${valueContent}
`; }).join(''); foodBreakdownList.innerHTML = breakdownHTML; if (foodBreakdownResults) foodBreakdownResults.style.display = 'block'; } else { if (foodBreakdownResults) foodBreakdownResults.style.display = 'none'; } // Generate individual food amount breakdown // Update daily food value with correct units const convertedDailyTotal = this.convertUnits(totalDailyGrams, unit); dailyFoodValue.textContent = this.formatNumber(convertedDailyTotal, decimals) + ` ${unitLabel}/day`; // Build HTML for individual food amounts const foodAmountsHTML = foodBreakdowns.map(breakdown => { const lockIndicator = breakdown.isLocked ? '🔒' : ''; if (!breakdown.hasEnergyContent) { // Show warning for food sources without energy content return `
${breakdown.name} ${breakdown.percentage}% ${lockIndicator}
⚠️
`; } else { const totalGramsForDays = breakdown.dailyGrams * numDays; const convertedAmount = this.convertUnits(totalGramsForDays, unit); return `
${breakdown.name} ${breakdown.percentage}% ${lockIndicator}
${this.formatNumber(convertedAmount, decimals)} ${unitLabel}
`; } }).join(''); // Calculate and display total const totalFoodGrams = totalDailyGrams * numDays; const totalConverted = this.convertUnits(totalFoodGrams, unit); // Update the display if (foodAmountsList) { foodAmountsList.innerHTML = foodAmountsHTML; } if (totalAmountDisplay) { totalAmountDisplay.textContent = `${this.formatNumber(totalConverted, decimals)} ${unitLabel}`; } foodAmountsSection.style.display = 'block'; this.sendHeightToParent(); } getFoodSourceEnergyPer100g(foodSource) { if (!foodSource.energy || !foodSource.energyUnit) return null; const energy = parseFloat(foodSource.energy); if (isNaN(energy)) return null; const unit = foodSource.energyUnit; // Convert all units to kcal/100g for internal calculations switch (unit) { case 'kcal100g': return energy; case 'kcalkg': return energy / 10; // 1 kg = 10 × 100g case 'kcalcup': return energy / 1.2; // Assume 1 cup ≈ 120g for dry dog food case 'kcalcan': return energy / 4.5; // Assume 1 can ≈ 450g for wet dog food default: return energy; } } setupIframeResize() { // Send height to parent window for iframe auto-resize this.sendHeightToParent(); // Monitor for content changes that might affect height const observer = new MutationObserver(() => { setTimeout(() => this.sendHeightToParent(), 100); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true }); // Send height on window resize window.addEventListener('resize', () => this.sendHeightToParent()); } sendHeightToParent() { const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); if (window.parent && window.parent !== window) { window.parent.postMessage({ type: 'dogCalculatorResize', height: height }, '*'); } } // Modal functionality showShareModal() { const modal = this.container.querySelector('#shareModal'); const shareUrl = this.container.querySelector('#shareUrl'); if (modal && shareUrl) { shareUrl.value = window.location.href; modal.style.display = 'block'; } } hideShareModal() { const modal = this.container.querySelector('#shareModal'); if (modal) modal.style.display = 'none'; } showEmbedModal() { const modal = this.container.querySelector('#embedModal'); const widgetCode = this.container.querySelector('#widgetCode'); const iframeCode = this.container.querySelector('#iframeCode'); if (modal && widgetCode && iframeCode) { // Build embed URL const baseUrl = window.location.protocol + '//embed.' + window.location.hostname; // Create widget code using createElement to avoid quote issues const scriptTag = document.createElement('script'); scriptTag.src = baseUrl + '/dog-calorie-calculator/dog-food-calculator-widget.js'; const divTag = document.createElement('div'); divTag.id = 'dog-calorie-calculator'; const widgetHtml = scriptTag.outerHTML + '\n' + divTag.outerHTML; widgetCode.textContent = widgetHtml; // Create iframe code using createElement const iframe = document.createElement('iframe'); iframe.src = baseUrl + '/dog-calorie-calculator/iframe.html'; iframe.width = '100%'; iframe.height = '600'; iframe.frameBorder = '0'; iframe.title = 'Dog Calorie Calculator'; iframeCode.textContent = iframe.outerHTML; modal.style.display = 'block'; } } hideEmbedModal() { const modal = this.container.querySelector('#embedModal'); if (modal) modal.style.display = 'none'; } shareToFacebook() { const url = encodeURIComponent(window.location.href); window.open('https://www.facebook.com/sharer/sharer.php?u=' + url, '_blank', 'width=600,height=400'); } shareToTwitter() { const url = encodeURIComponent(window.location.href); const text = encodeURIComponent('Check out this useful dog calorie calculator!'); window.open('https://twitter.com/intent/tweet?url=' + url + '&text=' + text, '_blank', 'width=600,height=400'); } shareToLinkedIn() { const url = encodeURIComponent(window.location.href); window.open('https://www.linkedin.com/sharing/share-offsite/?url=' + url, '_blank', 'width=600,height=400'); } shareViaEmail() { const subject = encodeURIComponent('Dog Calorie Calculator'); const body = encodeURIComponent('Check out this useful dog calorie calculator: ' + window.location.href); window.location.href = 'mailto:?subject=' + subject + '&body=' + body; } async copyShareLink() { const shareUrl = this.container.querySelector('#shareUrl'); const copyBtn = this.container.querySelector('#shareCopy'); if (shareUrl && copyBtn) { try { await navigator.clipboard.writeText(shareUrl.value); const originalText = copyBtn.textContent; copyBtn.textContent = 'Copied!'; copyBtn.classList.add('copied'); setTimeout(() => { copyBtn.textContent = originalText; copyBtn.classList.remove('copied'); }, 2000); } catch (err) { // Fallback for older browsers shareUrl.select(); document.execCommand('copy'); } } } async copyEmbedCode(type) { const codeElement = document.getElementById(type === 'widget' ? 'widgetCode' : 'iframeCode'); const copyBtn = document.getElementById(type === 'widget' ? 'copyWidget' : 'copyIframe'); if (codeElement && copyBtn) { try { await navigator.clipboard.writeText(codeElement.textContent); const originalText = copyBtn.textContent; copyBtn.textContent = 'Copied!'; copyBtn.classList.add('copied'); setTimeout(() => { copyBtn.textContent = originalText; copyBtn.classList.remove('copied'); }, 2000); } catch (err) { // Fallback for older browsers console.log('Copy fallback needed'); } } } } // Initialize calculator when DOM is ready // Auto-initialize widgets on page load function initializeWidget() { injectStyles(); const containers = document.querySelectorAll('#dog-calorie-calculator, .dog-calorie-calculator'); containers.forEach(container => { if (container.dataset.initialized) return; const options = { theme: container.dataset.theme || 'system', scale: parseFloat(container.dataset.scale) || 1.0 }; new DogCalorieCalculatorWidget(container, options); container.dataset.initialized = 'true'; }); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeWidget); } else { initializeWidget(); } // Export for manual initialization window.DogCalorieCalculatorWidget = DogCalorieCalculatorWidget; })();