Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90657f9aa4 | |||
| 99b516d087 | |||
| c4770d5ee6 |
@@ -1,6 +1,6 @@
|
|||||||
# 🐕 Sundog Dog Food Calorie Calculator
|
# 🐕 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)**
|
**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
|
- **Lock Indicators**: Visual indicators showing which percentages are locked
|
||||||
|
|
||||||
### User Experience
|
### 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
|
- **Theme Support**: Light, dark, and system themes
|
||||||
- **Responsive Design**: Mobile-first, optimized layouts for all devices
|
- **Responsive Design**: Mobile-first, optimized layouts for all devices
|
||||||
- **Two Embedding Options**: JavaScript widget and iframe
|
|
||||||
- **Accessibility**: Full keyboard navigation and screen reader support
|
- **Accessibility**: Full keyboard navigation and screen reader support
|
||||||
|
|
||||||
### Brand & Integration
|
### Brand & Integration
|
||||||
@@ -39,36 +38,16 @@ A professional veterinary nutrition tool for calculating dogs' daily calorie req
|
|||||||
- **Professional Design**: Clean, veterinary-grade interface
|
- **Professional Design**: Clean, veterinary-grade interface
|
||||||
- **Brand Protection**: Complete iframe isolation option
|
- **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
|
```html
|
||||||
<!-- Basic usage -->
|
|
||||||
<script src="https://yourdomain.com/sundog-dog-food-calculator.js"></script>
|
|
||||||
<div id="dog-calorie-calculator"></div>
|
|
||||||
|
|
||||||
<!-- With theme and scale options -->
|
|
||||||
<div id="dog-calorie-calculator" data-theme="dark" data-scale="0.8"></div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: iframe Embed
|
|
||||||
```html
|
|
||||||
<!-- Basic iframe -->
|
|
||||||
<iframe
|
<iframe
|
||||||
src="https://yourdomain.com/iframe.html"
|
src="https://embed.caninenutritionandwellness.com/dog-calorie-calculator/iframe.html?theme=light&scale=0.8"
|
||||||
width="100%"
|
width="100%" height="640" frameborder="0" title="Dog Calorie Calculator">
|
||||||
height="600"
|
|
||||||
frameborder="0"
|
|
||||||
title="Dog Calorie Calculator">
|
|
||||||
</iframe>
|
|
||||||
|
|
||||||
<!-- With theme and scale parameters -->
|
|
||||||
<iframe
|
|
||||||
src="https://yourdomain.com/iframe.html?theme=dark&scale=1.2"
|
|
||||||
width="100%"
|
|
||||||
height="600"
|
|
||||||
frameborder="0"
|
|
||||||
title="Dog Calorie Calculator">
|
|
||||||
</iframe>
|
</iframe>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -134,7 +113,7 @@ Choose from three themes:
|
|||||||
- `system` - Follows user's OS preference (default)
|
- `system` - Follows user's OS preference (default)
|
||||||
|
|
||||||
### Scale Options
|
### Scale Options
|
||||||
Resize the widget from 50% to 200%:
|
Resize the interface from 50% to 200%:
|
||||||
- Range: `0.5` to `2.0`
|
- Range: `0.5` to `2.0`
|
||||||
- Default: `1.0` (100% size)
|
- Default: `1.0` (100% size)
|
||||||
- Examples: `0.8` (80%), `1.2` (120%), `1.5` (150%)
|
- 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/cup` - US/Canada dry food
|
||||||
- `kcal/can` - US/Canada wet food
|
- `kcal/can` - US/Canada wet food
|
||||||
|
|
||||||
### Advanced JavaScript Usage
|
<!-- Embedding and external widget initialization are no longer supported. -->
|
||||||
```javascript
|
|
||||||
new DogCalorieCalculatorWidget(container, {
|
|
||||||
theme: 'dark', // 'light', 'dark', 'system'
|
|
||||||
scale: 1.2 // 0.5 to 2.0
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ Development
|
## 🛠️ Development
|
||||||
|
|
||||||
### Build System
|
### 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
|
- **Sources**: `src/` (HTML, CSS, JS modules)
|
||||||
- **Build Script**: `build.js` - Generates the widget from iframe.html
|
- **Build Script**: `build.js` - Generates `iframe.html` from `src/`
|
||||||
- **Generated Output**: `sundog-dog-food-calculator.js` - Embeddable widget
|
- **Output**: `iframe.html` - Standalone calculator page
|
||||||
|
|
||||||
### Development Workflow
|
### 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`
|
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?
|
### Why This Approach?
|
||||||
- ✅ **Single Source of Truth** - No need to maintain two separate files
|
- ✅ **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
|
- ✅ **No Sync Issues** - Build script ensures consistency
|
||||||
|
|
||||||
### Build Script Features
|
### Build Script Features
|
||||||
- Extracts CSS, HTML, and JavaScript from iframe.html
|
- Compiles CSS, HTML, and JavaScript from `src/`
|
||||||
- Transforms CSS classes for widget namespacing (`dog-calculator-` → `dog-calc-`)
|
|
||||||
- Preserves all functionality including unit switching and calculations
|
- 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
|
## 📁 Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
├── iframe.html # 🎯 MASTER SOURCE - Edit this file
|
├── src/ # ✏️ Source (HTML/CSS/JS)
|
||||||
├── build.js # 🔧 Build script - Run after changes
|
├── build.js # 🔧 Build script - Run after changes
|
||||||
├── sundog-dog-food-calculator.js # 📦 Generated widget (don't edit)
|
├── iframe.html # 📦 Generated standalone page
|
||||||
├── test-widget.html # 🧪 Test file for widget
|
|
||||||
└── README.md # 📖 This file
|
└── 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 |
|
| Working dog - heavy work | 5.0 | Intensive work |
|
||||||
| Senior dog | 1.1 | Reduced activity |
|
| 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
|
### Server Headers (required for robust enforcement)
|
||||||
- **Auto-initialization**: Detects `#dog-calorie-calculator` containers
|
Configure your server or CDN to send this header on `iframe.html`:
|
||||||
- **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
|
|
||||||
|
|
||||||
### iframe Features
|
```
|
||||||
- **Auto-resize**: Communicates height changes to parent
|
Content-Security-Policy: frame-ancestors https://caninenutritionandwellness.com https://www.caninenutritionandwellness.com;
|
||||||
- **Style Isolation**: Complete protection from host site CSS
|
```
|
||||||
- **Loading Animation**: Smooth fade-in when ready
|
|
||||||
- **Cross-origin Ready**: PostMessage communication for integration
|
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
|
## 🚀 Deployment Guide
|
||||||
|
|
||||||
### 1. Host the Files
|
### Deployment
|
||||||
Upload these files to your web server:
|
Host `iframe.html` (e.g., on `embed.caninenutritionandwellness.com`) and embed via iframe on your approved domains.
|
||||||
- `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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔒 Brand Protection
|
## 🔒 Brand Protection
|
||||||
|
Embedding is disabled to protect branding and ensure consistent presentation.
|
||||||
### 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
|
|
||||||
|
|
||||||
## 📱 Mobile Optimization
|
## 📱 Mobile Optimization
|
||||||
|
|
||||||
@@ -353,7 +285,6 @@ This calculator is provided for educational and professional use. The formulas a
|
|||||||
## 🔗 Links
|
## 🔗 Links
|
||||||
|
|
||||||
- **Website**: [caninenutritionandwellness.com](https://caninenutritionandwellness.com)
|
- **Website**: [caninenutritionandwellness.com](https://caninenutritionandwellness.com)
|
||||||
- **Widget Demo**: Open `test-widget.html` in your browser
|
|
||||||
- **Standalone**: Open `iframe.html` in your browser
|
- **Standalone**: Open `iframe.html` in your browser
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
/**
|
/**
|
||||||
* Dog Calculator Build System - ORGANIZED VERSION
|
* Dog Calculator Build System - ORGANIZED VERSION
|
||||||
*
|
*
|
||||||
* This build script generates iframe.html and sundog-dog-food-calculator.js
|
* This build script generates iframe.html from organized source files
|
||||||
* from organized source files in the src/ directory.
|
* in the src/ directory.
|
||||||
*
|
*
|
||||||
* Source structure:
|
* Source structure:
|
||||||
* - src/index.html - HTML template
|
* - src/index.html - HTML template
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
*
|
*
|
||||||
* Output files:
|
* Output files:
|
||||||
* - iframe.html - Standalone calculator page
|
* - iframe.html - Standalone calculator page
|
||||||
* - sundog-dog-food-calculator.js - Embeddable widget
|
|
||||||
*
|
*
|
||||||
* Usage: node build.js
|
* Usage: node build.js
|
||||||
*/
|
*/
|
||||||
@@ -85,7 +84,7 @@ function backupFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const filesToBackup = ['iframe.html', 'sundog-dog-food-calculator.js'];
|
const filesToBackup = ['iframe.html'];
|
||||||
|
|
||||||
filesToBackup.forEach(file => {
|
filesToBackup.forEach(file => {
|
||||||
if (fs.existsSync(file)) {
|
if (fs.existsSync(file)) {
|
||||||
@@ -141,8 +140,9 @@ function createWidgetJS(css, html, js) {
|
|||||||
.replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(')
|
.replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(')
|
||||||
// Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper
|
// Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper
|
||||||
.replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '')
|
.replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '')
|
||||||
// Remove duplicate theme/scale assignments that override options
|
// Remove theme/scale assignments that would override widget options
|
||||||
.replace(/this\.theme = this\.getThemeFromURL\(\) \|\| 'system';\s*\n\s*this\.scale = this\.getScaleFromURL\(\) \|\| 1\.0;/g, '')
|
.replace(/this\.theme = this\.getThemeFromURL\(\) \|\| CALCULATOR_CONFIG\.defaultTheme;/g, '')
|
||||||
|
.replace(/this\.scale = this\.getScaleFromURL\(\) \|\| CALCULATOR_CONFIG\.defaultScale;/g, '')
|
||||||
// Add widget initialization methods
|
// Add widget initialization methods
|
||||||
.replace(/constructor\(\) \{/, `constructor(container, options = {}) {
|
.replace(/constructor\(\) \{/, `constructor(container, options = {}) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@@ -186,14 +186,14 @@ function createWidgetJS(css, html, js) {
|
|||||||
// Remove existing theme classes
|
// Remove existing theme classes
|
||||||
calculatorContainer.classList.remove('theme-light', 'theme-dark', 'theme-system');
|
calculatorContainer.classList.remove('theme-light', 'theme-dark', 'theme-system');
|
||||||
// Add the selected theme class
|
// Add the selected theme class
|
||||||
if (['light', 'dark', 'system'].includes(this.options.theme)) {
|
if (['light', 'dark', 'system'].includes(this.theme)) {
|
||||||
calculatorContainer.classList.add('theme-' + this.options.theme);
|
calculatorContainer.classList.add('theme-' + this.theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyScale() {
|
applyScale() {
|
||||||
const scale = Math.max(0.5, Math.min(2.0, this.options.scale));
|
const scale = Math.max(0.5, Math.min(2.0, this.scale));
|
||||||
if (scale !== 1.0) {
|
if (scale !== 1.0) {
|
||||||
this.container.style.transform = \`scale(\${scale})\`;
|
this.container.style.transform = \`scale(\${scale})\`;
|
||||||
this.container.style.transformOrigin = 'top left';
|
this.container.style.transformOrigin = 'top left';
|
||||||
@@ -298,10 +298,7 @@ function build() {
|
|||||||
fs.writeFileSync('iframe.html', iframeContent);
|
fs.writeFileSync('iframe.html', iframeContent);
|
||||||
console.log(' ✅ Generated iframe.html');
|
console.log(' ✅ Generated iframe.html');
|
||||||
|
|
||||||
// Generate widget
|
// Embeddable widget generation removed (embedding no longer supported)
|
||||||
const widgetCode = createWidgetJS(css, html, js);
|
|
||||||
fs.writeFileSync('sundog-dog-food-calculator.js', widgetCode);
|
|
||||||
console.log(' ✅ Generated sundog-dog-food-calculator.js');
|
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('🎉 Build completed successfully!');
|
console.log('🎉 Build completed successfully!');
|
||||||
@@ -316,12 +313,11 @@ function build() {
|
|||||||
console.log('');
|
console.log('');
|
||||||
console.log(' Generated files:');
|
console.log(' Generated files:');
|
||||||
console.log(' • iframe.html - Standalone calculator');
|
console.log(' • iframe.html - Standalone calculator');
|
||||||
console.log(' • sundog-dog-food-calculator.js - Embeddable widget');
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('🔄 Your workflow:');
|
console.log('🔄 Your workflow:');
|
||||||
console.log(' 1. Edit organized files in src/');
|
console.log(' 1. Edit organized files in src/');
|
||||||
console.log(' 2. Run: node build.js');
|
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('');
|
||||||
console.log('💡 Clean, organized structure - easy to maintain!');
|
console.log('💡 Clean, organized structure - easy to maintain!');
|
||||||
|
|
||||||
|
|||||||
+228
-270
@@ -25,14 +25,14 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
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;
|
font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container {
|
.dog-calculator-container {
|
||||||
max-width: 600px;
|
max-width: 640px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -213,6 +213,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
gap: 10px; /* Add gap between label and value */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-result-item:last-child {
|
.dog-calculator-result-item:last-child {
|
||||||
@@ -232,6 +233,7 @@
|
|||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
background: rgba(241, 154, 95, 0.15);
|
background: rgba(241, 154, 95, 0.15);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-collapsible {
|
.dog-calculator-collapsible {
|
||||||
@@ -329,10 +331,7 @@
|
|||||||
color: #9f5999;
|
color: #9f5999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-btn-embed:hover {
|
/* Embed button removed */
|
||||||
border-color: var(--success-color);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dog-calculator-footer {
|
.dog-calculator-footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -543,6 +542,24 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
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 */
|
/* Dark theme - manual override */
|
||||||
@@ -709,10 +726,7 @@
|
|||||||
color: #f19a5f;
|
color: #f19a5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-btn-embed:hover {
|
/* Embed button removed */
|
||||||
border-color: var(--success-color);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme feeding configuration styles */
|
/* Dark theme feeding configuration styles */
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
|
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
|
||||||
@@ -915,10 +929,7 @@
|
|||||||
color: #f19a5f;
|
color: #f19a5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-system .dog-calculator-btn-embed:hover {
|
/* Embed button removed */
|
||||||
border-color: var(--success-color);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* System theme feeding configuration styles in dark mode */
|
/* System theme feeding configuration styles in dark mode */
|
||||||
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
|
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
|
||||||
@@ -960,13 +971,18 @@
|
|||||||
|
|
||||||
/* Modal Styles */
|
/* Modal Styles */
|
||||||
.dog-calculator-modal {
|
.dog-calculator-modal {
|
||||||
display: none;
|
display: none; /* set to flex via JS when opened */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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;
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,19 +994,19 @@
|
|||||||
.dog-calculator-modal-content {
|
.dog-calculator-modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
margin: 5% auto;
|
margin: 0;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
max-height: 90vh; /* ensure it fits viewport */
|
||||||
|
overflow: auto;
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
animation: slideIn 0.3s ease;
|
animation: slideIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-modal-embed {
|
/* Embed modal removed */
|
||||||
max-width: 700px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from {
|
from {
|
||||||
@@ -1076,74 +1092,7 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Embed Modal */
|
/* Embed UI removed */
|
||||||
.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 */
|
/* Dark theme modal styles */
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-modal-content {
|
.dog-calculator-container.theme-dark .dog-calculator-modal-content {
|
||||||
@@ -1169,28 +1118,7 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-embed-option {
|
/* Embed UI removed for dark theme */
|
||||||
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 */
|
/* System theme modal styles */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@@ -1217,28 +1145,7 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-system .dog-calculator-embed-option {
|
/* Embed UI removed for system theme */
|
||||||
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 */
|
/* Multi-Food Source Styles */
|
||||||
@@ -2199,9 +2106,6 @@
|
|||||||
<button class="dog-calculator-btn dog-calculator-btn-share" id="shareBtn">
|
<button class="dog-calculator-btn dog-calculator-btn-share" id="shareBtn">
|
||||||
Share
|
Share
|
||||||
</button>
|
</button>
|
||||||
<button class="dog-calculator-btn dog-calculator-btn-embed" id="embedBtn">
|
|
||||||
Embed
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-footer">
|
<div class="dog-calculator-footer">
|
||||||
@@ -2237,35 +2141,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Embed Modal -->
|
|
||||||
<div id="embedModal" class="dog-calculator-modal" style="display: none;">
|
|
||||||
<div class="dog-calculator-modal-content dog-calculator-modal-embed">
|
|
||||||
<span class="dog-calculator-modal-close" id="embedModalClose">×</span>
|
|
||||||
<h3>⚡ Embed the Calculator</h3>
|
|
||||||
|
|
||||||
<div class="dog-calculator-embed-options">
|
|
||||||
<div class="dog-calculator-embed-option">
|
|
||||||
<h4>⚡ JavaScript Widget</h4>
|
|
||||||
<div class="dog-calculator-code-container">
|
|
||||||
<pre><code id="widgetCode"></code></pre>
|
|
||||||
<button class="dog-calculator-copy-btn plausible-event-name=Calculator+Usage plausible-event-action=calculator-embed-js" id="copyWidget">
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dog-calculator-embed-option">
|
|
||||||
<h4>🛡️ iframe Embed</h4>
|
|
||||||
<div class="dog-calculator-code-container">
|
|
||||||
<pre><code id="iframeCode"></code></pre>
|
|
||||||
<button class="dog-calculator-copy-btn plausible-event-name=Calculator+Usage plausible-event-action=calculator-embed-iframe" id="copyIframe">
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
/**
|
/**
|
||||||
@@ -2288,6 +2164,8 @@ const CALCULATOR_CONFIG = {
|
|||||||
class DogCalorieCalculator {
|
class DogCalorieCalculator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.currentMER = 0;
|
this.currentMER = 0;
|
||||||
|
this.currentMERMin = 0; // For range calculations
|
||||||
|
this.currentMERMax = 0; // For range calculations
|
||||||
this.isImperial = false;
|
this.isImperial = false;
|
||||||
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
|
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
|
||||||
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
|
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
|
||||||
@@ -2340,10 +2218,8 @@ const CALCULATOR_CONFIG = {
|
|||||||
container.style.transform = `scale(${clampedScale})`;
|
container.style.transform = `scale(${clampedScale})`;
|
||||||
container.style.transformOrigin = 'top center';
|
container.style.transformOrigin = 'top center';
|
||||||
|
|
||||||
// Adjust container to account for scaling
|
// Recalculate height for parent without adding artificial margins
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const actualHeight = container.offsetHeight * clampedScale;
|
|
||||||
container.style.marginBottom = `${(clampedScale - 1) * container.offsetHeight}px`;
|
|
||||||
this.sendHeightToParent();
|
this.sendHeightToParent();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -3179,14 +3055,10 @@ const CALCULATOR_CONFIG = {
|
|||||||
|
|
||||||
// Modal event listeners
|
// Modal event listeners
|
||||||
const shareBtn = document.getElementById('shareBtn');
|
const shareBtn = document.getElementById('shareBtn');
|
||||||
const embedBtn = document.getElementById('embedBtn');
|
|
||||||
const shareModalClose = document.getElementById('shareModalClose');
|
const shareModalClose = document.getElementById('shareModalClose');
|
||||||
const embedModalClose = document.getElementById('embedModalClose');
|
|
||||||
|
|
||||||
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
|
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
|
||||||
if (embedBtn) embedBtn.addEventListener('click', () => this.showEmbedModal());
|
|
||||||
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
|
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
|
||||||
if (embedModalClose) embedModalClose.addEventListener('click', () => this.hideEmbedModal());
|
|
||||||
|
|
||||||
// Share buttons
|
// Share buttons
|
||||||
const shareFacebook = document.getElementById('shareFacebook');
|
const shareFacebook = document.getElementById('shareFacebook');
|
||||||
@@ -3201,16 +3073,10 @@ const CALCULATOR_CONFIG = {
|
|||||||
if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
|
if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
|
||||||
if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
|
if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
|
||||||
|
|
||||||
// Copy buttons
|
// Embed copy buttons removed (embedding disabled)
|
||||||
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'));
|
|
||||||
|
|
||||||
// Close modals on outside click
|
// Close modals on outside click
|
||||||
const shareModal = document.getElementById('shareModal');
|
const shareModal = document.getElementById('shareModal');
|
||||||
const embedModal = document.getElementById('embedModal');
|
|
||||||
|
|
||||||
if (shareModal) {
|
if (shareModal) {
|
||||||
shareModal.addEventListener('click', (e) => {
|
shareModal.addEventListener('click', (e) => {
|
||||||
@@ -3218,11 +3084,7 @@ const CALCULATOR_CONFIG = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (embedModal) {
|
// Embed modal removed
|
||||||
embedModal.addEventListener('click', (e) => {
|
|
||||||
if (e.target === embedModal) this.hideEmbedModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleUnits() {
|
toggleUnits() {
|
||||||
@@ -3335,6 +3197,25 @@ const CALCULATOR_CONFIG = {
|
|||||||
return 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) {
|
validateInput(value, min = 0, isInteger = false) {
|
||||||
const num = parseFloat(value);
|
const num = parseFloat(value);
|
||||||
if (isNaN(num) || num < min) return false;
|
if (isNaN(num) || num < min) return false;
|
||||||
@@ -3466,14 +3347,24 @@ const CALCULATOR_CONFIG = {
|
|||||||
const rer = this.calculateRER(weightKg);
|
const rer = this.calculateRER(weightKg);
|
||||||
const mer = this.calculateMER(rer, factor);
|
const mer = this.calculateMER(rer, factor);
|
||||||
|
|
||||||
this.currentMER = mer;
|
// 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';
|
rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
|
||||||
merValue.textContent = this.formatNumber(mer, 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';
|
calorieResults.style.display = 'block';
|
||||||
|
|
||||||
this.updateFoodCalculations();
|
this.updateFoodCalculations();
|
||||||
this.sendHeightToParent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCupsButtonState() {
|
updateCupsButtonState() {
|
||||||
@@ -3504,6 +3395,9 @@ const CALCULATOR_CONFIG = {
|
|||||||
updateFoodCalculations() {
|
updateFoodCalculations() {
|
||||||
if (this.currentMER === 0) return;
|
if (this.currentMER === 0) return;
|
||||||
|
|
||||||
|
// Check if we have a range
|
||||||
|
const hasRange = this.currentMERMin !== this.currentMERMax;
|
||||||
|
|
||||||
const daysInput = document.getElementById('days');
|
const daysInput = document.getElementById('days');
|
||||||
const unitSelect = document.getElementById('unit');
|
const unitSelect = document.getElementById('unit');
|
||||||
const dailyFoodResults = document.getElementById('dailyFoodResults');
|
const dailyFoodResults = document.getElementById('dailyFoodResults');
|
||||||
@@ -3576,43 +3470,70 @@ const CALCULATOR_CONFIG = {
|
|||||||
|
|
||||||
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
|
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
|
||||||
const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100;
|
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 dailyGramsForThisFood;
|
||||||
|
let dailyGramsMin, dailyGramsMax;
|
||||||
let dailyCupsForThisFood = null;
|
let dailyCupsForThisFood = null;
|
||||||
|
let dailyCupsMin, dailyCupsMax;
|
||||||
|
|
||||||
// For kcal/cup, calculate cups directly from calories
|
// For kcal/cup, calculate cups directly from calories
|
||||||
if (fs.energyUnit === 'kcalcup' && fs.energy) {
|
if (fs.energyUnit === 'kcalcup' && fs.energy) {
|
||||||
const caloriesPerCup = parseFloat(fs.energy);
|
const caloriesPerCup = parseFloat(fs.energy);
|
||||||
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
|
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
|
||||||
|
dailyCupsMin = dailyCaloriesMin / caloriesPerCup;
|
||||||
|
dailyCupsMax = dailyCaloriesMax / caloriesPerCup;
|
||||||
// We still need grams for total calculation, use approximation
|
// We still need grams for total calculation, use approximation
|
||||||
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
||||||
console.log('Cups calculation:', {
|
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
|
||||||
caloriesPerCup,
|
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
|
||||||
dailyCaloriesForThisFood,
|
|
||||||
dailyCupsForThisFood,
|
|
||||||
dailyGramsForThisFood
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// For other units, calculate grams normally
|
// For other units, calculate grams normally
|
||||||
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
||||||
|
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
|
||||||
|
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate per-meal amounts if needed
|
// Calculate per-meal amounts if needed
|
||||||
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
|
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 ?
|
const displayCups = dailyCupsForThisFood !== null ?
|
||||||
(this.showPerMeal ? dailyCupsForThisFood / this.mealsPerDay : 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 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({
|
foodBreakdowns.push({
|
||||||
name: fs.name,
|
name: fs.name,
|
||||||
percentage: fs.percentage,
|
percentage: fs.percentage,
|
||||||
dailyGrams: dailyGramsForThisFood,
|
dailyGrams: dailyGramsForThisFood,
|
||||||
|
dailyGramsMin: dailyGramsMin,
|
||||||
|
dailyGramsMax: dailyGramsMax,
|
||||||
displayGrams: displayGrams,
|
displayGrams: displayGrams,
|
||||||
|
displayGramsMin: displayGramsMin,
|
||||||
|
displayGramsMax: displayGramsMax,
|
||||||
dailyCups: dailyCupsForThisFood,
|
dailyCups: dailyCupsForThisFood,
|
||||||
|
dailyCupsMin: dailyCupsMin,
|
||||||
|
dailyCupsMax: dailyCupsMax,
|
||||||
displayCups: displayCups,
|
displayCups: displayCups,
|
||||||
|
displayCupsMin: displayCupsMin,
|
||||||
|
displayCupsMax: displayCupsMax,
|
||||||
calories: dailyCaloriesForThisFood,
|
calories: dailyCaloriesForThisFood,
|
||||||
displayCalories: displayCalories,
|
displayCalories: displayCalories,
|
||||||
|
displayCaloriesMin: displayCaloriesMin,
|
||||||
|
displayCaloriesMax: displayCaloriesMax,
|
||||||
isLocked: fs.isLocked,
|
isLocked: fs.isLocked,
|
||||||
hasEnergyContent: true,
|
hasEnergyContent: true,
|
||||||
|
hasRange: hasRange,
|
||||||
foodSource: fs // Store reference for cups conversion
|
foodSource: fs // Store reference for cups conversion
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3717,12 +3638,23 @@ const CALCULATOR_CONFIG = {
|
|||||||
if (unit === 'cups') {
|
if (unit === 'cups') {
|
||||||
// For cups, use the pre-calculated cups value if available
|
// For cups, use the pre-calculated cups value if available
|
||||||
if (breakdown.displayCups !== null) {
|
if (breakdown.displayCups !== null) {
|
||||||
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
|
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 {
|
} else {
|
||||||
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
|
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
|
// 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 {
|
} else {
|
||||||
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
|
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
|
||||||
@@ -3760,23 +3692,53 @@ const CALCULATOR_CONFIG = {
|
|||||||
if (validForCups) {
|
if (validForCups) {
|
||||||
// Calculate total cups using pre-calculated values
|
// Calculate total cups using pre-calculated values
|
||||||
let totalCups = 0;
|
let totalCups = 0;
|
||||||
|
let totalCupsMin = 0;
|
||||||
|
let totalCupsMax = 0;
|
||||||
foodBreakdowns.forEach(breakdown => {
|
foodBreakdowns.forEach(breakdown => {
|
||||||
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
|
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
|
||||||
totalCups += breakdown.displayCups;
|
totalCups += breakdown.displayCups;
|
||||||
|
if (breakdown.hasRange) {
|
||||||
|
totalCupsMin += breakdown.displayCupsMin || breakdown.displayCups;
|
||||||
|
totalCupsMax += breakdown.displayCupsMax || breakdown.displayCups;
|
||||||
|
} else {
|
||||||
|
totalCupsMin += breakdown.displayCups;
|
||||||
|
totalCupsMax += breakdown.displayCups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('Total cups display:', {
|
|
||||||
totalCups,
|
if (hasRange && totalCupsMin !== totalCupsMax) {
|
||||||
displayTotal,
|
totalDisplayText = `${this.formatNumber(totalCupsMin, decimals)}-${this.formatNumber(totalCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
|
||||||
foodBreakdowns: foodBreakdowns.map(b => ({ name: b.name, displayCups: b.displayCups }))
|
} else {
|
||||||
});
|
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
|
||||||
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
|
}
|
||||||
} else {
|
} else {
|
||||||
totalDisplayText = 'Mixed units - see breakdown';
|
totalDisplayText = 'Mixed units - see breakdown';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
convertedTotal = this.convertUnits(displayTotal, unit);
|
// Calculate totals for ranges
|
||||||
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
|
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;
|
dailyFoodValue.textContent = totalDisplayText;
|
||||||
@@ -3866,9 +3828,8 @@ const CALCULATOR_CONFIG = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foodAmountsSection.style.display = 'block';
|
foodAmountsSection.style.display = 'block';
|
||||||
|
this.sendHeightToParent();
|
||||||
this.sendHeightToParent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getFoodSourceEnergyPer100g(foodSource) {
|
getFoodSourceEnergyPer100g(foodSource) {
|
||||||
@@ -3894,13 +3855,18 @@ const CALCULATOR_CONFIG = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iframe auto-resize for allowed embeddings
|
||||||
setupIframeResize() {
|
setupIframeResize() {
|
||||||
// Send height to parent window for iframe auto-resize
|
// Only when embedded in an iframe
|
||||||
this.sendHeightToParent();
|
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(() => {
|
const observer = new MutationObserver(() => {
|
||||||
setTimeout(() => this.sendHeightToParent(), 100);
|
clearTimeout(this._resizeTimer);
|
||||||
|
this._resizeTimer = setTimeout(() => this.sendHeightToParent(), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.body, {
|
observer.observe(document.body, {
|
||||||
@@ -3909,18 +3875,25 @@ const CALCULATOR_CONFIG = {
|
|||||||
attributes: true
|
attributes: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send height on window resize
|
// On viewport resize
|
||||||
window.addEventListener('resize', () => this.sendHeightToParent());
|
window.addEventListener('resize', () => this.sendHeightToParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
sendHeightToParent() {
|
sendHeightToParent() {
|
||||||
const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
if (!(window.parent && window.parent !== window)) return;
|
||||||
if (window.parent && window.parent !== window) {
|
const container = document.getElementById('dogCalculator');
|
||||||
window.parent.postMessage({
|
// Prefer visual height including transform scaling
|
||||||
type: 'dogCalculatorResize',
|
let height = 0;
|
||||||
height: height
|
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
|
// Modal functionality
|
||||||
@@ -3929,50 +3902,25 @@ const CALCULATOR_CONFIG = {
|
|||||||
const shareUrl = document.getElementById('shareUrl');
|
const shareUrl = document.getElementById('shareUrl');
|
||||||
if (modal && shareUrl) {
|
if (modal && shareUrl) {
|
||||||
shareUrl.value = window.location.href;
|
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() {
|
hideShareModal() {
|
||||||
const modal = document.getElementById('shareModal');
|
const modal = document.getElementById('shareModal');
|
||||||
if (modal) modal.style.display = 'none';
|
if (modal) modal.style.display = 'none';
|
||||||
|
this.sendHeightToParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
showEmbedModal() {
|
// Embed modal removed (embedding disabled)
|
||||||
const modal = document.getElementById('embedModal');
|
|
||||||
const widgetCode = document.getElementById('widgetCode');
|
|
||||||
const iframeCode = document.getElementById('iframeCode');
|
|
||||||
|
|
||||||
if (modal && widgetCode && iframeCode) {
|
// Embed modal removed (embedding disabled)
|
||||||
// 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 = document.getElementById('embedModal');
|
|
||||||
if (modal) modal.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
shareToFacebook() {
|
shareToFacebook() {
|
||||||
const url = encodeURIComponent(window.location.href);
|
const url = encodeURIComponent(window.location.href);
|
||||||
@@ -4019,31 +3967,41 @@ const CALCULATOR_CONFIG = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyEmbedCode(type) {
|
// Embed code copy removed (embedding disabled)
|
||||||
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
|
// Initialize calculator when DOM is ready
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Allow embedding only from approved parent hosts
|
||||||
|
if (window.top !== window.self) {
|
||||||
|
const allowedHosts = ['caninenutritionandwellness.com', 'www.caninenutritionandwellness.com'];
|
||||||
|
let parentAllowed = false;
|
||||||
|
|
||||||
|
// Prefer document.referrer when available
|
||||||
|
try {
|
||||||
|
if (document.referrer) {
|
||||||
|
const r = new URL(document.referrer);
|
||||||
|
parentAllowed = allowedHosts.includes(r.hostname);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Fallback: Chrome's ancestorOrigins (may be empty or absent)
|
||||||
|
if (!parentAllowed && window.location.ancestorOrigins && window.location.ancestorOrigins.length) {
|
||||||
|
parentAllowed = Array.from(window.location.ancestorOrigins).some((originStr) => {
|
||||||
|
try {
|
||||||
|
const o = new URL(originStr);
|
||||||
|
return allowedHosts.includes(o.hostname);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentAllowed) {
|
||||||
|
document.body.innerHTML = '<div style="max-width:720px;margin:40px auto;padding:16px;border:1px solid #ddd;border-radius:8px;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;color:#333;line-height:1.5;text-align:center;">Embedding of this calculator is only allowed on caninenutritionandwellness.com.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
new DogCalorieCalculator();
|
new DogCalorieCalculator();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+23
-6
@@ -15,14 +15,14 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
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;
|
font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container {
|
.dog-calculator-container {
|
||||||
max-width: 600px;
|
max-width: 640px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -203,6 +203,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
gap: 10px; /* Add gap between label and value */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-result-item:last-child {
|
.dog-calculator-result-item:last-child {
|
||||||
@@ -222,6 +223,7 @@
|
|||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
background: rgba(241, 154, 95, 0.15);
|
background: rgba(241, 154, 95, 0.15);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-collapsible {
|
.dog-calculator-collapsible {
|
||||||
@@ -319,10 +321,7 @@
|
|||||||
color: #9f5999;
|
color: #9f5999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-btn-embed:hover {
|
/* Embed button removed */
|
||||||
border-color: var(--success-color);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dog-calculator-footer {
|
.dog-calculator-footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -533,6 +532,24 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
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 */
|
/* Dark theme - manual override */
|
||||||
|
|||||||
+15
-125
@@ -160,10 +160,7 @@
|
|||||||
color: #f19a5f;
|
color: #f19a5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-btn-embed:hover {
|
/* Embed button removed */
|
||||||
border-color: var(--success-color);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme feeding configuration styles */
|
/* Dark theme feeding configuration styles */
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
|
.dog-calculator-container.theme-dark .dog-calculator-feeding-config {
|
||||||
@@ -366,10 +363,7 @@
|
|||||||
color: #f19a5f;
|
color: #f19a5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-system .dog-calculator-btn-embed:hover {
|
/* Embed button removed */
|
||||||
border-color: var(--success-color);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* System theme feeding configuration styles in dark mode */
|
/* System theme feeding configuration styles in dark mode */
|
||||||
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
|
.dog-calculator-container.theme-system .dog-calculator-feeding-config {
|
||||||
@@ -411,13 +405,18 @@
|
|||||||
|
|
||||||
/* Modal Styles */
|
/* Modal Styles */
|
||||||
.dog-calculator-modal {
|
.dog-calculator-modal {
|
||||||
display: none;
|
display: none; /* set to flex via JS when opened */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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;
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,19 +428,19 @@
|
|||||||
.dog-calculator-modal-content {
|
.dog-calculator-modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
margin: 5% auto;
|
margin: 0;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
max-height: 90vh; /* ensure it fits viewport */
|
||||||
|
overflow: auto;
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
animation: slideIn 0.3s ease;
|
animation: slideIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-modal-embed {
|
/* Embed modal removed */
|
||||||
max-width: 700px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from {
|
from {
|
||||||
@@ -527,74 +526,7 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Embed Modal */
|
/* Embed UI removed */
|
||||||
.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 */
|
/* Dark theme modal styles */
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-modal-content {
|
.dog-calculator-container.theme-dark .dog-calculator-modal-content {
|
||||||
@@ -620,28 +552,7 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-dark .dog-calculator-embed-option {
|
/* Embed UI removed for dark theme */
|
||||||
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 */
|
/* System theme modal styles */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@@ -668,28 +579,7 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dog-calculator-container.theme-system .dog-calculator-embed-option {
|
/* Embed UI removed for system theme */
|
||||||
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 */
|
/* Multi-Food Source Styles */
|
||||||
|
|||||||
@@ -143,9 +143,6 @@
|
|||||||
<button class="dog-calculator-btn dog-calculator-btn-share" id="shareBtn">
|
<button class="dog-calculator-btn dog-calculator-btn-share" id="shareBtn">
|
||||||
Share
|
Share
|
||||||
</button>
|
</button>
|
||||||
<button class="dog-calculator-btn dog-calculator-btn-embed" id="embedBtn">
|
|
||||||
Embed
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-footer">
|
<div class="dog-calculator-footer">
|
||||||
@@ -181,33 +178,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Embed Modal -->
|
|
||||||
<div id="embedModal" class="dog-calculator-modal" style="display: none;">
|
|
||||||
<div class="dog-calculator-modal-content dog-calculator-modal-embed">
|
|
||||||
<span class="dog-calculator-modal-close" id="embedModalClose">×</span>
|
|
||||||
<h3>⚡ Embed the Calculator</h3>
|
|
||||||
|
|
||||||
<div class="dog-calculator-embed-options">
|
|
||||||
<div class="dog-calculator-embed-option">
|
|
||||||
<h4>⚡ JavaScript Widget</h4>
|
|
||||||
<div class="dog-calculator-code-container">
|
|
||||||
<pre><code id="widgetCode"></code></pre>
|
|
||||||
<button class="dog-calculator-copy-btn plausible-event-name=Calculator+Usage plausible-event-action=calculator-embed-js" id="copyWidget">
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dog-calculator-embed-option">
|
|
||||||
<h4>🛡️ iframe Embed</h4>
|
|
||||||
<div class="dog-calculator-code-container">
|
|
||||||
<pre><code id="iframeCode"></code></pre>
|
|
||||||
<button class="dog-calculator-copy-btn plausible-event-name=Calculator+Usage plausible-event-action=calculator-embed-iframe" id="copyIframe">
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+190
-108
@@ -6,6 +6,8 @@
|
|||||||
class DogCalorieCalculator {
|
class DogCalorieCalculator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.currentMER = 0;
|
this.currentMER = 0;
|
||||||
|
this.currentMERMin = 0; // For range calculations
|
||||||
|
this.currentMERMax = 0; // For range calculations
|
||||||
this.isImperial = false;
|
this.isImperial = false;
|
||||||
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
|
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
|
||||||
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
|
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
|
||||||
@@ -58,10 +60,8 @@
|
|||||||
container.style.transform = `scale(${clampedScale})`;
|
container.style.transform = `scale(${clampedScale})`;
|
||||||
container.style.transformOrigin = 'top center';
|
container.style.transformOrigin = 'top center';
|
||||||
|
|
||||||
// Adjust container to account for scaling
|
// Recalculate height for parent without adding artificial margins
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const actualHeight = container.offsetHeight * clampedScale;
|
|
||||||
container.style.marginBottom = `${(clampedScale - 1) * container.offsetHeight}px`;
|
|
||||||
this.sendHeightToParent();
|
this.sendHeightToParent();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -897,14 +897,10 @@
|
|||||||
|
|
||||||
// Modal event listeners
|
// Modal event listeners
|
||||||
const shareBtn = document.getElementById('shareBtn');
|
const shareBtn = document.getElementById('shareBtn');
|
||||||
const embedBtn = document.getElementById('embedBtn');
|
|
||||||
const shareModalClose = document.getElementById('shareModalClose');
|
const shareModalClose = document.getElementById('shareModalClose');
|
||||||
const embedModalClose = document.getElementById('embedModalClose');
|
|
||||||
|
|
||||||
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
|
if (shareBtn) shareBtn.addEventListener('click', () => this.showShareModal());
|
||||||
if (embedBtn) embedBtn.addEventListener('click', () => this.showEmbedModal());
|
|
||||||
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
|
if (shareModalClose) shareModalClose.addEventListener('click', () => this.hideShareModal());
|
||||||
if (embedModalClose) embedModalClose.addEventListener('click', () => this.hideEmbedModal());
|
|
||||||
|
|
||||||
// Share buttons
|
// Share buttons
|
||||||
const shareFacebook = document.getElementById('shareFacebook');
|
const shareFacebook = document.getElementById('shareFacebook');
|
||||||
@@ -919,16 +915,10 @@
|
|||||||
if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
|
if (shareEmail) shareEmail.addEventListener('click', () => this.shareViaEmail());
|
||||||
if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
|
if (shareCopy) shareCopy.addEventListener('click', () => this.copyShareLink());
|
||||||
|
|
||||||
// Copy buttons
|
// Embed copy buttons removed (embedding disabled)
|
||||||
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'));
|
|
||||||
|
|
||||||
// Close modals on outside click
|
// Close modals on outside click
|
||||||
const shareModal = document.getElementById('shareModal');
|
const shareModal = document.getElementById('shareModal');
|
||||||
const embedModal = document.getElementById('embedModal');
|
|
||||||
|
|
||||||
if (shareModal) {
|
if (shareModal) {
|
||||||
shareModal.addEventListener('click', (e) => {
|
shareModal.addEventListener('click', (e) => {
|
||||||
@@ -936,11 +926,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (embedModal) {
|
// Embed modal removed
|
||||||
embedModal.addEventListener('click', (e) => {
|
|
||||||
if (e.target === embedModal) this.hideEmbedModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleUnits() {
|
toggleUnits() {
|
||||||
@@ -1053,6 +1039,25 @@
|
|||||||
return 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) {
|
validateInput(value, min = 0, isInteger = false) {
|
||||||
const num = parseFloat(value);
|
const num = parseFloat(value);
|
||||||
if (isNaN(num) || num < min) return false;
|
if (isNaN(num) || num < min) return false;
|
||||||
@@ -1184,14 +1189,24 @@
|
|||||||
const rer = this.calculateRER(weightKg);
|
const rer = this.calculateRER(weightKg);
|
||||||
const mer = this.calculateMER(rer, factor);
|
const mer = this.calculateMER(rer, factor);
|
||||||
|
|
||||||
this.currentMER = mer;
|
// 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';
|
rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
|
||||||
merValue.textContent = this.formatNumber(mer, 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';
|
calorieResults.style.display = 'block';
|
||||||
|
|
||||||
this.updateFoodCalculations();
|
this.updateFoodCalculations();
|
||||||
this.sendHeightToParent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCupsButtonState() {
|
updateCupsButtonState() {
|
||||||
@@ -1222,6 +1237,9 @@
|
|||||||
updateFoodCalculations() {
|
updateFoodCalculations() {
|
||||||
if (this.currentMER === 0) return;
|
if (this.currentMER === 0) return;
|
||||||
|
|
||||||
|
// Check if we have a range
|
||||||
|
const hasRange = this.currentMERMin !== this.currentMERMax;
|
||||||
|
|
||||||
const daysInput = document.getElementById('days');
|
const daysInput = document.getElementById('days');
|
||||||
const unitSelect = document.getElementById('unit');
|
const unitSelect = document.getElementById('unit');
|
||||||
const dailyFoodResults = document.getElementById('dailyFoodResults');
|
const dailyFoodResults = document.getElementById('dailyFoodResults');
|
||||||
@@ -1294,43 +1312,70 @@
|
|||||||
|
|
||||||
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
|
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
|
||||||
const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100;
|
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 dailyGramsForThisFood;
|
||||||
|
let dailyGramsMin, dailyGramsMax;
|
||||||
let dailyCupsForThisFood = null;
|
let dailyCupsForThisFood = null;
|
||||||
|
let dailyCupsMin, dailyCupsMax;
|
||||||
|
|
||||||
// For kcal/cup, calculate cups directly from calories
|
// For kcal/cup, calculate cups directly from calories
|
||||||
if (fs.energyUnit === 'kcalcup' && fs.energy) {
|
if (fs.energyUnit === 'kcalcup' && fs.energy) {
|
||||||
const caloriesPerCup = parseFloat(fs.energy);
|
const caloriesPerCup = parseFloat(fs.energy);
|
||||||
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
|
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
|
||||||
|
dailyCupsMin = dailyCaloriesMin / caloriesPerCup;
|
||||||
|
dailyCupsMax = dailyCaloriesMax / caloriesPerCup;
|
||||||
// We still need grams for total calculation, use approximation
|
// We still need grams for total calculation, use approximation
|
||||||
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
||||||
console.log('Cups calculation:', {
|
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
|
||||||
caloriesPerCup,
|
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
|
||||||
dailyCaloriesForThisFood,
|
|
||||||
dailyCupsForThisFood,
|
|
||||||
dailyGramsForThisFood
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// For other units, calculate grams normally
|
// For other units, calculate grams normally
|
||||||
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
|
||||||
|
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
|
||||||
|
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate per-meal amounts if needed
|
// Calculate per-meal amounts if needed
|
||||||
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
|
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 ?
|
const displayCups = dailyCupsForThisFood !== null ?
|
||||||
(this.showPerMeal ? dailyCupsForThisFood / this.mealsPerDay : 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 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({
|
foodBreakdowns.push({
|
||||||
name: fs.name,
|
name: fs.name,
|
||||||
percentage: fs.percentage,
|
percentage: fs.percentage,
|
||||||
dailyGrams: dailyGramsForThisFood,
|
dailyGrams: dailyGramsForThisFood,
|
||||||
|
dailyGramsMin: dailyGramsMin,
|
||||||
|
dailyGramsMax: dailyGramsMax,
|
||||||
displayGrams: displayGrams,
|
displayGrams: displayGrams,
|
||||||
|
displayGramsMin: displayGramsMin,
|
||||||
|
displayGramsMax: displayGramsMax,
|
||||||
dailyCups: dailyCupsForThisFood,
|
dailyCups: dailyCupsForThisFood,
|
||||||
|
dailyCupsMin: dailyCupsMin,
|
||||||
|
dailyCupsMax: dailyCupsMax,
|
||||||
displayCups: displayCups,
|
displayCups: displayCups,
|
||||||
|
displayCupsMin: displayCupsMin,
|
||||||
|
displayCupsMax: displayCupsMax,
|
||||||
calories: dailyCaloriesForThisFood,
|
calories: dailyCaloriesForThisFood,
|
||||||
displayCalories: displayCalories,
|
displayCalories: displayCalories,
|
||||||
|
displayCaloriesMin: displayCaloriesMin,
|
||||||
|
displayCaloriesMax: displayCaloriesMax,
|
||||||
isLocked: fs.isLocked,
|
isLocked: fs.isLocked,
|
||||||
hasEnergyContent: true,
|
hasEnergyContent: true,
|
||||||
|
hasRange: hasRange,
|
||||||
foodSource: fs // Store reference for cups conversion
|
foodSource: fs // Store reference for cups conversion
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1435,12 +1480,23 @@
|
|||||||
if (unit === 'cups') {
|
if (unit === 'cups') {
|
||||||
// For cups, use the pre-calculated cups value if available
|
// For cups, use the pre-calculated cups value if available
|
||||||
if (breakdown.displayCups !== null) {
|
if (breakdown.displayCups !== null) {
|
||||||
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
|
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 {
|
} else {
|
||||||
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
|
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
|
// 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 {
|
} else {
|
||||||
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
|
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
|
||||||
@@ -1478,23 +1534,53 @@
|
|||||||
if (validForCups) {
|
if (validForCups) {
|
||||||
// Calculate total cups using pre-calculated values
|
// Calculate total cups using pre-calculated values
|
||||||
let totalCups = 0;
|
let totalCups = 0;
|
||||||
|
let totalCupsMin = 0;
|
||||||
|
let totalCupsMax = 0;
|
||||||
foodBreakdowns.forEach(breakdown => {
|
foodBreakdowns.forEach(breakdown => {
|
||||||
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
|
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
|
||||||
totalCups += breakdown.displayCups;
|
totalCups += breakdown.displayCups;
|
||||||
|
if (breakdown.hasRange) {
|
||||||
|
totalCupsMin += breakdown.displayCupsMin || breakdown.displayCups;
|
||||||
|
totalCupsMax += breakdown.displayCupsMax || breakdown.displayCups;
|
||||||
|
} else {
|
||||||
|
totalCupsMin += breakdown.displayCups;
|
||||||
|
totalCupsMax += breakdown.displayCups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('Total cups display:', {
|
|
||||||
totalCups,
|
if (hasRange && totalCupsMin !== totalCupsMax) {
|
||||||
displayTotal,
|
totalDisplayText = `${this.formatNumber(totalCupsMin, decimals)}-${this.formatNumber(totalCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
|
||||||
foodBreakdowns: foodBreakdowns.map(b => ({ name: b.name, displayCups: b.displayCups }))
|
} else {
|
||||||
});
|
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
|
||||||
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
|
}
|
||||||
} else {
|
} else {
|
||||||
totalDisplayText = 'Mixed units - see breakdown';
|
totalDisplayText = 'Mixed units - see breakdown';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
convertedTotal = this.convertUnits(displayTotal, unit);
|
// Calculate totals for ranges
|
||||||
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
|
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;
|
dailyFoodValue.textContent = totalDisplayText;
|
||||||
@@ -1584,9 +1670,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foodAmountsSection.style.display = 'block';
|
foodAmountsSection.style.display = 'block';
|
||||||
|
this.sendHeightToParent();
|
||||||
this.sendHeightToParent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getFoodSourceEnergyPer100g(foodSource) {
|
getFoodSourceEnergyPer100g(foodSource) {
|
||||||
@@ -1612,13 +1697,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iframe auto-resize for allowed embeddings
|
||||||
setupIframeResize() {
|
setupIframeResize() {
|
||||||
// Send height to parent window for iframe auto-resize
|
// Only when embedded in an iframe
|
||||||
this.sendHeightToParent();
|
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(() => {
|
const observer = new MutationObserver(() => {
|
||||||
setTimeout(() => this.sendHeightToParent(), 100);
|
clearTimeout(this._resizeTimer);
|
||||||
|
this._resizeTimer = setTimeout(() => this.sendHeightToParent(), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.body, {
|
observer.observe(document.body, {
|
||||||
@@ -1627,18 +1717,25 @@
|
|||||||
attributes: true
|
attributes: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send height on window resize
|
// On viewport resize
|
||||||
window.addEventListener('resize', () => this.sendHeightToParent());
|
window.addEventListener('resize', () => this.sendHeightToParent());
|
||||||
}
|
}
|
||||||
|
|
||||||
sendHeightToParent() {
|
sendHeightToParent() {
|
||||||
const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
if (!(window.parent && window.parent !== window)) return;
|
||||||
if (window.parent && window.parent !== window) {
|
const container = document.getElementById('dogCalculator');
|
||||||
window.parent.postMessage({
|
// Prefer visual height including transform scaling
|
||||||
type: 'dogCalculatorResize',
|
let height = 0;
|
||||||
height: height
|
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
|
// Modal functionality
|
||||||
@@ -1647,50 +1744,25 @@
|
|||||||
const shareUrl = document.getElementById('shareUrl');
|
const shareUrl = document.getElementById('shareUrl');
|
||||||
if (modal && shareUrl) {
|
if (modal && shareUrl) {
|
||||||
shareUrl.value = window.location.href;
|
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() {
|
hideShareModal() {
|
||||||
const modal = document.getElementById('shareModal');
|
const modal = document.getElementById('shareModal');
|
||||||
if (modal) modal.style.display = 'none';
|
if (modal) modal.style.display = 'none';
|
||||||
|
this.sendHeightToParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
showEmbedModal() {
|
// Embed modal removed (embedding disabled)
|
||||||
const modal = document.getElementById('embedModal');
|
|
||||||
const widgetCode = document.getElementById('widgetCode');
|
|
||||||
const iframeCode = document.getElementById('iframeCode');
|
|
||||||
|
|
||||||
if (modal && widgetCode && iframeCode) {
|
// Embed modal removed (embedding disabled)
|
||||||
// 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 = document.getElementById('embedModal');
|
|
||||||
if (modal) modal.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
shareToFacebook() {
|
shareToFacebook() {
|
||||||
const url = encodeURIComponent(window.location.href);
|
const url = encodeURIComponent(window.location.href);
|
||||||
@@ -1737,30 +1809,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyEmbedCode(type) {
|
// Embed code copy removed (embedding disabled)
|
||||||
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
|
// Initialize calculator when DOM is ready
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Allow embedding only from approved parent hosts
|
||||||
|
if (window.top !== window.self) {
|
||||||
|
const allowedHosts = ['caninenutritionandwellness.com', 'www.caninenutritionandwellness.com'];
|
||||||
|
let parentAllowed = false;
|
||||||
|
|
||||||
|
// Prefer document.referrer when available
|
||||||
|
try {
|
||||||
|
if (document.referrer) {
|
||||||
|
const r = new URL(document.referrer);
|
||||||
|
parentAllowed = allowedHosts.includes(r.hostname);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Fallback: Chrome's ancestorOrigins (may be empty or absent)
|
||||||
|
if (!parentAllowed && window.location.ancestorOrigins && window.location.ancestorOrigins.length) {
|
||||||
|
parentAllowed = Array.from(window.location.ancestorOrigins).some((originStr) => {
|
||||||
|
try {
|
||||||
|
const o = new URL(originStr);
|
||||||
|
return allowedHosts.includes(o.hostname);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parentAllowed) {
|
||||||
|
document.body.innerHTML = '<div style="max-width:720px;margin:40px auto;padding:16px;border:1px solid #ddd;border-radius:8px;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;color:#333;line-height:1.5;text-align:center;">Embedding of this calculator is only allowed on caninenutritionandwellness.com.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
new DogCalorieCalculator();
|
new DogCalorieCalculator();
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Widget Test</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
.test-container {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 20px 0;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Dog Calculator Widget Test</h1>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
<h2>Test 1: Basic Widget</h2>
|
|
||||||
<div class="dog-calorie-calculator" data-theme="light" data-scale="0.9"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="test-container">
|
|
||||||
<h2>Test 2: Dark Theme Widget</h2>
|
|
||||||
<div class="dog-calorie-calculator" data-theme="dark" data-scale="0.5"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="sundog-dog-food-calculator.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user