diff --git a/README.md b/README.md
index f7b5cac..b957374 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# π Sundog Dog Food Calorie Calculator
-A professional veterinary nutrition tool for calculating dogs' daily calorie requirements and food amounts. Features advanced multi-food source management, percentage locking, and detailed food amount breakdowns. Built for embedding on websites with complete brand protection options.
+A professional veterinary nutrition tool for calculating dogs' daily calorie requirements and food amounts. Features advanced multi-food source management, percentage locking, and detailed food amount breakdowns. Distributed as a standalone page; thirdβparty embedding is no longer supported.
**By [Canine Nutrition and Wellness](https://caninenutritionandwellness.com)**
@@ -28,10 +28,9 @@ A professional veterinary nutrition tool for calculating dogs' daily calorie req
- **Lock Indicators**: Visual indicators showing which percentages are locked
### User Experience
-- **Scalable Widget**: Easily resize from 50% to 200% with data attributes
+- **Scalable UI**: Resize from 50% to 200% via query params
- **Theme Support**: Light, dark, and system themes
- **Responsive Design**: Mobile-first, optimized layouts for all devices
-- **Two Embedding Options**: JavaScript widget and iframe
- **Accessibility**: Full keyboard navigation and screen reader support
### Brand & Integration
@@ -39,36 +38,16 @@ A professional veterinary nutrition tool for calculating dogs' daily calorie req
- **Professional Design**: Clean, veterinary-grade interface
- **Brand Protection**: Complete iframe isolation option
-## π Quick Start
+## π Usage
+
+- Open `iframe.html` locally, or host it as a standalone page on your site.
+- Embedding is allowed only on these domains: `caninenutritionandwellness.com`, `www.caninenutritionandwellness.com`.
+- Use an iframe to embed on your site, for example:
-### Option 1: JavaScript Widget (Recommended)
```html
-
-
-
-
-
-
-```
-
-### Option 2: iframe Embed
-```html
-
-
-
-
```
@@ -134,7 +113,7 @@ Choose from three themes:
- `system` - Follows user's OS preference (default)
### Scale Options
-Resize the widget from 50% to 200%:
+Resize the interface from 50% to 200%:
- Range: `0.5` to `2.0`
- Default: `1.0` (100% size)
- Examples: `0.8` (80%), `1.2` (120%), `1.5` (150%)
@@ -146,27 +125,21 @@ Support for regional differences:
- `kcal/cup` - US/Canada dry food
- `kcal/can` - US/Canada wet food
-### Advanced JavaScript Usage
-```javascript
-new DogCalorieCalculatorWidget(container, {
- theme: 'dark', // 'light', 'dark', 'system'
- scale: 1.2 // 0.5 to 2.0
-});
-```
+
## π οΈ Development
### Build System
-This project uses a single source of truth approach:
+This project uses an organized source layout compiled into a single page:
-- **Master Source**: `iframe.html` - Contains all functionality, styles, and calculations
-- **Build Script**: `build.js` - Generates the widget from iframe.html
-- **Generated Output**: `sundog-dog-food-calculator.js` - Embeddable widget
+- **Sources**: `src/` (HTML, CSS, JS modules)
+- **Build Script**: `build.js` - Generates `iframe.html` from `src/`
+- **Output**: `iframe.html` - Standalone calculator page
### Development Workflow
-1. **Make changes to `iframe.html`** - Edit calculations, design, layout, or functionality
+1. **Make changes in `src/`** - Edit calculations, design, layout, or functionality
2. **Run the build script**: `node build.js`
-3. **Done!** - Both iframe and widget now have identical functionality
+3. **Done!** - `iframe.html` is regenerated
### Why This Approach?
- β
**Single Source of Truth** - No need to maintain two separate files
@@ -175,18 +148,16 @@ This project uses a single source of truth approach:
- β
**No Sync Issues** - Build script ensures consistency
### Build Script Features
-- Extracts CSS, HTML, and JavaScript from iframe.html
-- Transforms CSS classes for widget namespacing (`dog-calculator-` β `dog-calc-`)
+- Compiles CSS, HTML, and JavaScript from `src/`
- Preserves all functionality including unit switching and calculations
-- Maintains theme and scale support via data attributes
+- Maintains theme and scale support via URL query parameters
## π Project Structure
```
-βββ iframe.html # π― MASTER SOURCE - Edit this file
+βββ src/ # βοΈ Source (HTML/CSS/JS)
βββ build.js # π§ Build script - Run after changes
-βββ sundog-dog-food-calculator.js # π¦ Generated widget (don't edit)
-βββ test-widget.html # π§ͺ Test file for widget
+βββ iframe.html # π¦ Generated standalone page
βββ README.md # π This file
```
@@ -217,71 +188,32 @@ Colors automatically adapt to light/dark themes via CSS custom properties.
| Working dog - heavy work | 5.0 | Intensive work |
| Senior dog | 1.1 | Reduced activity |
-## π§ Technical Implementation
+## π§ Technical Notes
+- Standalone page with theme and scale controls via URL params.
+- Embedding is allowlisted at runtime to your domains and should be enforced with server headers.
-### JavaScript Widget Features
-- **Auto-initialization**: Detects `#dog-calorie-calculator` containers
-- **CSS Namespacing**: All classes prefixed with `dog-calc-`
-- **Shadow DOM Ready**: Prepared for better style isolation
-- **Real-time Validation**: Input validation with error messages
-- **Mobile Optimized**: Responsive breakpoints and touch-friendly
+### Server Headers (required for robust enforcement)
+Configure your server or CDN to send this header on `iframe.html`:
-### iframe Features
-- **Auto-resize**: Communicates height changes to parent
-- **Style Isolation**: Complete protection from host site CSS
-- **Loading Animation**: Smooth fade-in when ready
-- **Cross-origin Ready**: PostMessage communication for integration
+```
+Content-Security-Policy: frame-ancestors https://caninenutritionandwellness.com https://www.caninenutritionandwellness.com;
+```
+
+Optional legacy header (deprecated but harmless as a supplement):
+
+```
+X-Frame-Options: SAMEORIGIN
+```
+
+If you serve the calculator from a subdomain (e.g., `embed.caninenutritionandwellness.com`) and embed it on the root domain, prefer the CSP `frame-ancestors` directive above.
## π Deployment Guide
-### 1. Host the Files
-Upload these files to your web server:
-- `sundog-dog-food-calculator.js` (for widget embedding)
-- `iframe.html` (for iframe embedding)
-
-### 2. Update URLs
-Replace `https://yourdomain.com` in:
-- `test-widget.html` examples
-- `sundog-dog-food-calculator.js` comments
-- This README
-
-### 3. CDN Distribution (Optional)
-For better performance, serve the widget script via CDN:
-- Use CloudFlare, AWS CloudFront, or similar
-- Enable CORS headers for cross-origin requests
-- Set appropriate cache headers (1 day for updates)
-
-### 4. Analytics Integration
-Add tracking to understand usage:
-
-```javascript
-// Track widget interactions
-document.addEventListener('DOMContentLoaded', function() {
- // Track when calculator is used
- document.addEventListener('change', function(e) {
- if (e.target.closest('.dog-calc-widget')) {
- gtag('event', 'calculator_interaction', {
- 'event_category': 'dog_calculator',
- 'event_label': e.target.id
- });
- }
- });
-});
-```
+### Deployment
+Host `iframe.html` (e.g., on `embed.caninenutritionandwellness.com`) and embed via iframe on your approved domains.
## π Brand Protection
-
-### JavaScript Widget Risks
-Users can override your styling with:
-```css
-.dog-calc-footer { display: none !important; }
-```
-
-### iframe Protection
-Your branding is completely protected in iframe mode. Users cannot:
-- Remove your footer link
-- Modify your styling
-- Access your content with JavaScript
+Embedding is disabled to protect branding and ensure consistent presentation.
## π± Mobile Optimization
@@ -353,9 +285,8 @@ This calculator is provided for educational and professional use. The formulas a
## π Links
- **Website**: [caninenutritionandwellness.com](https://caninenutritionandwellness.com)
-- **Widget Demo**: Open `test-widget.html` in your browser
- **Standalone**: Open `iframe.html` in your browser
---
-**Built with β€οΈ for canine nutrition professionals**
\ No newline at end of file
+**Built with β€οΈ for canine nutrition professionals**
diff --git a/build.js b/build.js
index 7e7e64c..16630a7 100644
--- a/build.js
+++ b/build.js
@@ -3,8 +3,8 @@
/**
* Dog Calculator Build System - ORGANIZED VERSION
*
- * This build script generates iframe.html and sundog-dog-food-calculator.js
- * from organized source files in the src/ directory.
+ * This build script generates iframe.html from organized source files
+ * in the src/ directory.
*
* Source structure:
* - src/index.html - HTML template
@@ -15,7 +15,6 @@
*
* Output files:
* - iframe.html - Standalone calculator page
- * - sundog-dog-food-calculator.js - Embeddable widget
*
* Usage: node build.js
*/
@@ -85,7 +84,7 @@ function backupFiles() {
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
- const filesToBackup = ['iframe.html', 'sundog-dog-food-calculator.js'];
+ const filesToBackup = ['iframe.html'];
filesToBackup.forEach(file => {
if (fs.existsSync(file)) {
@@ -299,10 +298,7 @@ function build() {
fs.writeFileSync('iframe.html', iframeContent);
console.log(' β
Generated iframe.html');
- // Generate widget
- const widgetCode = createWidgetJS(css, html, js);
- fs.writeFileSync('sundog-dog-food-calculator.js', widgetCode);
- console.log(' β
Generated sundog-dog-food-calculator.js');
+ // Embeddable widget generation removed (embedding no longer supported)
console.log('');
console.log('π Build completed successfully!');
@@ -317,12 +313,11 @@ function build() {
console.log('');
console.log(' Generated files:');
console.log(' β’ iframe.html - Standalone calculator');
- console.log(' β’ sundog-dog-food-calculator.js - Embeddable widget');
console.log('');
console.log('π Your workflow:');
console.log(' 1. Edit organized files in src/');
console.log(' 2. Run: node build.js');
- console.log(' 3. Both output files are regenerated!');
+ console.log(' 3. Output file is regenerated!');
console.log('');
console.log('π‘ Clean, organized structure - easy to maintain!');
@@ -338,4 +333,4 @@ if (require.main === module) {
build();
}
-module.exports = { build };
\ No newline at end of file
+module.exports = { build };
diff --git a/iframe.html b/iframe.html
index 2db4ceb..c38d605 100644
--- a/iframe.html
+++ b/iframe.html
@@ -25,7 +25,7 @@
margin: 0;
padding: 0;
background: transparent;
- overflow-x: hidden;
+ 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);
@@ -331,10 +331,7 @@
color: #9f5999;
}
- .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
+ /* Embed button removed */
.dog-calculator-footer {
text-align: center;
@@ -729,10 +726,7 @@
color: #f19a5f;
}
- .dog-calculator-container.theme-dark .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
+ /* Embed button removed */
/* Dark theme feeding configuration styles */
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
@@ -935,10 +929,7 @@
color: #f19a5f;
}
- .dog-calculator-container.theme-system .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
+ /* Embed button removed */
/* System theme feeding configuration styles in dark mode */
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
@@ -980,13 +971,18 @@
/* Modal Styles */
.dog-calculator-modal {
- display: none;
+ 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;
}
@@ -998,19 +994,19 @@
.dog-calculator-modal-content {
position: relative;
background-color: var(--bg-secondary);
- margin: 5% auto;
+ 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;
}
- .dog-calculator-modal-embed {
- max-width: 700px;
- }
+ /* Embed modal removed */
@keyframes slideIn {
from {
@@ -1096,74 +1092,7 @@
color: var(--text-primary);
}
- /* Embed Modal */
- .dog-calculator-embed-options {
- display: flex;
- flex-direction: column;
- gap: 24px;
- }
-
- .dog-calculator-embed-option {
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 20px;
- background: #fcfafd;
- }
-
- .dog-calculator-embed-option h4 {
- margin: 0 0 8px 0;
- color: var(--text-primary);
- font-size: 1.1rem;
- }
-
- .dog-calculator-embed-option p {
- margin: 0 0 16px 0;
- color: var(--text-label);
- font-size: 0.9rem;
- }
-
- /* Default (light theme) code containers */
- .dog-calculator-code-container {
- position: relative;
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- overflow: hidden;
- }
-
- .dog-calculator-code-container pre {
- margin: 0;
- padding: 16px 60px 16px 16px;
- overflow-x: auto;
- }
-
- .dog-calculator-code-container code {
- color: var(--text-primary);
- font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
- font-size: 0.85rem;
- line-height: 1.4;
- }
-
- .dog-calculator-copy-btn {
- position: absolute;
- top: 8px;
- right: 8px;
- padding: 6px 10px;
- background: #f19a5f;
- color: white;
- border: none;
- border-radius: 4px;
- font-size: 0.8rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s ease;
- font-family: inherit;
- z-index: 1;
- }
-
- .dog-calculator-copy-btn:hover { background: #e87741; }
- .dog-calculator-copy-btn.copied { background: var(--success-color); }
- .dog-calculator-copy-btn.copied:hover { background: var(--success-color); }
+ /* Embed UI removed */
/* Dark theme modal styles */
.dog-calculator-container.theme-dark .dog-calculator-modal-content {
@@ -1189,28 +1118,7 @@
color: var(--text-primary);
}
- .dog-calculator-container.theme-dark .dog-calculator-embed-option {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option h4 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option p {
- color: var(--text-secondary)
- }
-
- /* Dark theme code containers - different from embed option background */
- .dog-calculator-container.theme-dark .dog-calculator-code-container {
- background: #1a1621;
- border-color: #2a2330;
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-code-container code {
- color: var(--text-primary);
- }
+ /* Embed UI removed for dark theme */
/* System theme modal styles */
@media (prefers-color-scheme: dark) {
@@ -1237,28 +1145,7 @@
color: var(--text-primary);
}
- .dog-calculator-container.theme-system .dog-calculator-embed-option {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option h4 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option p {
- color: var(--text-secondary)
- }
-
- /* System theme code containers - different from embed option background */
- .dog-calculator-container.theme-system .dog-calculator-code-container {
- background: #1a1621;
- border-color: #2a2330;
- }
-
- .dog-calculator-container.theme-system .dog-calculator-code-container code {
- color: var(--text-primary);
- }
+ /* Embed UI removed for system theme */
}
/* Multi-Food Source Styles */
@@ -2219,9 +2106,6 @@
Share
-
- Embed
-
-
-
-
-
×
-
β‘ Embed the Calculator
-
-
-
-
β‘ JavaScript Widget
-
-
-
-
-
π‘οΈ iframe Embed
-
-
-
-
-
+
diff --git a/src/css/main.css b/src/css/main.css
index d08276a..5758566 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -15,7 +15,7 @@
margin: 0;
padding: 0;
background: transparent;
- overflow-x: hidden;
+ 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);
@@ -321,10 +321,7 @@
color: #9f5999;
}
- .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
+ /* Embed button removed */
.dog-calculator-footer {
text-align: center;
diff --git a/src/css/themes.css b/src/css/themes.css
index 5c81819..9d91e63 100644
--- a/src/css/themes.css
+++ b/src/css/themes.css
@@ -160,10 +160,7 @@
color: #f19a5f;
}
- .dog-calculator-container.theme-dark .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
+ /* Embed button removed */
/* Dark theme feeding configuration styles */
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
@@ -366,10 +363,7 @@
color: #f19a5f;
}
- .dog-calculator-container.theme-system .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
+ /* Embed button removed */
/* System theme feeding configuration styles in dark mode */
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
@@ -411,13 +405,18 @@
/* Modal Styles */
.dog-calculator-modal {
- display: none;
+ 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;
}
@@ -429,19 +428,19 @@
.dog-calculator-modal-content {
position: relative;
background-color: var(--bg-secondary);
- margin: 5% auto;
+ 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;
}
- .dog-calculator-modal-embed {
- max-width: 700px;
- }
+ /* Embed modal removed */
@keyframes slideIn {
from {
@@ -527,74 +526,7 @@
color: var(--text-primary);
}
- /* Embed Modal */
- .dog-calculator-embed-options {
- display: flex;
- flex-direction: column;
- gap: 24px;
- }
-
- .dog-calculator-embed-option {
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 20px;
- background: #fcfafd;
- }
-
- .dog-calculator-embed-option h4 {
- margin: 0 0 8px 0;
- color: var(--text-primary);
- font-size: 1.1rem;
- }
-
- .dog-calculator-embed-option p {
- margin: 0 0 16px 0;
- color: var(--text-label);
- font-size: 0.9rem;
- }
-
- /* Default (light theme) code containers */
- .dog-calculator-code-container {
- position: relative;
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- overflow: hidden;
- }
-
- .dog-calculator-code-container pre {
- margin: 0;
- padding: 16px 60px 16px 16px;
- overflow-x: auto;
- }
-
- .dog-calculator-code-container code {
- color: var(--text-primary);
- font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
- font-size: 0.85rem;
- line-height: 1.4;
- }
-
- .dog-calculator-copy-btn {
- position: absolute;
- top: 8px;
- right: 8px;
- padding: 6px 10px;
- background: #f19a5f;
- color: white;
- border: none;
- border-radius: 4px;
- font-size: 0.8rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s ease;
- font-family: inherit;
- z-index: 1;
- }
-
- .dog-calculator-copy-btn:hover { background: #e87741; }
- .dog-calculator-copy-btn.copied { background: var(--success-color); }
- .dog-calculator-copy-btn.copied:hover { background: var(--success-color); }
+ /* Embed UI removed */
/* Dark theme modal styles */
.dog-calculator-container.theme-dark .dog-calculator-modal-content {
@@ -620,28 +552,7 @@
color: var(--text-primary);
}
- .dog-calculator-container.theme-dark .dog-calculator-embed-option {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option h4 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option p {
- color: var(--text-secondary)
- }
-
- /* Dark theme code containers - different from embed option background */
- .dog-calculator-container.theme-dark .dog-calculator-code-container {
- background: #1a1621;
- border-color: #2a2330;
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-code-container code {
- color: var(--text-primary);
- }
+ /* Embed UI removed for dark theme */
/* System theme modal styles */
@media (prefers-color-scheme: dark) {
@@ -668,28 +579,7 @@
color: var(--text-primary);
}
- .dog-calculator-container.theme-system .dog-calculator-embed-option {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option h4 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option p {
- color: var(--text-secondary)
- }
-
- /* System theme code containers - different from embed option background */
- .dog-calculator-container.theme-system .dog-calculator-code-container {
- background: #1a1621;
- border-color: #2a2330;
- }
-
- .dog-calculator-container.theme-system .dog-calculator-code-container code {
- color: var(--text-primary);
- }
+ /* Embed UI removed for system theme */
}
/* Multi-Food Source Styles */
diff --git a/src/index.html b/src/index.html
index c515b13..53503c0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -143,9 +143,6 @@
Share
-
- Embed
-
-
-
-
-
×
-
β‘ Embed the Calculator
-
-
-
-
β‘ JavaScript Widget
-
-
-
-
-
π‘οΈ iframe Embed
-
-
-
-
-
+
diff --git a/src/js/calculator.js b/src/js/calculator.js
index 66edde8..1dee12c 100644
--- a/src/js/calculator.js
+++ b/src/js/calculator.js
@@ -60,10 +60,8 @@
container.style.transform = `scale(${clampedScale})`;
container.style.transformOrigin = 'top center';
- // Adjust container to account for scaling
+ // Recalculate height for parent without adding artificial margins
setTimeout(() => {
- const actualHeight = container.offsetHeight * clampedScale;
- container.style.marginBottom = `${(clampedScale - 1) * container.offsetHeight}px`;
this.sendHeightToParent();
}, 100);
}
@@ -899,14 +897,10 @@
// Modal event listeners
const shareBtn = document.getElementById('shareBtn');
- const embedBtn = document.getElementById('embedBtn');
const shareModalClose = document.getElementById('shareModalClose');
- const embedModalClose = document.getElementById('embedModalClose');
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
- if (embedBtn) embedBtn.addEventListener('click', () => this.showEmbedModal());
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
- if (embedModalClose) embedModalClose.addEventListener('click', () => this.hideEmbedModal());
// Share buttons
const shareFacebook = document.getElementById('shareFacebook');
@@ -921,16 +915,10 @@
if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
- // Copy buttons
- const copyWidget = document.getElementById('copyWidget');
- const copyIframe = document.getElementById('copyIframe');
-
- if (copyWidget) copyWidget.addEventListener('click', () => this.copyEmbedCode('widget'));
- if (copyIframe) copyIframe.addEventListener('click', () => this.copyEmbedCode('iframe'));
+ // Embed copy buttons removed (embedding disabled)
// Close modals on outside click
const shareModal = document.getElementById('shareModal');
- const embedModal = document.getElementById('embedModal');
if (shareModal) {
shareModal.addEventListener('click', (e) => {
@@ -938,11 +926,7 @@
});
}
- if (embedModal) {
- embedModal.addEventListener('click', (e) => {
- if (e.target === embedModal) this.hideEmbedModal();
- });
- }
+ // Embed modal removed
}
toggleUnits() {
@@ -1223,7 +1207,6 @@
calorieResults.style.display = 'block';
this.updateFoodCalculations();
- this.sendHeightToParent();
}
updateCupsButtonState() {
@@ -1687,9 +1670,8 @@
}
}
- foodAmountsSection.style.display = 'block';
-
- this.sendHeightToParent();
+ foodAmountsSection.style.display = 'block';
+ this.sendHeightToParent();
}
getFoodSourceEnergyPer100g(foodSource) {
@@ -1715,13 +1697,18 @@
}
}
+ // Iframe auto-resize for allowed embeddings
setupIframeResize() {
- // Send height to parent window for iframe auto-resize
- this.sendHeightToParent();
+ // Only when embedded in an iframe
+ if (window.top === window.self) return;
- // Monitor for content changes that might affect height
+ // Initial send once UI is ready
+ setTimeout(() => this.sendHeightToParent(), 50);
+
+ // Monitor for content/attribute changes
const observer = new MutationObserver(() => {
- setTimeout(() => this.sendHeightToParent(), 100);
+ clearTimeout(this._resizeTimer);
+ this._resizeTimer = setTimeout(() => this.sendHeightToParent(), 100);
});
observer.observe(document.body, {
@@ -1730,18 +1717,25 @@
attributes: true
});
- // Send height on window resize
+ // On viewport resize
window.addEventListener('resize', () => this.sendHeightToParent());
}
sendHeightToParent() {
- const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
- if (window.parent && window.parent !== window) {
- window.parent.postMessage({
- type: 'dogCalculatorResize',
- height: height
- }, '*');
+ 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
@@ -1750,50 +1744,25 @@
const shareUrl = document.getElementById('shareUrl');
if (modal && shareUrl) {
shareUrl.value = window.location.href;
- modal.style.display = 'block';
+ // 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();
}
- showEmbedModal() {
- const modal = document.getElementById('embedModal');
- const widgetCode = document.getElementById('widgetCode');
- const iframeCode = document.getElementById('iframeCode');
-
- if (modal && widgetCode && iframeCode) {
- // Build embed URL
- const baseUrl = window.location.protocol + '//embed.' + window.location.hostname;
-
- // Create widget code using createElement to avoid quote issues
- const scriptTag = document.createElement('script');
- scriptTag.src = baseUrl + '/dog-calorie-calculator/dog-food-calculator-widget.js';
- const divTag = document.createElement('div');
- divTag.id = 'dog-calorie-calculator';
-
- const widgetHtml = scriptTag.outerHTML + '\n' + divTag.outerHTML;
- widgetCode.textContent = widgetHtml;
-
- // Create iframe code using createElement
- const iframe = document.createElement('iframe');
- iframe.src = baseUrl + '/dog-calorie-calculator/iframe.html';
- iframe.width = '100%';
- iframe.height = '600';
- iframe.frameBorder = '0';
- iframe.title = 'Dog Calorie Calculator';
-
- iframeCode.textContent = iframe.outerHTML;
- modal.style.display = 'block';
- }
- }
+ // Embed modal removed (embedding disabled)
- hideEmbedModal() {
- const modal = document.getElementById('embedModal');
- if (modal) modal.style.display = 'none';
- }
+ // Embed modal removed (embedding disabled)
shareToFacebook() {
const url = encodeURIComponent(window.location.href);
@@ -1840,30 +1809,40 @@
}
}
- async copyEmbedCode(type) {
- const codeElement = document.getElementById(type === 'widget' ? 'widgetCode' : 'iframeCode');
- const copyBtn = document.getElementById(type === 'widget' ? 'copyWidget' : 'copyIframe');
-
- if (codeElement && copyBtn) {
- try {
- await navigator.clipboard.writeText(codeElement.textContent);
- const originalText = copyBtn.textContent;
- copyBtn.textContent = 'Copied!';
- copyBtn.classList.add('copied');
-
- setTimeout(() => {
- copyBtn.textContent = originalText;
- copyBtn.classList.remove('copied');
- }, 2000);
- } catch (err) {
- // Fallback for older browsers
- console.log('Copy fallback needed');
- }
- }
- }
+ // 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 = 'Embedding of this calculator is only allowed on caninenutritionandwellness.com.
';
+ return;
+ }
+ }
new DogCalorieCalculator();
});
diff --git a/sundog-dog-food-calculator.js b/sundog-dog-food-calculator.js
deleted file mode 100644
index 3effe59..0000000
--- a/sundog-dog-food-calculator.js
+++ /dev/null
@@ -1,4236 +0,0 @@
-/**
- * Dog Calorie Calculator Widget
- * Embeddable JavaScript widget for websites
- *
- * THIS CODE IS AUTO-GENERATED FROM src/ FILES - DO NOT EDIT MANUALLY
- * Edit files in src/ directory and run 'node build.js' to update
- *
- * Usage:
- *
- *
- *
- * Or with options:
- *
- *
- * By Canine Nutrition and Wellness
- * https://caninenutritionandwellness.com
- */
-
-(function() {
- 'use strict';
-
- // Inject widget styles
- const CSS_STYLES = `/* Sundog Dog Food Calorie Calculator Styles */
-
- /* CSS Variables for theming */
- :root {
- --bg-primary: #fdfcfe;
- --bg-secondary: #ffffff;
- --border-color: #e8e3ed;
- --text-primary: #6f3f6d;
- --text-secondary: #8f7a8e;
- --accent-color: #f19a5f;
- --text-label: #635870; /* For form labels, secondary UI text */
- --success-color: #7fa464; /* Green for success states */
- --bg-tertiary: #f8f5fa; /* Light background variant */
- --error-color: #e87159; /* Error states and messages */
- }
-
- body {
- margin: 0;
- padding: 0;
- background: transparent;
- overflow-x: hidden;
- font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
- line-height: 1.5;
- color: var(--text-primary);
- }
-
- .dog-calculator-container {
- max-width: 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);
- }
-
- .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;
- }
-
- .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
-
- .dog-calculator-footer {
- text-align: center;
- padding: 20px;
- background: var(--bg-primary);
- border: 1px solid var(--border-color);
- border-radius: 0 0 8px 8px;
- border-top: none;
- margin-top: -1px;
- box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
- }
-
- .dog-calculator-footer a {
- color: #9f5999;
- text-decoration: none;
- font-size: 0.9rem;
- font-weight: 500;
- transition: color 0.2s ease;
- }
-
- .dog-calculator-footer a:hover {
- color: #f19a5f;
- text-decoration: underline;
- }
-
- /* Mobile Responsive Design */
- @media (max-width: 576px) {
- .dog-calculator-container {
- padding: 16px;
- }
-
- .dog-calculator-section,
- .dog-calculator-collapsible-inner {
- padding: 20px;
- }
-
- .dog-calculator-section h2,
- .dog-calculator-collapsible-header h3 {
- font-size: 1.3rem;
- }
-
- .dog-calculator-section-header {
- flex-direction: column;
- align-items: stretch;
- gap: 12px;
- }
-
- .dog-calculator-section h2 {
- text-align: center;
- }
-
- .dog-calculator-unit-switch {
- justify-content: center;
- }
-
- .dog-calculator-action-buttons {
- flex-direction: column;
- padding: 16px;
- }
-
- .dog-calculator-btn {
- width: 100%;
- justify-content: center;
- }
-
- .dog-calculator-input-group {
- flex-direction: row;
- gap: 12px;
- align-items: flex-end;
- }
-
- .dog-calculator-input-group .dog-calculator-form-group {
- margin-bottom: 0;
- }
-
- /* First form group takes 55%, second takes 40% with some flex */
- .dog-calculator-input-group .dog-calculator-form-group:first-child {
- flex: 0 0 55%;
- }
-
- .dog-calculator-input-group .dog-calculator-form-group:last-child {
- flex: 1 1 40%;
- min-width: 100px;
- }
-
- /* Make sure number inputs don't get too wide */
- .dog-calculator-input-group input[type="number"] {
- max-width: 100%;
- }
-
- /* Ensure dropdowns don't overflow their containers */
- .dog-calculator-input-group select {
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
-
- .dog-calculator-result-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 8px;
- }
-
- .dog-calculator-result-value {
- align-self: stretch;
- text-align: center;
- }
-
- .dog-calculator-collapsible-header {
- padding: 16px 20px;
- }
- }
-
- /* 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;
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
-
- /* 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;
- }
-
- .dog-calculator-container.theme-system .dog-calculator-btn-embed:hover {
- border-color: var(--success-color);
- color: var(--success-color);
- }
-
- /* 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;
- position: fixed;
- z-index: 10000;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- animation: fadeIn 0.3s ease;
- }
-
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
-
- .dog-calculator-modal-content {
- position: relative;
- background-color: var(--bg-secondary);
- margin: 5% auto;
- padding: 30px;
- border: 1px solid var(--border-color);
- border-radius: 12px;
- width: 90%;
- max-width: 500px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- animation: slideIn 0.3s ease;
- }
-
- .dog-calculator-modal-embed {
- max-width: 700px;
- }
-
- @keyframes slideIn {
- from {
- opacity: 0;
- transform: translateY(-20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
-
- .dog-calculator-modal-close {
- position: absolute;
- right: 20px;
- top: 20px;
- font-size: 28px;
- font-weight: 300;
- color: var(--text-primary);
- cursor: pointer;
- transition: color 0.2s ease;
- }
-
- .dog-calculator-modal-close:hover {
- color: #f19a5f;
- }
-
- .dog-calculator-modal h3 {
- margin: 0 0 24px 0;
- color: var(--text-primary);
- font-size: 1.5rem;
- }
-
- /* Share Modal */
- .dog-calculator-share-buttons {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
- gap: 12px;
- margin-bottom: 20px;
- }
-
- .dog-calculator-share-btn {
- padding: 12px 16px;
- border: none;
- border-radius: 6px;
- font-size: 0.9rem;
- font-weight: 500;
- color: white;
- cursor: pointer;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- font-family: inherit;
- }
-
- .dog-calculator-share-facebook { background: #1877f2; }
- .dog-calculator-share-facebook:hover { background: #1664d1; transform: translateY(-1px); }
- .dog-calculator-share-twitter { background: #1da1f2; }
- .dog-calculator-share-twitter:hover { background: #1991da; transform: translateY(-1px); }
- .dog-calculator-share-linkedin { background: #0a66c2; }
- .dog-calculator-share-linkedin:hover { background: #084d95; transform: translateY(-1px); }
- .dog-calculator-share-email { background: #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 Modal */
- .dog-calculator-embed-options {
- display: flex;
- flex-direction: column;
- gap: 24px;
- }
-
- .dog-calculator-embed-option {
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 20px;
- background: #fcfafd;
- }
-
- .dog-calculator-embed-option h4 {
- margin: 0 0 8px 0;
- color: var(--text-primary);
- font-size: 1.1rem;
- }
-
- .dog-calculator-embed-option p {
- margin: 0 0 16px 0;
- color: var(--text-label);
- font-size: 0.9rem;
- }
-
- /* Default (light theme) code containers */
- .dog-calculator-code-container {
- position: relative;
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 6px;
- overflow: hidden;
- }
-
- .dog-calculator-code-container pre {
- margin: 0;
- padding: 16px 60px 16px 16px;
- overflow-x: auto;
- }
-
- .dog-calculator-code-container code {
- color: var(--text-primary);
- font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
- font-size: 0.85rem;
- line-height: 1.4;
- }
-
- .dog-calculator-copy-btn {
- position: absolute;
- top: 8px;
- right: 8px;
- padding: 6px 10px;
- background: #f19a5f;
- color: white;
- border: none;
- border-radius: 4px;
- font-size: 0.8rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s ease;
- font-family: inherit;
- z-index: 1;
- }
-
- .dog-calculator-copy-btn:hover { background: #e87741; }
- .dog-calculator-copy-btn.copied { background: var(--success-color); }
- .dog-calculator-copy-btn.copied:hover { background: var(--success-color); }
-
- /* Dark theme modal styles */
- .dog-calculator-container.theme-dark .dog-calculator-modal-content {
- background-color: var(--bg-primary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-modal h3 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-modal-close {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-modal-close:hover {
- color: #f19a5f;
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-share-url input {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option h4 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-embed-option p {
- color: var(--text-secondary)
- }
-
- /* Dark theme code containers - different from embed option background */
- .dog-calculator-container.theme-dark .dog-calculator-code-container {
- background: #1a1621;
- border-color: #2a2330;
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-code-container code {
- color: var(--text-primary);
- }
-
- /* System theme modal styles */
- @media (prefers-color-scheme: dark) {
- .dog-calculator-container.theme-system .dog-calculator-modal-content {
- background-color: var(--bg-primary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-modal h3 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-modal-close {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-modal-close:hover {
- color: #f19a5f;
- }
-
- .dog-calculator-container.theme-system .dog-calculator-share-url input {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option h4 {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-embed-option p {
- color: var(--text-secondary)
- }
-
- /* System theme code containers - different from embed option background */
- .dog-calculator-container.theme-system .dog-calculator-code-container {
- background: #1a1621;
- border-color: #2a2330;
- }
-
- .dog-calculator-container.theme-system .dog-calculator-code-container code {
- color: var(--text-primary);
- }
- }
-
- /* Multi-Food Source Styles */
- .dog-calculator-food-sources {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
-
- .dog-calculator-food-source-card {
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 20px;
- position: relative;
- transition: all 0.2s ease;
- }
-
- .dog-calculator-food-source-card:hover {
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
-
- .dog-calculator-food-source-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- }
-
- .dog-calculator-food-source-title {
- font-weight: 600;
- color: var(--text-primary);
- font-size: 1.1rem;
- margin: 0;
- }
-
- .dog-calculator-remove-food-btn {
- background: var(--error-color);
- color: white;
- border: none;
- border-radius: 50%;
- width: 28px;
- height: 28px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- font-size: 16px;
- font-weight: 600;
- transition: all 0.2s ease;
- line-height: 1;
- }
-
- .dog-calculator-remove-food-btn:hover {
- background: #d65a47;
- transform: scale(1.1);
- }
-
- .dog-calculator-percentage-group {
- margin-top: 16px;
- padding-top: 16px;
- border-top: 1px solid var(--border-color);
- }
-
- .dog-calculator-percentage-label {
- display: block;
- margin-bottom: 8px;
- font-weight: 500;
- color: var(--text-primary);
- font-size: 1rem;
- }
-
- .dog-calculator-percentage-input-group {
- display: flex;
- align-items: center;
- gap: 12px;
- }
-
- .dog-calculator-percentage-slider {
- flex: 1;
- height: 6px;
- border-radius: 3px;
- background: var(--border-color);
- outline: none;
- transition: all 0.2s ease;
- -webkit-appearance: none;
- appearance: none;
- }
-
- .dog-calculator-percentage-slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background: #f19a5f;
- cursor: pointer;
- border: 2px solid white;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
- transition: all 0.2s ease;
- }
-
- .dog-calculator-percentage-slider::-webkit-slider-thumb:hover {
- background: #e87741;
- transform: scale(1.1);
- }
-
- .dog-calculator-percentage-slider::-moz-range-thumb {
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background: #f19a5f;
- cursor: pointer;
- border: 2px solid white;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
- transition: all 0.2s ease;
- }
-
- .dog-calculator-percentage-input {
- width: 70px;
- padding: 8px 12px;
- border: 1px solid var(--border-color);
- border-radius: 6px;
- font-size: 0.9rem;
- text-align: center;
- background-color: var(--bg-secondary);
- color: var(--text-primary);
- }
-
- .dog-calculator-percentage-input:focus {
- outline: none;
- border-color: #f19a5f;
- box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1);
- }
-
- .dog-calculator-add-food-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- width: 100%;
- padding: 16px;
- border: 2px dashed var(--border-color);
- border-radius: 8px;
- background: transparent;
- color: var(--text-label);
- font-size: 1rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s ease;
- font-family: inherit;
- margin-top: 16px;
- }
-
- .dog-calculator-add-food-btn:hover {
- border-color: #f19a5f;
- color: #f19a5f;
- background: rgba(241, 154, 95, 0.05);
- }
-
- .dog-calculator-add-food-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- border-color: var(--border-color);
- color: var(--text-label);
- background: transparent;
- }
-
- .dog-calculator-add-food-btn:disabled:hover {
- border-color: var(--border-color);
- color: var(--text-label);
- background: transparent;
- }
-
- .dog-calculator-food-results {
- background: linear-gradient(135deg, rgba(241, 154, 95, 0.08) 0%, rgba(241, 154, 95, 0.04) 100%);
- border: 1px solid rgba(241, 154, 95, 0.2);
- border-radius: 6px;
- padding: 16px;
- margin-top: 20px;
- }
-
- .dog-calculator-food-result-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- font-size: 0.9rem;
- }
-
- .dog-calculator-food-result-item:last-child {
- margin-bottom: 0;
- }
-
- .dog-calculator-food-result-label {
- font-weight: 500;
- color: var(--text-primary);
- }
-
- .dog-calculator-food-result-value {
- font-weight: 600;
- color: var(--text-primary);
- padding: 2px 8px;
- background: rgba(241, 154, 95, 0.15);
- border-radius: 3px;
- font-size: 0.85rem;
- }
-
- /* Dark theme support for food sources */
- .dog-calculator-container.theme-dark .dog-calculator-food-source-card {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-food-source-title {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-percentage-label {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-percentage-slider {
- background: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-percentage-input {
- background: var(--border-color);
- border-color: #524a5f;
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-percentage-group {
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-add-food-btn {
- border-color: var(--border-color);
- color: var(--text-secondary)
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-add-food-btn:hover {
- border-color: #f19a5f;
- color: #f19a5f;
- background: rgba(241, 154, 95, 0.1);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-food-results {
- background: linear-gradient(135deg, rgba(241, 154, 95, 0.15) 0%, rgba(241, 154, 95, 0.08) 100%);
- border-color: rgba(241, 154, 95, 0.3);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-food-result-label {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-food-result-value {
- color: var(--text-primary);
- background: rgba(241, 154, 95, 0.2);
- }
-
- /* System theme support for food sources */
- @media (prefers-color-scheme: dark) {
- .dog-calculator-container.theme-system .dog-calculator-food-source-card {
- background: var(--bg-secondary);
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-food-source-title {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-percentage-label {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-percentage-slider {
- background: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-percentage-input {
- background: var(--border-color);
- border-color: #524a5f;
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-percentage-group {
- border-color: var(--border-color);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-add-food-btn {
- border-color: var(--border-color);
- color: var(--text-secondary)
- }
-
- .dog-calculator-container.theme-system .dog-calculator-add-food-btn:hover {
- border-color: #f19a5f;
- color: #f19a5f;
- background: rgba(241, 154, 95, 0.1);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-food-results {
- background: linear-gradient(135deg, rgba(241, 154, 95, 0.15) 0%, rgba(241, 154, 95, 0.08) 100%);
- border-color: rgba(241, 154, 95, 0.3);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-food-result-label {
- color: var(--text-primary);
- }
-
- .dog-calculator-container.theme-system .dog-calculator-food-result-value {
- color: var(--text-primary);
- background: rgba(241, 154, 95, 0.2);
- }
- }
-
- /* Mobile responsive design for food sources */
- @media (max-width: 576px) {
- .dog-calculator-food-source-card {
- padding: 16px;
- }
-
- .dog-calculator-food-source-header {
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
- }
-
- .dog-calculator-remove-food-btn {
- align-self: flex-end;
- margin-top: -8px;
- }
-
- .dog-calculator-percentage-input-group {
- flex-direction: column;
- gap: 8px;
- align-items: stretch;
- }
-
- .dog-calculator-percentage-input {
- width: 100%;
- }
-
- .dog-calculator-add-food-btn {
- padding: 12px;
- font-size: 0.9rem;
- }
-
- .dog-calculator-food-result-item {
- flex-direction: column;
- align-items: flex-start;
- gap: 4px;
- }
-
- .dog-calculator-food-result-value {
- align-self: stretch;
- text-align: center;
- }
- }
-
- /* Lock Icon Styles */
- .dog-calculator-lock-icon {
- display: inline-block;
- width: 16px;
- height: 16px;
- margin-left: 8px;
- cursor: pointer;
- font-size: 14px;
- line-height: 1;
- vertical-align: middle;
- transition: all 0.2s ease;
- user-select: none;
- opacity: 0.6;
- }
-
- .dog-calculator-lock-icon:hover {
- opacity: 1;
- transform: scale(1.1);
- }
-
- .dog-calculator-lock-icon.locked {
- color: #f19a5f;
- opacity: 1;
- font-weight: bold;
- }
-
- .dog-calculator-lock-icon.unlocked {
- color: var(--text-label);
- }
-
- .dog-calculator-lock-icon.disabled {
- opacity: 0.3;
- cursor: not-allowed;
- }
-
- .dog-calculator-lock-icon.disabled:hover {
- opacity: 0.3;
- transform: none;
- }
-
- /* Dark theme support for lock icons */
- .dog-calculator-container.theme-dark .dog-calculator-lock-icon.unlocked {
- color: var(--text-secondary)
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-lock-icon.locked {
- color: #f19a5f;
- }
-
- /* System theme support for lock icons */
- @media (prefers-color-scheme: dark) {
- .dog-calculator-container.theme-system .dog-calculator-lock-icon.unlocked {
- color: var(--text-secondary)
- }
-
- .dog-calculator-container.theme-system .dog-calculator-lock-icon.locked {
- color: #f19a5f;
- }
- }
-
- /* Disabled slider and input styles */
- .dog-calculator-percentage-slider:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- background: #f0f0f0;
- pointer-events: none;
- }
-
- .dog-calculator-percentage-slider:disabled::-webkit-slider-thumb {
- background: #ccc;
- cursor: not-allowed;
- }
-
- .dog-calculator-percentage-slider:disabled::-webkit-slider-thumb:hover {
- background: #ccc;
- transform: none;
- }
-
- .dog-calculator-percentage-slider:disabled::-moz-range-thumb {
- background: #ccc;
- cursor: not-allowed;
- }
-
- .dog-calculator-percentage-input:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- background-color: #f8f8f8;
- border-color: #ddd;
- pointer-events: none;
- }
-
- /* Dark theme disabled styles */
- .dog-calculator-container.theme-dark .dog-calculator-percentage-slider:disabled {
- background: #2a2530;
- }
-
- .dog-calculator-container.theme-dark .dog-calculator-percentage-input:disabled {
- background-color: #2a2530;
- border-color: #3a3442;
- color: #8a8a8a;
- }
-
- /* System theme disabled styles */
- @media (prefers-color-scheme: dark) {
- .dog-calculator-container.theme-system .dog-calculator-percentage-slider:disabled {
- background: #2a2530;
- }
-
- .dog-calculator-container.theme-system .dog-calculator-percentage-input:disabled {
- background-color: #2a2530;
- border-color: #3a3442;
- color: #8a8a8a;
- }
- }
-
- /* Food Amount Breakdown Styling */
- .dog-calculator-food-amounts-section {
- margin-top: 1.5rem;
- padding: 1rem;
- background: var(--bg-secondary);
- border-radius: 8px;
- border: 1px solid var(--border-color);
- }
-
- .dog-calculator-section-title {
- margin: 0 0 1rem 0;
- font-size: 1rem;
- font-weight: 600;
- color: var(--text-primary);
- }
-
- .dog-calculator-food-amounts-list {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
- margin-bottom: 1rem;
- }
-
- .dog-calculator-food-amount-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.75rem;
- background: var(--bg-primary);
- border-radius: 6px;
- border: 1px solid var(--border-color);
- }
-
- .dog-calculator-food-amount-label {
- font-weight: 500;
- color: var(--text-primary);
- display: flex;
- align-items: center;
- gap: 0.5rem;
- }
-
- .dog-calculator-food-percentage {
- background: var(--primary-color);
- color: white;
- padding: 0.2rem 0.5rem;
- border-radius: 12px;
- font-size: 0.8rem;
- font-weight: 500;
- }
-
- .dog-calculator-lock-indicator {
- font-size: 0.8rem;
- opacity: 0.7;
- }
-
- .dog-calculator-food-amount-value {
- font-weight: 600;
- color: var(--text-primary);
- font-size: 1rem;
- }
-
- /* Warning styles for missing energy content */
- .dog-calculator-warning {
- color: #e11d48;
- font-weight: 500;
- font-size: 1.2rem;
- text-align: left;
- cursor: help;
- }
-
- /* Inline unit selector in results */
- .dog-calculator-inline-unit {
- margin-left: 12px;
- min-width: 110px;
- padding: 4px 8px;
- background: var(--bg-primary);
- border: 1px solid rgba(241, 154, 95, 0.4);
- border-radius: 6px;
- color: var(--text-primary);
- font-size: 0.9rem;
- font-weight: 500;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- transition: all 0.2s ease;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236f3f6d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
- background-repeat: no-repeat;
- background-position: right 8px center;
- background-size: 16px;
- padding-right: 32px;
- }
-
- .dog-calculator-inline-unit:hover {
- border-color: #f19a5f;
- box-shadow: 0 2px 6px rgba(241, 154, 95, 0.2);
- }
-
- .dog-calculator-inline-unit:focus {
- outline: none;
- border-color: #f19a5f;
- box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1);
- }
-
- /* Inline days input in breakdown header */
- .dog-calculator-inline-days {
- width: 60px;
- padding: 2px 6px;
- border: 1px solid var(--border-color);
- border-radius: 4px;
- text-align: center;
- font-size: inherit;
- font-family: inherit;
- margin: 0 4px;
- }
-
- /* Unit selection buttons */
- .dog-calculator-unit-buttons {
- display: flex;
- justify-content: center;
- gap: 16px;
- margin: 24px auto;
- flex-wrap: wrap;
- width: fit-content;
- }
-
- .dog-calculator-unit-btn {
- padding: 8px 14px;
- border: 2px solid var(--border-color);
- border-radius: 6px;
- background: var(--bg-primary);
- color: var(--text-primary);
- font-size: 0.9rem;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s ease;
- min-width: 50px;
- text-align: center;
- }
-
- .dog-calculator-unit-btn:hover {
- border-color: #f19a5f;
- background: rgba(241, 154, 95, 0.1);
- }
-
- .dog-calculator-unit-btn.active {
- border-color: #f19a5f;
- background: #f19a5f;
- color: white;
- }
-
- .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;
- }
-
- }`;
-
- function injectStyles() {
- if (document.getElementById('dog-calculator-styles')) return;
-
- const style = document.createElement('style');
- style.id = 'dog-calculator-styles';
- style.textContent = CSS_STYLES;
- document.head.appendChild(style);
- }
-
- // JavaScript from src/calculator.js (transformed for widget use)
- /**
- * 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 DogCalorieCalculatorWidget {
- constructor(container, options = {}) {
- this.container = container;
- this.options = {
- theme: options.theme || this.getThemeFromURL() || 'system',
- scale: options.scale || this.getScaleFromURL() || 1.0,
- ...options
- };
- this.theme = this.options.theme;
- this.scale = this.options.scale;
- this.currentMER = 0;
- this.currentMERMin = 0; // For range calculations
- this.currentMERMax = 0; // For range calculations
- this.isImperial = false;
-
-
- this.foodSources = [];
- this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
- this.mealsPerDay = 2;
- this.showPerMeal = false;
- this.init();
- }
-
- init() {
- // Inject the calculator HTML into the container
- this.container.innerHTML = `
-
-
-
-
- Dog Type / Activity Level:
-
- Select dog type...
- Puppy (0-4 months)
- Puppy (4 months - adult)
- Adult - inactive/obese
- Adult (neutered/spayed) - average activity
- Adult (intact) - average activity
- Adult - weight loss
- Adult - weight gain
- Working dog - light work
- Working dog - moderate work
- Working dog - heavy work
- Senior dog
-
-
-
-
-
-
-
- Resting Energy Requirement (RER):
- - cal/day
-
-
- Maintenance Energy Requirement (MER):
- - cal/day
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- +
- Add another food source
-
-
-
-
-
-
-
-
-
-
- g
- kg
- oz
- lb
- cups
-
-
-
-
-
- Total Daily Amount:
- - g/day
-
-
-
-
-
- grams (g)
- kilograms (kg)
- ounces (oz)
- pounds (lb)
- cups
-
-
-
-
- Calculate amounts for
-
- day :
-
-
Please enter a valid number of days (minimum 1)
-
-
-
-
- Total Amount:
-
-
-
-
-
-
-
-
-
- Share
-
-
- Embed
-
-
-
-
-
-
-
-
×
-
Share Calculator
-
-
- Facebook
-
-
-
- LinkedIn
-
-
- Email
-
-
- Copy Link
-
-
-
-
-
-
-
-
-
-
-
-
×
-
β‘ Embed the Calculator
-
-
-
-
β‘ JavaScript Widget
-
-
-
-
-
π‘οΈ iframe Embed
-
-
-
-
-
-
`;
-
- // Apply widget-specific settings
- this.applyTheme();
- this.applyScale();
-
- // Continue with original init logic
- this.applyTheme();
- this.applyScale();
- this.initializeFoodSources();
- this.bindEvents();
- this.updateUnitLabels();
- this.setupIframeResize();
-
- // Show the calculator with fade-in
- const container = this.container.querySelector('#dogCalculator');
- container.classList.add('loaded');
- }
-
- getThemeFromURL() {
- const urlParams = new URLSearchParams(window.location.search);
- const theme = urlParams.get('theme');
- return ['light', 'dark', 'system'].includes(theme) ? theme : null;
- }
-
- getScaleFromURL() {
- const urlParams = new URLSearchParams(window.location.search);
- const scale = parseFloat(urlParams.get('scale'));
- return (!isNaN(scale) && scale >= CALCULATOR_CONFIG.minScale && scale <= CALCULATOR_CONFIG.maxScale) ? scale : null;
- }
-
- applyTheme() {
- const container = this.container.querySelector('#dogCalculator');
- container.classList.remove('theme-light', 'theme-dark', 'theme-system');
- container.classList.add('theme-' + this.theme);
- }
-
- applyScale() {
- const container = this.container.querySelector('#dogCalculator');
- if (!container) return;
-
- // Clamp scale between 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';
-
- // Adjust container to account for scaling
- setTimeout(() => {
- const actualHeight = container.offsetHeight * clampedScale;
- container.style.marginBottom = `${(clampedScale - 1) * container.offsetHeight}px`;
- this.sendHeightToParent();
- }, 100);
- }
- }
-
- // Food Source Management Methods
- initializeFoodSources() {
- this.addFoodSource();
- this.updateAddButton();
- }
-
- addFoodSource() {
- if (this.foodSources.length >= this.maxFoodSources) {
- return;
- }
-
- const id = this.generateFoodSourceId();
- const foodSource = {
- id: id,
- name: `Food Source ${this.foodSources.length + 1}`,
- energy: '',
- energyUnit: this.isImperial ? 'kcalcup' : 'kcal100g',
- percentage: this.foodSources.length === 0 ? 100 : 0,
- isLocked: false
- };
-
- this.foodSources.push(foodSource);
- this.redistributePercentages();
- this.renderFoodSource(foodSource);
- this.updateAddButton();
- this.updateRemoveButtons();
- this.refreshAllPercentageUI();
- }
-
- removeFoodSource(id) {
- if (this.foodSources.length <= 1) {
- return; // Cannot remove the last food source
- }
-
- const index = this.foodSources.findIndex(fs => fs.id === id);
- if (index === -1) return;
-
- this.foodSources.splice(index, 1);
-
- // Remove the DOM element
- const element = document.getElementById(`foodSource-${id}`);
- if (element) {
- element.remove();
- }
-
- // Redistribute percentages among remaining sources
- this.redistributePercentages();
- this.updateFoodSourceNames();
- this.updateAddButton();
- this.updateRemoveButtons();
- this.refreshAllPercentageUI();
- }
-
- generateFoodSourceId() {
- return 'fs_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
- }
-
- redistributePercentages() {
- const count = this.foodSources.length;
- if (count === 0) return;
-
- // Only redistribute among unlocked sources
- const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
- const lockedSources = this.foodSources.filter(fs => fs.isLocked);
-
- // Calculate total locked percentage
- const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- // Available percentage for unlocked sources
- const availablePercentage = 100 - totalLockedPercentage;
-
- if (unlockedSources.length > 0) {
- const equalPercentage = Math.floor(availablePercentage / unlockedSources.length);
- const remainder = availablePercentage - (equalPercentage * unlockedSources.length);
-
- unlockedSources.forEach((fs, index) => {
- fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
- });
- }
-
- // Update the UI sliders and inputs
- this.refreshAllPercentageUI();
- }
-
- // OBSOLETE METHODS - Replaced by new validation system
- // Keeping for reference but these are no longer used
-
- /*
- updatePercentageInputs() {
- this.foodSources.forEach(fs => {
- const slider = document.getElementById(`percentage-slider-${fs.id}`);
- const input = document.getElementById(`percentage-input-${fs.id}`);
- const display = document.getElementById(`percentage-display-${fs.id}`);
-
- if (slider) slider.value = fs.percentage;
- if (input) input.value = fs.percentage;
- if (display) display.textContent = `${fs.percentage}%`;
- });
-
- // Update constraints after values are set
- this.updatePercentageConstraints();
- }
-
- updatePercentageConstraints() {
- this.foodSources.forEach(fs => {
- const slider = document.getElementById(`percentage-slider-${fs.id}`);
- const input = document.getElementById(`percentage-input-${fs.id}`);
-
- if (!slider || !input) return;
-
- // Always keep full 0-100 scale for all sliders
- slider.max = 100;
- input.max = 100;
-
- if (fs.isLocked) {
- // Locked sources can't be changed
- slider.disabled = true;
- input.disabled = true;
- } else {
- // Calculate the maximum this source can have
- const lockedSources = this.foodSources.filter(other => other.id !== fs.id && other.isLocked);
- const totalLockedPercentage = lockedSources.reduce((sum, other) => sum + other.percentage, 0);
- const maxAllowed = 100 - totalLockedPercentage;
-
- // Re-enable
- slider.disabled = false;
- input.disabled = false;
-
- // Store max allowed for validation (we'll check this in event handlers)
- slider.dataset.maxAllowed = maxAllowed;
- input.dataset.maxAllowed = maxAllowed;
-
- // If current value exceeds max, adjust it
- if (fs.percentage > maxAllowed) {
- fs.percentage = maxAllowed;
- slider.value = maxAllowed;
- input.value = maxAllowed;
- document.getElementById(`percentage-display-${fs.id}`).textContent = `${maxAllowed}%`;
- }
- }
- });
- }
-
- adjustPercentages(changedId, newPercentage) {
- const changedIndex = this.foodSources.findIndex(fs => fs.id === changedId);
- if (changedIndex === -1) return;
-
- const oldPercentage = this.foodSources[changedIndex].percentage;
- const difference = newPercentage - oldPercentage;
-
- this.foodSources[changedIndex].percentage = newPercentage;
-
- // Only redistribute among unlocked sources (excluding the changed one)
- const otherUnlockedSources = this.foodSources.filter((fs, index) =>
- index !== changedIndex && !fs.isLocked
- );
-
- // If this is the only unlocked source, force it to fill remaining percentage
- if (otherUnlockedSources.length === 0) {
- const lockedSources = this.foodSources.filter((fs, index) =>
- index !== changedIndex && fs.isLocked
- );
- const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
- const requiredPercentage = 100 - totalLockedPercentage;
-
- // Force the changed source to the required percentage
- this.foodSources[changedIndex].percentage = requiredPercentage;
- this.updatePercentageInputs();
- this.updateFoodCalculations();
- return;
- }
-
- // Calculate total locked percentage (excluding the changed source)
- const lockedSources = this.foodSources.filter((fs, index) =>
- index !== changedIndex && fs.isLocked
- );
- const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- // Available percentage for unlocked sources
- const availablePercentage = 100 - newPercentage - totalLockedPercentage;
-
- const totalUnlockedPercentage = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- if (totalUnlockedPercentage === 0) {
- // If all other unlocked sources are 0, distribute equally
- const equalShare = Math.floor(availablePercentage / otherUnlockedSources.length);
- const remainder = availablePercentage - (equalShare * otherUnlockedSources.length);
-
- otherUnlockedSources.forEach((fs, index) => {
- fs.percentage = equalShare + (index < remainder ? 1 : 0);
- });
- } else {
- // Distribute proportionally among unlocked sources
- const scale = availablePercentage / totalUnlockedPercentage;
-
- let distributedTotal = 0;
- otherUnlockedSources.forEach((fs, index) => {
- if (index === otherUnlockedSources.length - 1) {
- // Last item gets the remainder to ensure exact 100%
- fs.percentage = availablePercentage - distributedTotal;
- } else {
- fs.percentage = Math.round(fs.percentage * scale);
- distributedTotal += fs.percentage;
- }
- });
- }
-
- this.updatePercentageInputs();
- this.updateFoodCalculations();
- }
- */
-
- // New validation system methods
- validatePercentageChange(sourceId, requestedValue) {
- // Find the source being changed
- const changedSource = this.foodSources.find(fs => fs.id === sourceId);
- if (!changedSource) {
- return { isValid: false, reason: 'Source not found' };
- }
-
- // If the source is locked, no change allowed
- if (changedSource.isLocked) {
- return { isValid: false, reason: 'Source is locked' };
- }
-
- // Ensure requested value is within bounds
- const clampedValue = Math.max(0, Math.min(100, requestedValue));
-
- // Calculate locked and other unlocked totals
- const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked);
- const otherUnlockedSources = this.foodSources.filter(fs => fs.id !== sourceId && !fs.isLocked);
-
- const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- // Check if the only unlocked source
- if (otherUnlockedSources.length === 0) {
- // This is the only unlocked source, must fill remaining percentage
- const requiredPercentage = 100 - totalLocked;
- return {
- isValid: true,
- actualValue: requiredPercentage,
- affectedSources: [{ id: sourceId, newPercentage: requiredPercentage }],
- reason: 'Only unlocked source, forced to fill remainder'
- };
- }
-
- // Calculate available percentage for redistribution
- const availableForOthers = 100 - clampedValue - totalLocked;
-
- // Check if redistribution is possible
- if (availableForOthers < 0) {
- // Cannot accommodate this value
- const maxAllowed = 100 - totalLocked;
- return {
- isValid: true,
- actualValue: maxAllowed,
- affectedSources: this.calculateRedistribution(sourceId, maxAllowed, otherUnlockedSources),
- reason: 'Value clamped to maximum allowed'
- };
- }
-
- // Calculate redistribution
- const affectedSources = this.calculateRedistribution(sourceId, clampedValue, otherUnlockedSources);
-
- return {
- isValid: true,
- actualValue: clampedValue,
- affectedSources: affectedSources,
- reason: 'Valid change'
- };
- }
-
- calculateRedistribution(sourceId, newValue, otherUnlockedSources) {
- const result = [{ id: sourceId, newPercentage: newValue }];
-
- if (otherUnlockedSources.length === 0) {
- return result;
- }
-
- // Calculate total locked percentage
- const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked);
- const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- // Available percentage for other unlocked sources
- const availableForOthers = 100 - newValue - totalLocked;
-
- // Current total of other unlocked sources
- const currentOtherTotal = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- if (currentOtherTotal === 0 || availableForOthers === 0) {
- // Distribute equally among other unlocked sources
- const equalShare = Math.floor(availableForOthers / otherUnlockedSources.length);
- const remainder = availableForOthers - (equalShare * otherUnlockedSources.length);
-
- otherUnlockedSources.forEach((fs, index) => {
- const newPercentage = equalShare + (index < remainder ? 1 : 0);
- result.push({ id: fs.id, newPercentage });
- });
- } else {
- // Distribute proportionally
- const scale = availableForOthers / currentOtherTotal;
- let distributedTotal = 0;
-
- otherUnlockedSources.forEach((fs, index) => {
- let newPercentage;
- if (index === otherUnlockedSources.length - 1) {
- // Last item gets remainder to ensure exact total
- newPercentage = availableForOthers - distributedTotal;
- } else {
- newPercentage = Math.round(fs.percentage * scale);
- distributedTotal += newPercentage;
- }
- result.push({ id: fs.id, newPercentage });
- });
- }
-
- return result;
- }
-
- applyValidatedChanges(validationResult) {
- if (!validationResult.isValid) {
- return false;
- }
-
- // Apply all percentage changes
- validationResult.affectedSources.forEach(change => {
- const source = this.foodSources.find(fs => fs.id === change.id);
- if (source) {
- source.percentage = change.newPercentage;
- }
- });
-
- return true;
- }
-
- refreshAllPercentageUI() {
- this.foodSources.forEach(fs => {
- // Update all UI elements from single source of truth
- const slider = document.getElementById(`percentage-slider-${fs.id}`);
- const input = document.getElementById(`percentage-input-${fs.id}`);
- const display = document.getElementById(`percentage-display-${fs.id}`);
-
- if (slider) slider.value = fs.percentage;
- if (input) input.value = fs.percentage;
- if (display) display.textContent = `${fs.percentage}%`;
-
- // Update constraints and disabled states
- this.updateSliderConstraints(fs);
- });
-
- // Update food calculations
- this.updateFoodCalculations();
- }
-
- updateSliderConstraints(foodSource) {
- const slider = document.getElementById(`percentage-slider-${foodSource.id}`);
- const input = document.getElementById(`percentage-input-${foodSource.id}`);
-
- if (!slider || !input) return;
-
- // Always keep 0-100 scale
- slider.max = 100;
- input.max = 100;
-
- if (foodSource.isLocked) {
- slider.disabled = true;
- input.disabled = true;
- } else {
- // Calculate maximum allowed and store for validation
- const maxAllowed = this.calculateMaxAllowed(foodSource.id);
- slider.disabled = (maxAllowed <= 0);
- input.disabled = (maxAllowed <= 0);
- slider.dataset.maxAllowed = maxAllowed;
- input.dataset.maxAllowed = maxAllowed;
- }
- }
-
- calculateMaxAllowed(sourceId) {
- const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked);
- const otherUnlockedSources = this.foodSources.filter(fs => fs.id !== sourceId && !fs.isLocked);
-
- const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
-
- // If this is the only unlocked source, it must take up the remainder
- if (otherUnlockedSources.length === 0) {
- return 100 - totalLocked;
- }
-
- // Otherwise, maximum is 100 minus locked percentages
- return Math.max(0, 100 - totalLocked);
- }
-
- updateFoodSourceNames() {
- this.foodSources.forEach((fs, index) => {
- // Only update if the name is still the default pattern
- if (fs.name.match(/^Food Source \d+$/)) {
- fs.name = `Food Source ${index + 1}`;
- const titleElement = document.getElementById(`food-title-${fs.id}`);
- if (titleElement) {
- titleElement.value = fs.name;
- }
- }
- });
- }
-
- updateAddButton() {
- const addBtn = this.container.querySelector('#addFoodBtn');
-
- if (addBtn) {
- const remaining = this.maxFoodSources - this.foodSources.length;
- const buttonText = addBtn.querySelector('span:last-child');
-
- if (remaining <= 0) {
- // Disable button and show max reached message
- addBtn.disabled = true;
- if (buttonText) {
- buttonText.textContent = `Maximum ${this.maxFoodSources} sources reached`;
- }
- } else {
- // Enable button with normal text
- addBtn.disabled = false;
- if (buttonText) {
- buttonText.textContent = 'Add another food source';
- }
- }
- }
- }
-
- updateRemoveButtons() {
- // Show/hide remove buttons based on whether we have more than one source
- const hasMultipleSources = this.foodSources.length > 1;
-
- this.foodSources.forEach(fs => {
- const removeBtn = document.getElementById(`remove-${fs.id}`);
- if (removeBtn) {
- removeBtn.style.display = hasMultipleSources ? 'block' : 'none';
- }
- });
- }
-
- renderFoodSource(foodSource) {
- const container = this.container.querySelector('#foodSources');
- if (!container) return;
-
- const cardHTML = `
-
-
-
-
-
Please enter a valid energy content
-
-
-
- `;
-
- container.insertAdjacentHTML('beforeend', cardHTML);
-
- // Bind events for the new food source
- this.bindFoodSourceEvents(foodSource.id);
- }
-
- bindFoodSourceEvents(id) {
- // Name input events
- const nameInput = document.getElementById(`food-title-${id}`);
-
- // Energy input events
- const energyInput = document.getElementById(`energy-${id}`);
- const energyUnitSelect = document.getElementById(`energy-unit-${id}`);
- const percentageSlider = document.getElementById(`percentage-slider-${id}`);
- const percentageInput = document.getElementById(`percentage-input-${id}`);
- const removeBtn = document.getElementById(`remove-${id}`);
- const lockBtn = document.getElementById(`lock-${id}`);
-
- if (nameInput) {
- nameInput.addEventListener('input', () => {
- const newName = nameInput.value.trim() || `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`;
- this.updateFoodSourceData(id, 'name', newName);
- this.updateFoodCalculations(); // This will refresh the food amount breakdown with new names
- });
-
- nameInput.addEventListener('blur', () => {
- // If field is empty, restore default name
- if (!nameInput.value.trim()) {
- const defaultName = `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`;
- nameInput.value = defaultName;
- this.updateFoodSourceData(id, 'name', defaultName);
- this.updateFoodCalculations();
- }
- });
- }
-
- if (energyInput) {
- energyInput.addEventListener('input', () => {
- this.updateFoodSourceData(id, 'energy', energyInput.value);
-
- // 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) {
- const unitSelect = this.container.querySelector('#unit');
- const cupsButton = this.container.querySelector('#cupsButton');
-
- // First check if cups button will be enabled after update
- const willEnableCups = this.foodSources.some(fs =>
- fs.energyUnit === 'kcalcup' && fs.energy && parseFloat(fs.energy) > 0
- );
-
- if (willEnableCups && unitSelect) {
- // Set cups BEFORE updating calculations
- unitSelect.value = 'cups';
- unitSelect.setAttribute('value', 'cups');
- this.setActiveUnitButton('cups');
-
- // Enable the cups button manually since we know it will be valid
- if (cupsButton) {
- cupsButton.disabled = false;
- cupsButton.title = 'Show amounts in cups';
- }
- }
-
- // Now update calculations with cups already selected
- this.updateFoodCalculations();
- } else {
- this.updateFoodCalculations();
- }
- });
- energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id));
- }
-
- if (energyUnitSelect) {
- energyUnitSelect.addEventListener('change', () => {
- this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
-
- // Auto-select the most appropriate unit based on energy unit
- const unitSelect = this.container.querySelector('#unit');
- const energyInput = document.getElementById(`energy-${id}`);
-
- if (unitSelect) {
- switch(energyUnitSelect.value) {
- case 'kcalcup':
- // Check if we have energy value to enable cups
- const foodSource = this.foodSources.find(fs => fs.id === id);
- if (foodSource && foodSource.energy && parseFloat(foodSource.energy) > 0) {
- // Set cups BEFORE updating calculations
- unitSelect.value = 'cups';
- unitSelect.setAttribute('value', 'cups');
- this.setActiveUnitButton('cups');
-
- // Enable the cups button manually
- const cupsButton = this.container.querySelector('#cupsButton');
- if (cupsButton) {
- cupsButton.disabled = false;
- cupsButton.title = 'Show amounts in cups';
- }
- }
- 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 (or ounces in imperial)
- unitSelect.value = this.isImperial ? 'oz' : 'g';
- this.setActiveUnitButton(unitSelect.value);
- 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 = this.container.querySelector('#weight');
- const dogTypeSelect = this.container.querySelector('#dogType');
- const daysInput = this.container.querySelector('#days');
- const unitSelect = this.container.querySelector('#unit');
- const unitToggle = this.container.querySelector('#unitToggle');
- const addFoodBtn = this.container.querySelector('#addFoodBtn');
-
- if (weightInput) {
- weightInput.addEventListener('input', () => this.updateCalorieCalculations());
- weightInput.addEventListener('blur', () => this.validateWeight());
- }
-
- if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
-
- if (daysInput) {
- daysInput.addEventListener('input', () => {
- this.updateDayLabel();
- this.updateFoodCalculations();
- });
- daysInput.addEventListener('blur', () => this.validateDays());
- }
-
- if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations());
-
- // Unit button event listeners
- const unitButtons = this.container.querySelectorAll('.dog-calculator-unit-btn');
- unitButtons.forEach(button => {
- button.addEventListener('click', (e) => {
- const selectedUnit = e.target.dataset.unit;
- this.setActiveUnitButton(selectedUnit);
- // Update hidden select to trigger existing logic
- if (unitSelect) {
- unitSelect.value = selectedUnit;
- this.updateFoodCalculations();
- }
- });
- });
-
- if (unitToggle) unitToggle.addEventListener('change', () => this.toggleUnits());
-
- if (addFoodBtn) addFoodBtn.addEventListener('click', () => this.addFoodSource());
-
- // Feeding configuration event listeners
- const showDaily = this.container.querySelector('#showDaily');
- const showPerMeal = this.container.querySelector('#showPerMeal');
- const mealsPerDayInput = this.container.querySelector('#mealsPerDay');
- const mealInputGroup = this.container.querySelector('#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 = this.container.querySelector('#shareBtn');
- const embedBtn = this.container.querySelector('#embedBtn');
- const shareModalClose = this.container.querySelector('#shareModalClose');
- const embedModalClose = this.container.querySelector('#embedModalClose');
-
- if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
- if (embedBtn) embedBtn.addEventListener('click', () => this.showEmbedModal());
- if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
- if (embedModalClose) embedModalClose.addEventListener('click', () => this.hideEmbedModal());
-
- // Share buttons
- const shareFacebook = this.container.querySelector('#shareFacebook');
- const shareTwitter = this.container.querySelector('#shareTwitter');
- const shareLinkedIn = this.container.querySelector('#shareLinkedIn');
- const shareEmail = this.container.querySelector('#shareEmail');
- const shareCopy = this.container.querySelector('#shareCopy');
-
- if (shareFacebook) shareFacebook.addEventListener('click', () => this.shareToFacebook());
- if (shareTwitter) shareTwitter.addEventListener('click', () => this.shareToTwitter());
- if (shareLinkedIn) shareLinkedIn.addEventListener('click', () => this.shareToLinkedIn());
- if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
- if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
-
- // Copy buttons
- const copyWidget = this.container.querySelector('#copyWidget');
- const copyIframe = this.container.querySelector('#copyIframe');
-
- if (copyWidget) copyWidget.addEventListener('click', () => this.copyEmbedCode('widget'));
- if (copyIframe) copyIframe.addEventListener('click', () => this.copyEmbedCode('iframe'));
-
- // Close modals on outside click
- const shareModal = this.container.querySelector('#shareModal');
- const embedModal = this.container.querySelector('#embedModal');
-
- if (shareModal) {
- shareModal.addEventListener('click', (e) => {
- if (e.target === shareModal) this.hideShareModal();
- });
- }
-
- if (embedModal) {
- embedModal.addEventListener('click', (e) => {
- if (e.target === embedModal) this.hideEmbedModal();
- });
- }
- }
-
- toggleUnits() {
- const toggle = this.container.querySelector('#unitToggle');
- this.isImperial = toggle.checked;
-
- this.updateUnitLabels();
- this.convertExistingValues();
- this.updateCalorieCalculations();
- }
-
- updateUnitLabels() {
- const metricLabel = this.container.querySelector('#metricLabel');
- const imperialLabel = this.container.querySelector('#imperialLabel');
- const weightLabel = this.container.querySelector('#weightLabel');
- const weightInput = this.container.querySelector('#weight');
- const unitSelect = this.container.querySelector('#unit');
-
- if (metricLabel && imperialLabel) {
- metricLabel.classList.toggle('active', !this.isImperial);
- imperialLabel.classList.toggle('active', this.isImperial);
- }
-
- if (this.isImperial) {
- if (weightLabel) weightLabel.textContent = "Dog's Weight (lbs):";
- if (weightInput) {
- weightInput.placeholder = "Enter weight in lbs";
- weightInput.min = "0.2";
- weightInput.step = "0.1";
- }
- if (unitSelect) {
- unitSelect.innerHTML = 'ounces (oz) ' +
- 'pounds (lb) ' +
- 'grams (g) ' +
- 'kilograms (kg) ';
- unitSelect.value = 'oz'; // Auto-select ounces for imperial
- this.setActiveUnitButton('oz'); // Sync unit buttons
- }
-
- // Update energy units for all food sources to kcal/cup for imperial
- this.foodSources.forEach(fs => {
- if (fs.energyUnit === 'kcal100g') {
- fs.energyUnit = 'kcalcup';
- const energyUnitSelect = document.getElementById(`energy-unit-${fs.id}`);
- if (energyUnitSelect) {
- energyUnitSelect.value = 'kcalcup';
- }
- }
- });
- } else {
- if (weightLabel) weightLabel.textContent = "Dog's Weight (kg):";
- if (weightInput) {
- weightInput.placeholder = "Enter weight in kg";
- weightInput.min = "0.1";
- weightInput.step = "0.1";
- }
- if (unitSelect) {
- unitSelect.innerHTML = 'grams (g) ' +
- 'kilograms (kg) ' +
- 'ounces (oz) ' +
- 'pounds (lb) ';
- unitSelect.value = 'g'; // Auto-select grams for metric
- this.setActiveUnitButton('g'); // Sync unit buttons
- }
-
- // Update energy units for all food sources to kcal/100g for metric
- this.foodSources.forEach(fs => {
- if (fs.energyUnit === 'kcalcup') {
- fs.energyUnit = 'kcal100g';
- const energyUnitSelect = document.getElementById(`energy-unit-${fs.id}`);
- if (energyUnitSelect) {
- energyUnitSelect.value = 'kcal100g';
- }
- }
- });
- }
- }
-
- convertExistingValues() {
- const weightInput = this.container.querySelector('#weight');
-
- if (weightInput && weightInput.value) {
- const currentWeight = parseFloat(weightInput.value);
- if (!isNaN(currentWeight)) {
- if (this.isImperial) {
- weightInput.value = this.formatNumber(currentWeight * 2.20462, 1);
- } else {
- weightInput.value = this.formatNumber(currentWeight / 2.20462, 1);
- }
- }
- }
- }
-
- getWeightInKg() {
- const weightInput = this.container.querySelector('#weight');
- if (!weightInput || !weightInput.value) return null;
-
- const weight = parseFloat(weightInput.value);
- if (isNaN(weight)) return null;
-
- return this.isImperial ? weight / 2.20462 : weight;
- }
-
-
- calculateRER(weightKg) {
- return 70 * Math.pow(weightKg, 0.75);
- }
-
- calculateMER(rer, factor) {
- return rer * factor;
- }
-
- // 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 = this.container.querySelector('#days')?.value;
- if (days && !this.validateInput(days, 1, true)) {
- this.showError('daysError', true);
- } else {
- this.showError('daysError', false);
- }
- }
-
- updateDayLabel() {
- const days = this.container.querySelector('#days')?.value;
- const dayLabel = this.container.querySelector('#dayLabel');
- const mealNote = this.container.querySelector('#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 = this.container.querySelectorAll('.dog-calculator-unit-btn');
- unitButtons.forEach(button => {
- button.classList.remove('active');
- if (button.dataset.unit === unit) {
- button.classList.add('active');
- }
- });
- }
-
- updateCalorieCalculations() {
- const dogTypeSelect = this.container.querySelector('#dogType');
- const calorieResults = this.container.querySelector('#calorieResults');
- const rerValue = this.container.querySelector('#rerValue');
- const merValue = this.container.querySelector('#merValue');
-
- if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) {
- return;
- }
-
- const weightKg = this.getWeightInKg();
- const dogTypeFactor = dogTypeSelect.value;
-
- this.showError('weightError', false);
-
- if (!weightKg || weightKg < 0.1) {
- const weightInput = this.container.querySelector('#weight');
- if (weightInput && weightInput.value) this.showError('weightError', true);
- calorieResults.style.display = 'none';
- return;
- }
-
- if (!dogTypeFactor) {
- calorieResults.style.display = 'none';
- return;
- }
-
- const factor = parseFloat(dogTypeFactor);
-
- const rer = this.calculateRER(weightKg);
- const mer = this.calculateMER(rer, factor);
-
- // Calculate range for MER
- const range = this.getLifeStageRange(factor);
- this.currentMERMin = this.calculateMER(rer, range.min);
- this.currentMERMax = this.calculateMER(rer, range.max);
- this.currentMER = mer; // Keep middle/selected value for compatibility
-
- rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
-
- // Show MER as range if applicable
- if (range.min !== range.max) {
- merValue.textContent = this.formatNumber(this.currentMERMin, 0) + '-' +
- this.formatNumber(this.currentMERMax, 0) + ' cal/day';
- } else {
- merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
- }
- calorieResults.style.display = 'block';
-
- this.updateFoodCalculations();
- this.sendHeightToParent();
- }
-
- updateCupsButtonState() {
- const cupsButton = this.container.querySelector('#cupsButton');
- if (!cupsButton) return;
-
- // Check if any food source has kcal/cup selected
- const hasKcalCup = this.foodSources.some(fs =>
- fs.energyUnit === 'kcalcup' && fs.energy && parseFloat(fs.energy) > 0
- );
-
- if (hasKcalCup) {
- cupsButton.disabled = false;
- cupsButton.title = 'Show amounts in cups';
- } else {
- cupsButton.disabled = true;
- cupsButton.title = 'Available when using kcal/cup measurement';
-
- // If cups was selected, switch back to grams
- const unitSelect = this.container.querySelector('#unit');
- if (unitSelect && unitSelect.value === 'cups') {
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- }
- }
- }
-
- updateFoodCalculations() {
- if (this.currentMER === 0) return;
-
- // Check if we have a range
- const hasRange = this.currentMERMin !== this.currentMERMax;
-
- const daysInput = this.container.querySelector('#days');
- const unitSelect = this.container.querySelector('#unit');
- const dailyFoodResults = this.container.querySelector('#dailyFoodResults');
- const dailyFoodValue = this.container.querySelector('#dailyFoodValue');
- const foodAmountsSection = this.container.querySelector('#foodAmountsSection');
- const foodAmountsList = this.container.querySelector('#foodAmountsList');
- const totalAmountDisplay = this.container.querySelector('#totalAmountDisplay');
- const foodBreakdownResults = this.container.querySelector('#foodBreakdownResults');
- const foodBreakdownList = this.container.querySelector('#foodBreakdownList');
- const feedingConfig = this.container.querySelector('#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 = this.container.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 = this.container.querySelector('#unitButtons');
- if (unitButtons) unitButtons.style.display = 'none';
- return;
- }
-
- const numDays = parseInt(days);
-
- // Calculate per-food breakdown
- const foodBreakdowns = [];
- let totalDailyGrams = 0;
- let hasValidFoods = false;
-
- this.foodSources.forEach(fs => {
- const energyPer100g = this.getFoodSourceEnergyPer100g(fs);
-
- if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
- const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100;
- // Calculate range values if applicable
- const dailyCaloriesMin = hasRange ? (this.currentMERMin * fs.percentage) / 100 : dailyCaloriesForThisFood;
- const dailyCaloriesMax = hasRange ? (this.currentMERMax * fs.percentage) / 100 : dailyCaloriesForThisFood;
-
- let dailyGramsForThisFood;
- let dailyGramsMin, dailyGramsMax;
- let dailyCupsForThisFood = null;
- let dailyCupsMin, dailyCupsMax;
-
- // For kcal/cup, calculate cups directly from calories
- if (fs.energyUnit === 'kcalcup' && fs.energy) {
- const caloriesPerCup = parseFloat(fs.energy);
- dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
- dailyCupsMin = dailyCaloriesMin / caloriesPerCup;
- dailyCupsMax = dailyCaloriesMax / caloriesPerCup;
- // We still need grams for total calculation, use approximation
- dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
- dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
- dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
- } else {
- // For other units, calculate grams normally
- dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
- dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
- dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
- }
-
- // Calculate per-meal amounts if needed
- const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
- const displayGramsMin = this.showPerMeal ? dailyGramsMin / this.mealsPerDay : dailyGramsMin;
- const displayGramsMax = this.showPerMeal ? dailyGramsMax / this.mealsPerDay : dailyGramsMax;
-
- const displayCups = dailyCupsForThisFood !== null ?
- (this.showPerMeal ? dailyCupsForThisFood / this.mealsPerDay : dailyCupsForThisFood) : null;
- const displayCupsMin = dailyCupsMin !== undefined ?
- (this.showPerMeal ? dailyCupsMin / this.mealsPerDay : dailyCupsMin) : null;
- const displayCupsMax = dailyCupsMax !== undefined ?
- (this.showPerMeal ? dailyCupsMax / this.mealsPerDay : dailyCupsMax) : null;
-
- const displayCalories = this.showPerMeal ? dailyCaloriesForThisFood / this.mealsPerDay : dailyCaloriesForThisFood;
- const displayCaloriesMin = this.showPerMeal ? dailyCaloriesMin / this.mealsPerDay : dailyCaloriesMin;
- const displayCaloriesMax = this.showPerMeal ? dailyCaloriesMax / this.mealsPerDay : dailyCaloriesMax;
-
- foodBreakdowns.push({
- name: fs.name,
- percentage: fs.percentage,
- dailyGrams: dailyGramsForThisFood,
- dailyGramsMin: dailyGramsMin,
- dailyGramsMax: dailyGramsMax,
- displayGrams: displayGrams,
- displayGramsMin: displayGramsMin,
- displayGramsMax: displayGramsMax,
- dailyCups: dailyCupsForThisFood,
- dailyCupsMin: dailyCupsMin,
- dailyCupsMax: dailyCupsMax,
- displayCups: displayCups,
- displayCupsMin: displayCupsMin,
- displayCupsMax: displayCupsMax,
- calories: dailyCaloriesForThisFood,
- displayCalories: displayCalories,
- displayCaloriesMin: displayCaloriesMin,
- displayCaloriesMax: displayCaloriesMax,
- isLocked: fs.isLocked,
- hasEnergyContent: true,
- hasRange: hasRange,
- foodSource: fs // Store reference for cups conversion
- });
-
- totalDailyGrams += dailyGramsForThisFood;
- hasValidFoods = true;
- } else if (fs.percentage > 0) {
- // Include food sources without energy content but show them as needing energy content
- foodBreakdowns.push({
- name: fs.name,
- percentage: fs.percentage,
- dailyGrams: 0,
- displayGrams: 0,
- dailyCups: null,
- displayCups: null,
- calories: 0,
- displayCalories: 0,
- isLocked: fs.isLocked,
- hasEnergyContent: false,
- foodSource: fs // Store reference for cups conversion
- });
- }
- });
-
- 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 = this.container.querySelector('#unitButtons');
- if (unitButtons) unitButtons.style.display = 'none';
-
- // If we have any food sources without energy content, still show the breakdown section
- if (foodBreakdowns.length > 0) {
- // Show food amounts section with warnings for missing energy content
- const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb';
-
- const foodAmountsHTML = foodBreakdowns.map(breakdown => {
- const lockIndicator = breakdown.isLocked ? 'π ' : '';
-
- return `
-
-
- ${breakdown.name}
- ${breakdown.percentage}%
- ${lockIndicator}
-
-
- β οΈ
-
-
- `;
- }).join('');
-
- if (foodAmountsList) {
- foodAmountsList.innerHTML = foodAmountsHTML;
- }
-
- if (totalAmountDisplay) {
- totalAmountDisplay.textContent = "Enter energy content for all foods";
- }
-
- foodAmountsSection.style.display = 'block';
- this.sendHeightToParent();
- } else {
- foodAmountsSection.style.display = 'none';
- }
- return;
- }
-
- // Update daily food results (total) - will be updated with proper units later
- dailyFoodResults.style.display = 'block';
-
- // Show 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 = this.container.querySelector('#showDaily');
- if (showDaily && !showDaily.checked && !this.container.querySelector('#showPerMeal').checked) {
- showDaily.checked = true;
- }
- }
-
- // Show unit buttons when daily results are shown
- const unitButtons = this.container.querySelector('#unitButtons');
- if (unitButtons) unitButtons.style.display = 'flex';
-
- // Update per-food breakdown
- if (foodBreakdownList && foodBreakdowns.length > 1) {
- const breakdownHTML = foodBreakdowns.map(breakdown => {
- 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 = `N/A `;
- }
- } 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 = `β οΈ `;
- }
-
- return `
-
- ${breakdown.name} (${breakdown.percentage}%${breakdown.isLocked ? ' - locked' : ''}):
- ${valueContent}
-
- `;
- }).join('');
-
- foodBreakdownList.innerHTML = breakdownHTML;
- if (foodBreakdownResults) foodBreakdownResults.style.display = 'block';
- } else {
- if (foodBreakdownResults) foodBreakdownResults.style.display = 'none';
- }
-
- // Generate individual food amount breakdown
-
- // Update daily food value with correct units
- const 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 ? 'π ' : '';
-
- if (!breakdown.hasEnergyContent) {
- // Show warning for food sources without energy content
- return `
-
-
- ${breakdown.name}
- ${breakdown.percentage}%
- ${lockIndicator}
-
-
- β οΈ
-
-
- `;
- } 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 = `N/A `;
- }
- } 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 `
-
-
- ${breakdown.name}
- ${breakdown.percentage}%
- ${lockIndicator}
-
-
- ${amountDisplay}
-
-
- `;
- }
- }).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;
- }
- }
-
- setupIframeResize() {
- // Send height to parent window for iframe auto-resize
- this.sendHeightToParent();
-
- // Monitor for content changes that might affect height
- const observer = new MutationObserver(() => {
- setTimeout(() => this.sendHeightToParent(), 100);
- });
-
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: true
- });
-
- // Send height on window resize
- window.addEventListener('resize', () => this.sendHeightToParent());
- }
-
- sendHeightToParent() {
- const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
- if (window.parent && window.parent !== window) {
- window.parent.postMessage({
- type: 'dogCalculatorResize',
- height: height
- }, '*');
- }
- }
-
- // Modal functionality
- showShareModal() {
- const modal = this.container.querySelector('#shareModal');
- const shareUrl = this.container.querySelector('#shareUrl');
- if (modal && shareUrl) {
- shareUrl.value = window.location.href;
- modal.style.display = 'block';
- }
- }
-
- hideShareModal() {
- const modal = this.container.querySelector('#shareModal');
- if (modal) modal.style.display = 'none';
- }
-
- showEmbedModal() {
- const modal = this.container.querySelector('#embedModal');
- const widgetCode = this.container.querySelector('#widgetCode');
- const iframeCode = this.container.querySelector('#iframeCode');
-
- if (modal && widgetCode && iframeCode) {
- // Build embed URL
- const baseUrl = window.location.protocol + '//embed.' + window.location.hostname;
-
- // Create widget code using createElement to avoid quote issues
- const scriptTag = document.createElement('script');
- scriptTag.src = baseUrl + '/dog-calorie-calculator/dog-food-calculator-widget.js';
- const divTag = document.createElement('div');
- divTag.id = 'dog-calorie-calculator';
-
- const widgetHtml = scriptTag.outerHTML + '\n' + divTag.outerHTML;
- widgetCode.textContent = widgetHtml;
-
- // Create iframe code using createElement
- const iframe = document.createElement('iframe');
- iframe.src = baseUrl + '/dog-calorie-calculator/iframe.html';
- iframe.width = '100%';
- iframe.height = '600';
- iframe.frameBorder = '0';
- iframe.title = 'Dog Calorie Calculator';
-
- iframeCode.textContent = iframe.outerHTML;
- modal.style.display = 'block';
- }
- }
-
- hideEmbedModal() {
- const modal = this.container.querySelector('#embedModal');
- if (modal) modal.style.display = 'none';
- }
-
- shareToFacebook() {
- const url = encodeURIComponent(window.location.href);
- window.open('https://www.facebook.com/sharer/sharer.php?u=' + url, '_blank', 'width=600,height=400');
- }
-
- shareToTwitter() {
- const url = encodeURIComponent(window.location.href);
- const text = encodeURIComponent('Check out this useful dog calorie calculator!');
- window.open('https://twitter.com/intent/tweet?url=' + url + '&text=' + text, '_blank', 'width=600,height=400');
- }
-
- shareToLinkedIn() {
- const url = encodeURIComponent(window.location.href);
- window.open('https://www.linkedin.com/sharing/share-offsite/?url=' + url, '_blank', 'width=600,height=400');
- }
-
- shareViaEmail() {
- const subject = encodeURIComponent('Dog Calorie Calculator');
- const body = encodeURIComponent('Check out this useful dog calorie calculator: ' + window.location.href);
- window.location.href = 'mailto:?subject=' + subject + '&body=' + body;
- }
-
- async copyShareLink() {
- const shareUrl = this.container.querySelector('#shareUrl');
- const copyBtn = this.container.querySelector('#shareCopy');
-
- if (shareUrl && copyBtn) {
- try {
- await navigator.clipboard.writeText(shareUrl.value);
- const originalText = copyBtn.textContent;
- copyBtn.textContent = 'Copied!';
- copyBtn.classList.add('copied');
-
- setTimeout(() => {
- copyBtn.textContent = originalText;
- copyBtn.classList.remove('copied');
- }, 2000);
- } catch (err) {
- // Fallback for older browsers
- shareUrl.select();
- document.execCommand('copy');
- }
- }
- }
-
- async copyEmbedCode(type) {
- const codeElement = document.getElementById(type === 'widget' ? 'widgetCode' : 'iframeCode');
- const copyBtn = document.getElementById(type === 'widget' ? 'copyWidget' : 'copyIframe');
-
- if (codeElement && copyBtn) {
- try {
- await navigator.clipboard.writeText(codeElement.textContent);
- const originalText = copyBtn.textContent;
- copyBtn.textContent = 'Copied!';
- copyBtn.classList.add('copied');
-
- setTimeout(() => {
- copyBtn.textContent = originalText;
- copyBtn.classList.remove('copied');
- }, 2000);
- } catch (err) {
- // Fallback for older browsers
- console.log('Copy fallback needed');
- }
- }
- }
- }
-
- // Initialize calculator when DOM is ready
-
-
- // Auto-initialize widgets on page load
- function initializeWidget() {
- injectStyles();
-
- const containers = document.querySelectorAll('#dog-calorie-calculator, .dog-calorie-calculator');
- containers.forEach(container => {
- if (container.dataset.initialized) return;
-
- const options = {
- theme: container.dataset.theme || 'system',
- scale: parseFloat(container.dataset.scale) || 1.0
- };
-
- new DogCalorieCalculatorWidget(container, options);
- container.dataset.initialized = 'true';
- });
- }
-
- // Initialize when DOM is ready
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initializeWidget);
- } else {
- initializeWidget();
- }
-
- // Export for manual initialization
- window.DogCalorieCalculatorWidget = DogCalorieCalculatorWidget;
-
-})();
\ No newline at end of file
diff --git a/test-widget.html b/test-widget.html
deleted file mode 100644
index 404397e..0000000
--- a/test-widget.html
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
- Widget Test
-
-
-
- Dog Calculator Widget Test
-
-
-
Test 1: Basic Widget
-
-
-
-
-
Test 2: Dark Theme Widget
-
-
-
-
-
-
\ No newline at end of file