sundog-calculator/iframe.html
2025-11-12 18:00:36 +01:00

3918 lines
158 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dog Calorie Calculator - Canine Nutrition and Wellness</title>
<style>
/* 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: hidden; /* hide internal scrollbars; parent resizes iframe */
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: 640px;
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);
}
/* Kaya end-weight readonly field: compact, non-editable */
#kayaEndWeight {
width: 120px;
display: inline-block;
}
.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;
gap: 10px; /* Add gap between label and value */
}
.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;
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
}
.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;
}
/* Embed button removed */
.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;
}
}
/* Feeding Configuration Styles */
.dog-calculator-container .dog-calculator-feeding-config {
margin-top: 20px;
padding: 16px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
}
.dog-calculator-container .dog-calculator-frequency-row {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.dog-calculator-container .dog-calculator-frequency-row > label {
font-weight: 500;
color: var(--text-label);
margin: 0;
}
.dog-calculator-container .dog-calculator-radio-group {
display: flex;
gap: 20px;
align-items: center;
}
.dog-calculator-container .dog-calculator-radio-group label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
color: var(--text-primary);
font-size: 0.95rem;
}
.dog-calculator-container .dog-calculator-radio-group input[type="radio"] {
cursor: pointer;
margin: 0;
}
.dog-calculator-container .dog-calculator-radio-group input[type="radio"]:checked + span {
font-weight: 600;
color: var(--accent-color);
}
.dog-calculator-container .dog-calculator-meal-input {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0 auto;
}
.dog-calculator-container .dog-calculator-meal-input span {
color: var(--text-secondary);
font-size: 0.95rem;
}
.dog-calculator-container .dog-calculator-meal-input input[type="number"] {
width: 50px;
padding: 4px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.95rem;
color: var(--text-primary);
background: var(--bg-secondary);
text-align: center;
}
.dog-calculator-container .dog-calculator-meal-input input[type="number"]:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(241, 154, 95, 0.1);
}
/* Update meal note styling */
.dog-calculator-container #mealNote {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: normal;
margin-left: 4px;
}
/* Mobile responsive adjustments for feeding config */
@media (max-width: 480px) {
.dog-calculator-meal-input {
margin-left: 0;
width: 100%;
margin-top: 8px;
}
.dog-calculator-frequency-row {
flex-direction: column;
align-items: flex-start;
}
/* Stack result items vertically on small screens */
.dog-calculator-result-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dog-calculator-result-label {
margin-right: 0;
font-size: 0.9rem;
}
.dog-calculator-result-value {
font-size: 1rem;
align-self: stretch;
text-align: center;
}
}
/* Dark theme - manual override */
.dog-calculator-container.theme-dark {
--bg-primary: #24202d;
--bg-secondary: #312b3b;
--bg-tertiary: #1f1b26;
--border-color: #433c4f;
--text-primary: #f5f3f7;
--text-secondary: #b8b0c2;
--text-label: #9f94ae;
--success-color: #7fa464;
--error-color: #e87159;
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-unit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
border-color: var(--border-color);
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.dog-calculator-container.theme-dark .dog-calculator-unit-btn:disabled:hover {
border-color: var(--border-color);
background: var(--bg-tertiary);
}
.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;
}
/* Embed button removed */
/* Dark theme feeding configuration styles */
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
background: var(--bg-tertiary);
border-color: var(--border-color);
}
.dog-calculator-container.theme-dark .dog-calculator-frequency-row > label {
color: var(--text-label);
}
.dog-calculator-container.theme-dark .dog-calculator-radio-group label {
color: var(--text-primary);
}
.dog-calculator-container.theme-dark .dog-calculator-radio-group input[type="radio"]:checked + span {
color: #f19a5f;
}
.dog-calculator-container.theme-dark .dog-calculator-meal-input span {
color: var(--text-secondary);
}
.dog-calculator-container.theme-dark .dog-calculator-meal-input input[type="number"] {
background: var(--bg-secondary);
border-color: var(--border-color);
color: var(--text-primary);
}
.dog-calculator-container.theme-dark .dog-calculator-meal-input input[type="number"]:focus {
border-color: #f19a5f;
box-shadow: 0 0 0 2px rgba(241, 154, 95, 0.15);
}
.dog-calculator-container.theme-dark #mealNote {
color: var(--text-secondary);
}
/* System theme - follows user's OS preference */
@media (prefers-color-scheme: dark) {
.dog-calculator-container.theme-system {
--bg-primary: #24202d;
--bg-secondary: #312b3b;
--bg-tertiary: #1f1b26;
--border-color: #433c4f;
--text-primary: #f5f3f7;
--text-secondary: #b8b0c2;
--text-label: #9f94ae;
--success-color: #7fa464;
--error-color: #e87159;
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-unit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
border-color: var(--border-color);
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.dog-calculator-container.theme-system .dog-calculator-unit-btn:disabled:hover {
border-color: var(--border-color);
background: var(--bg-tertiary);
}
.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;
}
/* Embed button removed */
/* System theme feeding configuration styles in dark mode */
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
background: var(--bg-tertiary);
border-color: var(--border-color);
}
.dog-calculator-container.theme-system .dog-calculator-frequency-row > label {
color: var(--text-label);
}
.dog-calculator-container.theme-system .dog-calculator-radio-group label {
color: var(--text-primary);
}
.dog-calculator-container.theme-system .dog-calculator-radio-group input[type="radio"]:checked + span {
color: #f19a5f;
}
.dog-calculator-container.theme-system .dog-calculator-meal-input span {
color: var(--text-secondary);
}
.dog-calculator-container.theme-system .dog-calculator-meal-input input[type="number"] {
background: var(--bg-secondary);
border-color: var(--border-color);
color: var(--text-primary);
}
.dog-calculator-container.theme-system .dog-calculator-meal-input input[type="number"]:focus {
border-color: #f19a5f;
box-shadow: 0 0 0 2px rgba(241, 154, 95, 0.15);
}
.dog-calculator-container.theme-system #mealNote {
color: var(--text-secondary);
}
}
/* Modal Styles */
.dog-calculator-modal {
display: none; /* set to flex via JS when opened */
position: fixed;
z-index: 10000;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 20px;
box-sizing: border-box;
overflow: auto; /* allow modal content scroll if needed */
align-items: center;
justify-content: center;
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: 0;
padding: 30px;
border: 1px solid var(--border-color);
border-radius: 12px;
width: 90%;
max-width: 500px;
max-height: 90vh; /* ensure it fits viewport */
overflow: auto;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
animation: slideIn 0.3s ease;
}
/* Embed modal removed */
@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: #6f3f6d; }
.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 UI removed */
/* 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);
}
/* Embed UI removed for dark theme */
/* 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);
}
/* Embed UI removed for system theme */
}
/* 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;
}
.dog-calculator-unit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
border-color: var(--border-color);
background: var(--bg-tertiary);
color: var(--text-secondary);
}
.dog-calculator-unit-btn:disabled:hover {
border-color: var(--border-color);
background: var(--bg-tertiary);
transform: none;
}
/* Hidden unit select for compatibility */
.dog-calculator-unit-select-hidden {
display: none;
}
/* 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;
}
}
</style>
</head>
<body>
<div class="dog-calculator-container" id="dogCalculator">
<div class="dog-calculator-section">
<div class="dog-calculator-section-header">
<h2>Kayas Transition</h2>
</div>
<div class="dog-calculator-form-group">
<label for="ageMonths">Kayas age (months):</label>
<input type="number" id="ageMonths" min="2" max="12" step="0.1" placeholder="Enter age in months" aria-describedby="ageHelp">
<div id="ageClampNote" class="dog-calculator-error dog-calculator-hidden">Age adjusted to the supported 212 month range.</div>
</div>
<div class="dog-calculator-form-group">
<label for="kayaEndWeight">Kayas endweight:</label>
<input type="text" id="kayaEndWeight" value="30 kg" readonly>
</div>
</div>
<div class="dog-calculator-collapsible active" id="foodCalculator">
<div class="dog-calculator-collapsible-header">
<h3>How much should I feed?</h3>
</div>
<div class="dog-calculator-collapsible-content">
<div class="dog-calculator-collapsible-inner">
<!-- Food Sources Container -->
<div class="dog-calculator-food-sources" id="foodSources">
<!-- Initial food source will be added by JavaScript -->
</div>
<!-- Add Food Source Button -->
<button class="dog-calculator-add-food-btn" id="addFoodBtn" type="button">
<span>+</span>
<span>Add another food source</span>
</button>
<!-- Feeding Configuration -->
<div class="dog-calculator-feeding-config" id="feedingConfig" style="display: none;">
<div class="dog-calculator-frequency-row">
<label>Show amounts:</label>
<div class="dog-calculator-radio-group">
<label>
<input type="radio" name="showAs" value="daily" id="showDaily" checked>
<span>Per day</span>
</label>
<label>
<input type="radio" name="showAs" value="meal" id="showPerMeal">
<span>Per meal</span>
</label>
</div>
<div class="dog-calculator-meal-input" id="mealInputGroup" style="display: none;">
<span>×</span>
<input type="number" id="mealsPerDay" value="2" min="1" max="10">
<span>meals/day</span>
</div>
</div>
</div>
<!-- Per-Food Results -->
<div class="dog-calculator-food-results" id="foodBreakdownResults" style="display: none;">
<div id="foodBreakdownList">
<!-- Individual food breakdowns will be populated by JavaScript -->
</div>
</div>
<!-- Unit Selection Buttons -->
<div class="dog-calculator-unit-buttons" id="unitButtons" style="display: none;">
<button type="button" class="dog-calculator-unit-btn active" data-unit="g">g</button>
<button type="button" class="dog-calculator-unit-btn" data-unit="kg">kg</button>
</div>
<!-- Daily Total Results -->
<div class="dog-calculator-results" id="dailyFoodResults" style="display: none;">
<div class="dog-calculator-result-item">
<span class="dog-calculator-result-label">Total Daily Amount:</span>
<span class="dog-calculator-result-value" id="dailyFoodValue">- g/day</span>
</div>
</div>
<!-- Hidden select for compatibility -->
<select id="unit" class="dog-calculator-unit-select-hidden" aria-describedby="unitHelp">
<option value="g">grams (g)</option>
<option value="kg">kilograms (kg)</option>
</select>
<div class="dog-calculator-food-amounts-section" id="foodAmountsSection" style="display: none;">
<h4 class="dog-calculator-section-title">
Calculate amounts for
<input type="number" id="days" min="1" step="1" value="1" placeholder="1" aria-describedby="daysHelp" class="dog-calculator-inline-days">
<span id="dayLabel">day</span><span id="mealNote" style="display: none;"></span>:
</h4>
<div id="daysError" class="dog-calculator-error dog-calculator-hidden">Please enter a valid number of days (minimum 1)</div>
<div id="foodAmountsList" class="dog-calculator-food-amounts-list">
<!-- Individual food amounts will be populated here -->
</div>
<div class="dog-calculator-total-row" id="totalAmountRow">
<span class="dog-calculator-total-label">Total Amount:</span>
<span class="dog-calculator-total-value" id="totalAmountDisplay"></span>
</div>
</div>
</div>
</div>
</div>
<div class="dog-calculator-footer">
<a href="https://caninenutritionandwellness.com" target="_blank" rel="noopener noreferrer">
by caninenutritionandwellness.com
</a>
</div>
</div>
<script>
/**
* Configuration constants for Dog Calorie Calculator
*/
const CALCULATOR_CONFIG = {
defaultTheme: 'system',
defaultScale: 1.0,
maxFoodSources: 5,
minScale: 0.5,
maxScale: 2.0
};
/**
* Dog Calorie Calculator - iframe version
* by Canine Nutrition and Wellness
*/
class DogCalorieCalculator {
constructor() {
this.currentMER = 0;
this.currentMERMin = 0; // For range calculations
this.currentMERMax = 0; // For range calculations
this.isImperial = false;
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
this.foodSources = [];
this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
this.mealsPerDay = 2;
this.showPerMeal = false;
// Kayafied reference source tracking
this.kibbleRefId = null;
this.gcRefId = null;
this.init();
}
init() {
this.applyTheme();
this.applyScale();
this.initializeFoodSources();
this.bindEvents();
this.updateUnitLabels();
this.setupIframeResize();
// Show the calculator with fade-in
const container = document.getElementById('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 >= CALCULATOR_CONFIG.minScale && scale <= CALCULATOR_CONFIG.maxScale) ? scale : null;
}
applyTheme() {
const container = document.getElementById('dogCalculator');
container.classList.remove('theme-light', 'theme-dark', 'theme-system');
container.classList.add('theme-' + this.theme);
}
applyScale() {
const container = document.getElementById('dogCalculator');
if (!container) return;
// Clamp scale between min and max for usability
const clampedScale = Math.max(CALCULATOR_CONFIG.minScale, Math.min(CALCULATOR_CONFIG.maxScale, this.scale));
if (clampedScale !== 1.0) {
container.style.transform = `scale(${clampedScale})`;
container.style.transformOrigin = 'top center';
// Recalculate height for parent without adding artificial margins
setTimeout(() => {
this.sendHeightToParent();
}, 100);
}
}
// Food Source Management Methods
initializeFoodSources() {
// Seed three sources for Kaya's transition
const gc = {
id: this.generateFoodSourceId(),
name: 'Fred & Felia (Junior Huhn)',
energy: '115',
energyUnit: 'kcal100g',
percentage: 5,
isLocked: false,
chartType: 'gc'
};
this.foodSources.push(gc);
this.renderFoodSource(gc);
this.gcRefId = gc.id;
const kibble = {
id: this.generateFoodSourceId(),
name: 'Eukanuba (Large Breed Fresh Chicken)',
energy: '372',
energyUnit: 'kcal100g',
percentage: 95,
isLocked: false,
chartType: 'kibble'
};
this.foodSources.push(kibble);
this.renderFoodSource(kibble);
this.kibbleRefId = kibble.id;
const treats = {
id: this.generateFoodSourceId(),
name: 'Treats',
energy: '',
energyUnit: 'kcal100g',
percentage: 0,
isLocked: false,
chartType: null
};
this.foodSources.push(treats);
this.renderFoodSource(treats);
this.updateAddButton();
this.updateRemoveButtons();
this.refreshAllPercentageUI();
}
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);
// If reference IDs were removed, clear them
if (this.kibbleRefId === id) this.kibbleRefId = null;
if (this.gcRefId === id) this.gcRefId = null;
// Remove the DOM element
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();
this.updateCalorieCalculations();
}
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 = document.getElementById('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 = document.getElementById('foodSources');
if (!container) return;
const cardHTML = `
<div class="dog-calculator-food-source-card" id="foodSource-${foodSource.id}">
<div class="dog-calculator-food-source-header">
<input type="text" class="dog-calculator-food-source-name-input" id="food-title-${foodSource.id}" value="${foodSource.name}" placeholder="Enter food name" maxlength="50" title="Click to edit food source name">
<button class="dog-calculator-remove-food-btn" id="remove-${foodSource.id}" type="button" title="Remove this food source" style="display: ${this.foodSources.length > 1 ? 'block' : 'none'}">×</button>
</div>
<div class="dog-calculator-input-group">
<div class="dog-calculator-form-group">
<label for="energy-${foodSource.id}">Energy Content:</label>
<input type="number" id="energy-${foodSource.id}" min="1" step="1" placeholder="Enter energy content" value="${foodSource.energy}">
</div>
<div class="dog-calculator-form-group">
<label for="energy-unit-${foodSource.id}">Unit:</label>
<select id="energy-unit-${foodSource.id}" class="dog-calculator-unit-select">
<option value="kcal100g" ${foodSource.energyUnit === 'kcal100g' ? 'selected' : ''}>kcal/100g</option>
<option value="kcalkg" ${foodSource.energyUnit === 'kcalkg' ? 'selected' : ''}>kcal/kg</option>
<option value="kcalcup" ${foodSource.energyUnit === 'kcalcup' ? 'selected' : ''}>kcal/cup</option>
<option value="kcalcan" ${foodSource.energyUnit === 'kcalcan' ? 'selected' : ''}>kcal/can</option>
</select>
</div>
</div>
<div id="energy-error-${foodSource.id}" class="dog-calculator-error dog-calculator-hidden">Please enter a valid energy content</div>
<div class="dog-calculator-percentage-group">
<label class="dog-calculator-percentage-label" for="percentage-slider-${foodSource.id}">
Percentage of Diet: <span id="percentage-display-${foodSource.id}">${foodSource.percentage}%</span>
<span class="dog-calculator-lock-icon unlocked" id="lock-${foodSource.id}" title="Lock this percentage">🔒</span>
</label>
<div class="dog-calculator-percentage-input-group">
<input type="range" id="percentage-slider-${foodSource.id}" class="dog-calculator-percentage-slider"
min="0" max="100" value="${foodSource.percentage}">
<input type="number" id="percentage-input-${foodSource.id}" class="dog-calculator-percentage-input"
min="0" max="100" value="${foodSource.percentage}">
</div>
</div>
</div>
`;
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);
// If kibble reference changed, recompute daily target
if (id === this.kibbleRefId) {
this.updateCalorieCalculations();
}
// Auto-select cups when entering energy for kcal/cup
const foodSource = this.foodSources.find(fs => fs.id === id);
if (foodSource && foodSource.energyUnit === 'kcalcup' && parseFloat(energyInput.value) > 0) {
// Cups display removed; default to grams
const unitSelect = document.getElementById('unit');
if (unitSelect) {
unitSelect.value = 'g';
unitSelect.setAttribute('value', 'g');
this.setActiveUnitButton('g');
}
}
this.updateFoodCalculations();
});
energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id));
}
if (energyUnitSelect) {
energyUnitSelect.addEventListener('change', () => {
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
if (id === this.kibbleRefId) {
this.updateCalorieCalculations();
}
// Auto-select the most appropriate unit based on energy unit
const unitSelect = document.getElementById('unit');
const energyInput = document.getElementById(`energy-${id}`);
if (unitSelect) {
switch(energyUnitSelect.value) {
case 'kcalcup':
// Cups display not available; default to grams
unitSelect.value = 'g';
this.setActiveUnitButton('g');
this.updateFoodCalculations();
break;
case 'kcal100g':
// For kcal/100g, select grams
unitSelect.value = 'g';
this.setActiveUnitButton('g');
this.updateFoodCalculations();
break;
case 'kcalkg':
// For kcal/kg, also select grams (or could be kg)
unitSelect.value = 'g';
this.setActiveUnitButton('g');
this.updateFoodCalculations();
break;
case 'kcalcan':
// For kcal/can, use grams as default
unitSelect.value = 'g';
this.setActiveUnitButton('g');
this.updateFoodCalculations();
break;
}
} else {
// No unit select, just update calculations
this.updateFoodCalculations();
}
});
}
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 = document.getElementById('weight');
const dogTypeSelect = document.getElementById('dogType');
const ageInput = document.getElementById('ageMonths');
const daysInput = document.getElementById('days');
const unitSelect = document.getElementById('unit');
const unitToggle = document.getElementById('unitToggle');
const addFoodBtn = document.getElementById('addFoodBtn');
if (weightInput) {
weightInput.addEventListener('input', () => this.updateCalorieCalculations());
weightInput.addEventListener('blur', () => this.validateWeight());
}
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
// Kayafied: age input drives energy target
if (ageInput) {
ageInput.addEventListener('input', () => this.updateCalorieCalculations());
ageInput.addEventListener('blur', () => this.updateCalorieCalculations());
}
if (daysInput) {
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 = document.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());
// Feeding configuration event listeners
const showDaily = document.getElementById('showDaily');
const showPerMeal = document.getElementById('showPerMeal');
const mealsPerDayInput = document.getElementById('mealsPerDay');
const mealInputGroup = document.getElementById('mealInputGroup');
if (showDaily) {
showDaily.addEventListener('change', () => {
if (showDaily.checked) {
this.showPerMeal = false;
if (mealInputGroup) mealInputGroup.style.display = 'none';
this.updateDayLabel();
this.updateFoodCalculations();
}
});
}
if (showPerMeal) {
showPerMeal.addEventListener('change', () => {
if (showPerMeal.checked) {
this.showPerMeal = true;
if (mealInputGroup) mealInputGroup.style.display = 'inline-flex';
this.updateDayLabel();
this.updateFoodCalculations();
}
});
}
if (mealsPerDayInput) {
mealsPerDayInput.addEventListener('input', () => {
const meals = parseInt(mealsPerDayInput.value);
if (meals && meals >= 1 && meals <= 10) {
this.mealsPerDay = meals;
if (this.showPerMeal) {
this.updateDayLabel();
this.updateFoodCalculations();
}
}
});
mealsPerDayInput.addEventListener('blur', () => {
if (!mealsPerDayInput.value || parseInt(mealsPerDayInput.value) < 1) {
mealsPerDayInput.value = 2;
this.mealsPerDay = 2;
if (this.showPerMeal) {
this.updateDayLabel();
this.updateFoodCalculations();
}
}
});
}
// Modal event listeners
const shareBtn = document.getElementById('shareBtn');
const shareModalClose = document.getElementById('shareModalClose');
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
// Share buttons
const shareFacebook = document.getElementById('shareFacebook');
const shareTwitter = document.getElementById('shareTwitter');
const shareLinkedIn = document.getElementById('shareLinkedIn');
const shareEmail = document.getElementById('shareEmail');
const shareCopy = document.getElementById('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());
// Embed copy buttons removed (embedding disabled)
// Close modals on outside click
const shareModal = document.getElementById('shareModal');
if (shareModal) {
shareModal.addEventListener('click', (e) => {
if (e.target === shareModal) this.hideShareModal();
});
}
// Embed modal removed
}
toggleUnits() {
const toggle = document.getElementById('unitToggle');
this.isImperial = toggle.checked;
this.updateUnitLabels();
this.convertExistingValues();
this.updateCalorieCalculations();
}
updateUnitLabels() {
const metricLabel = document.getElementById('metricLabel');
const imperialLabel = document.getElementById('imperialLabel');
const weightLabel = document.getElementById('weightLabel');
const weightInput = document.getElementById('weight');
const unitSelect = document.getElementById('unit');
if (metricLabel && imperialLabel) {
metricLabel.classList.toggle('active', !this.isImperial);
imperialLabel.classList.toggle('active', this.isImperial);
}
// Kaya: restrict to metric g/kg only
if (unitSelect) {
unitSelect.innerHTML = '<option value="g">grams (g)</option>' +
'<option value="kg">kilograms (kg)</option>';
if (!unitSelect.value || (unitSelect.value !== 'g' && unitSelect.value !== 'kg')) {
unitSelect.value = 'g';
this.setActiveUnitButton('g');
}
}
}
convertExistingValues() {
const weightInput = document.getElementById('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 = document.getElementById('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;
}
// Get the range multipliers for each life stage
getLifeStageRange(factor) {
// Define ranges based on the reference image
const ranges = {
'3.0': { min: 3.0, max: 3.0 }, // Puppy 0-4 months (no range)
'2.0': { min: 2.0, max: 2.0 }, // Puppy 4m-adult OR Working light (no range for puppies)
'1.2': { min: 1.2, max: 1.4 }, // Adult inactive/obese
'1.6': { min: 1.4, max: 1.6 }, // Adult neutered/spayed
'1.8': { min: 1.6, max: 1.8 }, // Adult intact
'1.0': { min: 1.0, max: 1.0 }, // Weight loss (fixed)
'1.7': { min: 1.2, max: 1.8 }, // Weight gain (wide range)
'5.0': { min: 5.0, max: 5.0 }, // Working heavy (upper bound)
'1.1': { min: 1.1, max: 1.1 } // Senior (no range)
};
const key = factor.toFixed(1);
return ranges[key] || { min: factor, max: factor };
}
validateInput(value, min = 0, isInteger = false) {
const num = parseFloat(value);
if (isNaN(num) || num < min) return false;
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, foodSource = null) {
switch (unit) {
case 'kg':
return grams / 1000;
case 'oz':
return grams / 28.3495;
case 'lb':
return grams / 453.592;
case 'cups':
// For cups, we need to convert from grams worth of calories to cups
if (foodSource && foodSource.energyUnit === 'kcalcup' && foodSource.energy) {
// Get calories per 100g for this food
const caloriesPerGram = this.getFoodSourceEnergyPer100g(foodSource) / 100;
// Calculate total calories represented by these grams
const totalCalories = grams * caloriesPerGram;
// Divide by calories per cup to get number of cups
const caloriesPerCup = parseFloat(foodSource.energy);
return totalCalories / caloriesPerCup;
}
return null; // Cannot convert to cups without kcal/cup
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 = document.getElementById('days')?.value;
if (days && !this.validateInput(days, 1, true)) {
this.showError('daysError', true);
} else {
this.showError('daysError', false);
}
}
updateDayLabel() {
const days = document.getElementById('days')?.value;
const dayLabel = document.getElementById('dayLabel');
const mealNote = document.getElementById('mealNote');
if (dayLabel && days) {
const numDays = parseInt(days);
dayLabel.textContent = numDays === 1 ? 'day' : 'days';
}
if (mealNote) {
if (this.showPerMeal && days) {
const numDays = parseInt(days);
const totalMeals = numDays * this.mealsPerDay;
mealNote.textContent = ` (${totalMeals} meal${totalMeals === 1 ? '' : 's'} total)`;
mealNote.style.display = 'inline';
} else {
mealNote.style.display = 'none';
}
}
}
setActiveUnitButton(unit) {
const unitButtons = document.querySelectorAll('.dog-calculator-unit-btn');
unitButtons.forEach(button => {
button.classList.remove('active');
if (button.dataset.unit === unit) {
button.classList.add('active');
}
});
}
updateCalorieCalculations() {
// Kaya-specific: only track age and trigger recompute
const ageInput = document.getElementById('ageMonths');
const ageClampNote = document.getElementById('ageClampNote');
if (ageClampNote) ageClampNote.classList.add('dog-calculator-hidden');
if (!ageInput || ageInput.value === '') {
this.currentAge = null;
this.updateFoodCalculations();
return;
}
let age = parseFloat(ageInput.value);
if (isNaN(age)) {
this.currentAge = null;
this.updateFoodCalculations();
return;
}
if (age < 2) { age = 2; if (ageClampNote) ageClampNote.classList.remove('dog-calculator-hidden'); }
if (age > 12) { age = 12; if (ageClampNote) ageClampNote.classList.remove('dog-calculator-hidden'); }
this.currentAge = age;
this.updateFoodCalculations();
}
// Kaya: monthly table with interpolation between months
getKayaKibbleGramsForAge(ageMonths) {
// Precomputed monthly values for 30 kg (g/day)
const table = {
2: 250,
3: 330,
4: 365,
5: 382,
6: 400,
7: 405,
8: 410,
9: 410,
10: 410,
11: 408,
12: 405
};
// Exact integer month
const lower = Math.floor(ageMonths);
const upper = Math.ceil(ageMonths);
if (lower === upper) return table[lower] || 0;
// Linear interpolation between bounds
const lowerVal = table[lower] || 0;
const upperVal = table[upper] || 0;
const t = (ageMonths - lower) / (upper - lower);
return lowerVal + (upperVal - lowerVal) * t;
}
// Kaya: GC chart interpolation across buckets
getGCChartGramsForAge(ageMonths) {
// Buckets per guideline with boundary rule (5.0 → later bucket, 7.0 → later bucket)
// <5 mo (2.0<5.0): 950→1350
// 56 mo (5.06.0): 1250→1550; (6.0<7.0): hold 1550
// 712 mo (7.012.0): 1300→1500
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
const age = clamp(ageMonths, 2, 12);
if (age < 5) {
const t = (age - 2) / (5 - 2);
return 950 + t * (1350 - 950);
} else if (age < 6) {
const t = (age - 5) / (6 - 5);
return 1250 + t * (1550 - 1250);
} else if (age < 7) {
return 1550; // hold upper value until 7.0
} else {
const t = (age - 7) / (12 - 7);
return 1300 + t * (1500 - 1300);
}
}
updateCupsButtonState() {
// Cups UI is not used in this configuration
return;
}
updateFoodCalculations() {
// Chart-first: no MER check
const hasRange = false;
const daysInput = document.getElementById('days');
const unitSelect = document.getElementById('unit');
const dailyFoodResults = document.getElementById('dailyFoodResults');
const dailyFoodValue = document.getElementById('dailyFoodValue');
const foodAmountsSection = document.getElementById('foodAmountsSection');
const foodAmountsList = document.getElementById('foodAmountsList');
const totalAmountDisplay = document.getElementById('totalAmountDisplay');
const foodBreakdownResults = document.getElementById('foodBreakdownResults');
const foodBreakdownList = document.getElementById('foodBreakdownList');
const feedingConfig = document.getElementById('feedingConfig');
// Update cups button state
this.updateCupsButtonState();
if (!daysInput || !unitSelect || !dailyFoodResults || !dailyFoodValue || !foodAmountsSection) {
return;
}
const days = daysInput.value;
let unit = unitSelect.value;
// Failsafe: if unit is empty string but cups button is active, use 'cups'
if (!unit || unit === '') {
const activeButton = document.querySelector('.dog-calculator-unit-btn.active');
if (activeButton) {
unit = activeButton.dataset.unit || 'g';
} else {
unit = 'g'; // Default fallback
}
}
const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : unit === 'lb' ? 'lb' : 'cups';
const decimals = unit === 'g' ? 0 : unit === 'kg' ? 2 : unit === 'cups' ? 1 : 1;
// Debug: log what unit is being used
console.log('UpdateFoodCalculations - unit:', unit, 'unitLabel:', unitLabel);
// Determine frequency suffix for display
const frequencySuffix = this.showPerMeal ? '/meal' : '/day';
// 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';
if (feedingConfig) feedingConfig.style.display = 'none';
// Hide unit buttons when validation fails
const unitButtons = document.getElementById('unitButtons');
if (unitButtons) unitButtons.style.display = 'none';
return;
}
const numDays = parseInt(days);
// Require a valid age for chart-first outputs
const ageInput = document.getElementById('ageMonths');
let age = ageInput && ageInput.value !== '' ? parseFloat(ageInput.value) : null;
if (age !== null) {
if (isNaN(age)) age = null;
if (age !== null) {
if (age < 2) age = 2;
if (age > 12) age = 12;
}
}
// Calculate per-food breakdown (chart-first)
const foodBreakdowns = [];
let totalDailyGrams = 0;
let hasValidFoods = false;
// First pass: charted baseline
let chartedKcal = 0;
let chartedPercent = 0;
const firstPass = [];
this.foodSources.forEach(fs => {
const energyPer100g = this.getFoodSourceEnergyPer100g(fs);
let chartGrams = null;
if (fs.chartType === 'gc') {
chartGrams = age !== null ? this.getGCChartGramsForAge(age) : null;
} else if (fs.chartType === 'kibble') {
chartGrams = age !== null ? this.getKayaKibbleGramsForAge(age) : null;
}
const gramsPortion = (chartGrams !== null && chartGrams !== undefined) ? (chartGrams * (fs.percentage || 0) / 100) : null;
if (gramsPortion !== null && energyPer100g && energyPer100g > 0) {
chartedKcal += gramsPortion * (energyPer100g / 100);
chartedPercent += (fs.percentage || 0);
}
firstPass.push({ fs, energyPer100g, gramsPortion });
});
const kcalPerPercent = chartedPercent > 0 ? (chartedKcal / chartedPercent) : null;
// Second pass: finalize amounts
firstPass.forEach(({ fs, energyPer100g, gramsPortion }) => {
let dailyGramsForThisFood = 0;
let hasEnergyContent = !!(energyPer100g && energyPer100g > 0);
if ((fs.chartType === 'gc' || fs.chartType === 'kibble')) {
if (gramsPortion !== null) {
dailyGramsForThisFood = gramsPortion;
}
} else {
if (hasEnergyContent && kcalPerPercent && (fs.percentage || 0) > 0) {
const perGramKcal = energyPer100g / 100;
const kcalForFood = (fs.percentage || 0) * kcalPerPercent;
dailyGramsForThisFood = kcalForFood / perGramKcal;
} else {
hasEnergyContent = false;
dailyGramsForThisFood = 0;
}
}
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
foodBreakdowns.push({
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
displayGrams: displayGrams,
dailyCups: null,
displayCups: null,
calories: hasEnergyContent ? (dailyGramsForThisFood * (energyPer100g / 100)) : 0,
displayCalories: hasEnergyContent ? (this.showPerMeal ? (dailyGramsForThisFood * (energyPer100g / 100)) / this.mealsPerDay : (dailyGramsForThisFood * (energyPer100g / 100))) : 0,
isLocked: fs.isLocked,
hasEnergyContent: hasEnergyContent,
foodSource: fs
});
totalDailyGrams += dailyGramsForThisFood;
if (dailyGramsForThisFood > 0) hasValidFoods = true;
});
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';
if (feedingConfig) feedingConfig.style.display = 'none';
// Hide unit buttons when no valid foods
const unitButtons = document.getElementById('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 ? '<span class="dog-calculator-lock-indicator">🔒</span>' : '';
return `
<div class="dog-calculator-food-amount-item">
<div class="dog-calculator-food-amount-label">
<span>${breakdown.name}</span>
<span class="dog-calculator-food-percentage">${breakdown.percentage}%</span>
${lockIndicator}
</div>
<div class="dog-calculator-food-amount-value dog-calculator-warning" title="Enter energy content to calculate amount">
⚠️
</div>
</div>
`;
}).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 feeding configuration when we have valid foods
if (feedingConfig) {
feedingConfig.style.display = 'block';
// Ensure "Per day" is checked when feeding config becomes visible
const showDaily = document.getElementById('showDaily');
if (showDaily && !showDaily.checked && !document.getElementById('showPerMeal').checked) {
showDaily.checked = true;
}
}
// Show unit buttons when daily results are shown
const unitButtons = document.getElementById('unitButtons');
if (unitButtons) unitButtons.style.display = 'flex';
// Update per-food breakdown
if (foodBreakdownList && foodBreakdowns.length > 1) {
const breakdownHTML = foodBreakdowns.map(breakdown => {
let valueContent;
if (breakdown.hasEnergyContent) {
if (unit === 'cups') {
// For cups, use the pre-calculated cups value if available
if (breakdown.displayCups !== null) {
if (breakdown.hasRange && breakdown.displayCupsMin !== breakdown.displayCupsMax) {
valueContent = `${this.formatNumber(breakdown.displayCupsMin, decimals)}-${this.formatNumber(breakdown.displayCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
// For other units (g, kg, oz, lb)
if (breakdown.hasRange && breakdown.displayGramsMin !== breakdown.displayGramsMax) {
const minConverted = this.convertUnits(breakdown.displayGramsMin, unit);
const maxConverted = this.convertUnits(breakdown.displayGramsMax, unit);
valueContent = `${this.formatNumber(minConverted, decimals)}-${this.formatNumber(maxConverted, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
}
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
}
return `
<div class="dog-calculator-food-result-item">
<span class="dog-calculator-food-result-label">${breakdown.name} (${breakdown.percentage}%${breakdown.isLocked ? ' - locked' : ''}):</span>
<span class="dog-calculator-food-result-value">${valueContent}</span>
</div>
`;
}).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 displayTotal = this.showPerMeal ? totalDailyGrams / this.mealsPerDay : totalDailyGrams;
let convertedTotal;
let totalDisplayText;
if (unit === 'cups') {
console.log('Unit is cups, checking validity...');
// For cups, we can only show total if all foods with percentage > 0 have kcal/cup
const validForCups = foodBreakdowns.filter(b => b.percentage > 0)
.every(b => b.displayCups !== null && b.displayCups !== undefined);
console.log('Valid for cups?', validForCups, 'Breakdowns:', foodBreakdowns);
if (validForCups) {
// Calculate total cups using pre-calculated values
let totalCups = 0;
let totalCupsMin = 0;
let totalCupsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
totalCups += breakdown.displayCups;
if (breakdown.hasRange) {
totalCupsMin += breakdown.displayCupsMin || breakdown.displayCups;
totalCupsMax += breakdown.displayCupsMax || breakdown.displayCups;
} else {
totalCupsMin += breakdown.displayCups;
totalCupsMax += breakdown.displayCups;
}
}
});
if (hasRange && totalCupsMin !== totalCupsMax) {
totalDisplayText = `${this.formatNumber(totalCupsMin, decimals)}-${this.formatNumber(totalCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
totalDisplayText = 'Mixed units - see breakdown';
}
} else {
// Calculate totals for ranges
if (hasRange) {
let totalGramsMin = 0;
let totalGramsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.hasEnergyContent) {
totalGramsMin += breakdown.displayGramsMin || breakdown.displayGrams;
totalGramsMax += breakdown.displayGramsMax || breakdown.displayGrams;
}
});
const convertedMin = this.convertUnits(totalGramsMin, unit);
const convertedMax = this.convertUnits(totalGramsMax, unit);
if (totalGramsMin !== totalGramsMax) {
totalDisplayText = `${this.formatNumber(convertedMin, decimals)}-${this.formatNumber(convertedMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(convertedMin, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
}
dailyFoodValue.textContent = totalDisplayText;
// Build HTML for individual food amounts
const foodAmountsHTML = foodBreakdowns.map(breakdown => {
const lockIndicator = breakdown.isLocked ? '<span class="dog-calculator-lock-indicator">🔒</span>' : '';
if (!breakdown.hasEnergyContent) {
// Show warning for food sources without energy content
return `
<div class="dog-calculator-food-amount-item">
<div class="dog-calculator-food-amount-label">
<span>${breakdown.name}</span>
<span class="dog-calculator-food-percentage">${breakdown.percentage}%</span>
${lockIndicator}
</div>
<div class="dog-calculator-food-amount-value dog-calculator-warning" title="Enter energy content to calculate amount">
⚠️
</div>
</div>
`;
} else {
// For multi-day calculations: show total amount for all days
let amountDisplay;
if (unit === 'cups') {
// For cups, use pre-calculated cups value
if (breakdown.dailyCups !== null) {
const totalCupsForDays = breakdown.dailyCups * numDays;
amountDisplay = `${this.formatNumber(totalCupsForDays, decimals)} ${unitLabel}`;
} else {
amountDisplay = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
// For other units, calculate from grams
const totalGramsForDays = this.showPerMeal
? (breakdown.dailyGrams / this.mealsPerDay) * numDays * this.mealsPerDay
: breakdown.dailyGrams * numDays;
const convertedAmount = this.convertUnits(totalGramsForDays, unit);
amountDisplay = `${this.formatNumber(convertedAmount, decimals)} ${unitLabel}`;
}
return `
<div class="dog-calculator-food-amount-item">
<div class="dog-calculator-food-amount-label">
<span>${breakdown.name}</span>
<span class="dog-calculator-food-percentage">${breakdown.percentage}%</span>
${lockIndicator}
</div>
<div class="dog-calculator-food-amount-value">
${amountDisplay}
</div>
</div>
`;
}
}).join('');
// Calculate and display total
const totalFoodGrams = totalDailyGrams * numDays;
// Update the display
if (foodAmountsList) {
foodAmountsList.innerHTML = foodAmountsHTML;
}
if (totalAmountDisplay) {
if (unit === 'cups') {
// For cups total, check if all foods can be converted
const validForCups = foodBreakdowns.filter(b => b.percentage > 0)
.every(b => b.dailyCups !== null && b.dailyCups !== undefined);
if (validForCups) {
// Calculate total cups using pre-calculated values
let totalCups = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.dailyCups !== null) {
totalCups += breakdown.dailyCups * numDays;
}
});
totalAmountDisplay.textContent = `${this.formatNumber(totalCups, decimals)} ${unitLabel}`;
} else {
totalAmountDisplay.textContent = 'Mixed units - see individual amounts';
}
} else {
const totalConverted = this.convertUnits(totalFoodGrams, unit);
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;
}
}
// Iframe auto-resize for allowed embeddings
setupIframeResize() {
// Only when embedded in an iframe
if (window.top === window.self) return;
// Initial send once UI is ready
setTimeout(() => this.sendHeightToParent(), 50);
// Monitor for content/attribute changes
const observer = new MutationObserver(() => {
clearTimeout(this._resizeTimer);
this._resizeTimer = setTimeout(() => this.sendHeightToParent(), 100);
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true
});
// On viewport resize
window.addEventListener('resize', () => this.sendHeightToParent());
}
sendHeightToParent() {
if (!(window.parent && window.parent !== window)) return;
const container = document.getElementById('dogCalculator');
// Prefer visual height including transform scaling
let height = 0;
if (container) {
const rect = container.getBoundingClientRect();
height = Math.ceil(rect.height);
} else {
height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
}
window.parent.postMessage({
type: 'dogCalculatorResize',
height: height
}, '*');
}
// Modal functionality
showShareModal() {
const modal = document.getElementById('shareModal');
const shareUrl = document.getElementById('shareUrl');
if (modal && shareUrl) {
shareUrl.value = window.location.href;
// Use flex so content is centered within modal viewport
modal.style.display = 'flex';
// Sync modal scroll position with current page scroll so content is visible
try { modal.scrollTop = window.scrollY || document.documentElement.scrollTop || 0; } catch (e) {}
// Ensure the modal is visible even when the page is scrolled
// by recalculating parent iframe height (defensive)
this.sendHeightToParent();
}
}
hideShareModal() {
const modal = document.getElementById('shareModal');
if (modal) modal.style.display = 'none';
this.sendHeightToParent();
}
// Embed modal removed (embedding disabled)
// Embed modal removed (embedding disabled)
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 = document.getElementById('shareUrl');
const copyBtn = document.getElementById('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');
}
}
}
// Embed code copy removed (embedding disabled)
}
// Initialize calculator when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
// Allow embedding only from approved parent hosts
if (window.top !== window.self) {
const allowedHosts = ['caninenutritionandwellness.com', 'www.caninenutritionandwellness.com'];
let parentAllowed = false;
// Prefer document.referrer when available
try {
if (document.referrer) {
const r = new URL(document.referrer);
parentAllowed = allowedHosts.includes(r.hostname);
}
} catch (e) {}
// Fallback: Chrome's ancestorOrigins (may be empty or absent)
if (!parentAllowed && window.location.ancestorOrigins && window.location.ancestorOrigins.length) {
parentAllowed = Array.from(window.location.ancestorOrigins).some((originStr) => {
try {
const o = new URL(originStr);
return allowedHosts.includes(o.hostname);
} catch (e) {
return false;
}
});
}
if (!parentAllowed) {
document.body.innerHTML = '<div style="max-width:720px;margin:40px auto;padding:16px;border:1px solid #ddd;border-radius:8px;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;color:#333;line-height:1.5;text-align:center;">Embedding of this calculator is only allowed on caninenutritionandwellness.com.</div>';
return;
}
}
new DogCalorieCalculator();
});
</script>
</body>
</html>