20 Commits

Author SHA1 Message Date
Dayowe 68d5527a89 fix(kaya): lock Fred & Felia kcal/100g (always 115) 2026-02-06 12:52:13 +01:00
Dayowe 19592f2230 • feat(kaya): use current weight for Fred & Felia MER grams
- Add current weight input + validation to Kaya UI
  - Persist/restore weight in local storage
  - Compute Fred & Felia grams/day from MER using entered weight and age factor
  - Keep kibble chart-based; allow editing Fred energy density
  - Rebuild iframe.html from src/
2026-01-28 15:51:28 +01:00
Dayowe d0a30a8f8d Add local stoarge 2025-11-12 18:39:10 +01:00
Dayowe 374d067cf4 Fixes and improvements 2025-11-12 18:34:26 +01:00
Dayowe 73c4648978 Updates 2025-11-12 18:00:36 +01:00
Dayowe da9fd20ffb Remve oz, cups, lb 2025-11-12 17:00:45 +01:00
Dayowe d489a87722 Add doc 2025-11-12 16:48:43 +01:00
Dayowe 7fd139b321 Kaya transition v1 2025-11-12 16:44:43 +01:00
Dayowe 90657f9aa4 Remove embedding and js widget 2025-10-28 09:58:20 +01:00
Dayowe 99b516d087 Add range calculations for dog food amounts based on life stage
- Implement range multipliers for different life stages (e.g., 1.6-1.8x for intact adults)
- Display MER and food amounts as ranges (e.g., '2042-2298 cal/day')
- Add CSS to prevent value wrapping with white-space: nowrap
- Increase calculator max-width from 600px to 640px for better text layout
- Based on veterinary RER multiplier ranges for more accurate feeding recommendations
2025-08-18 15:33:44 +02:00
Dayowe c4770d5ee6 Fix data-theme and data-scale widget attributes not being applied
- Remove theme/scale assignments that override widget options in build process
- Widget now properly uses data-theme and data-scale attributes from HTML
- Both light and dark themes work correctly when multiple widgets on same page
- Scale attribute properly applies to each widget independently
2025-08-18 14:54:25 +02:00
Dayowe 4ec42fdbc0 Add cups unit option for kcal/cup measurements
Features:
- Add cups as a unit option that only works with kcal/cup energy measurements
- Cups button is disabled with tooltip when kcal/cup is not selected
- Auto-select cups when user selects kcal/cup as energy unit
- Auto-switch to appropriate units when changing energy measurement types
- Calculate cup amounts directly from calories without density assumptions

Fixes:
- Ensure cups display correct values immediately upon auto-selection
- Fix "Per day" radio button to be pre-selected when feeding config becomes visible
- Handle empty unit values with failsafe fallback to active button

Technical details:
- Direct calorie-to-cups conversion bypassing gram conversions
- Pre-calculate cups values in food breakdown for efficient display
- Set unit before updating calculations to avoid timing issues
- Added CSS styling for disabled button state across all themes
2025-08-18 14:36:25 +02:00
Dayowe 8758ac1dbc Add per-meal feeding frequency feature
- Added feeding configuration section with daily/per-meal toggle
- Implemented meals per day selector (1-10 meals)
- Updated all calculations to support per-meal division
- Dynamic unit labels change from "/day" to "/meal"
- Added helpful meal count display for multi-day calculations
- Proper dark theme support for new UI elements
- Mobile responsive design for feeding controls
- Centered meal input group for better UX

This feature allows users to easily switch between viewing daily food amounts
and per-meal portions, making feeding schedules more practical to follow.
2025-08-18 12:45:44 +02:00
Dayowe c7a4aa01ba Improve build system and clean up project structure
- Configure build script to create backups in backup/ directory
- Add backup/ directory to .gitignore to keep repo clean
- Add .gitignore file with common exclusions
- Remove obsolete dog-food-calculator-widget.js file
- Move all temporary and reference files to sundog-calculator-meta/

This keeps the project root clean and organized while maintaining
automatic backups during builds.
2025-08-18 09:21:54 +02:00
Dayowe 73793f43a1 Reorganize source structure for better maintainability
- Move from 3-file structure to organized 5-file structure
- Create css/ and js/ subdirectories for better organization
- Split styles into main.css and themes.css for clarity
- Extract configuration constants to separate config.js file
- Rename template.html to index.html for clarity
- Update build.js to handle new organized structure
- Replace magic numbers with CALCULATOR_CONFIG constants

New structure:
  src/
    ├── index.html       (HTML template)
    ├── css/
    │   ├── main.css     (Core styles)
    │   └── themes.css   (Theme variations)
    └── js/
        ├── config.js    (Configuration constants)
        └── calculator.js (Main logic)

This provides a good balance between organization and simplicity,
making the codebase easier to maintain without over-modularization.
2025-08-18 09:05:00 +02:00
Dayowe ba3e0a6a6a Modularize calculator into separate source files
- Split 3470-line iframe.html into manageable components
- Created src/ directory with styles.css, template.html, calculator.js
- Updated build.js to assemble from modular source files
- Maintains identical functionality with improved maintainability
- Single source of truth: edit in src/, run build.js to generate both outputs
2025-08-17 21:20:05 +02:00
Dayowe 976e7d9136 Updte file 2025-06-26 18:10:03 +02:00
Dayowe 6a22bac56c No modal bg 2025-06-26 18:09:18 +02:00
Dayowe 650b469202 Fix share/embed modals 2025-06-26 18:03:39 +02:00
Dayowe c4a15a95b3 Update README.md 2025-06-26 17:35:39 +02:00
15 changed files with 5754 additions and 5963 deletions
+27
View File
@@ -0,0 +1,27 @@
# Dependencies
node_modules/
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Temporary files
*.tmp
*.temp
# Personal files
theme.scss
_variables.scss
reference.png
better.png
START_PROMPT.md
CLAUDE.md
CLAUDE_V2.md
math.md
vetcalculators/
*.js.backup*
backup/
+43
View File
@@ -0,0 +1,43 @@
Kayas Transition Calculator — Friendly Guide
============================================
This quick guide shows you how to use the calculator to move Kaya from kibble to gently cooked food at a steady, safe pace.
How to use it
-------------
1) Enter Kayas age in months (e.g. "5.5")
2) Check the energy numbers for your foods:
- Eukanuba (kibble) defaults to 372 kcal/100g (replace if your bag shows a different number)
- Fred & Felia (gently cooked) defaults to 115 kcal/100g (replace if your bag shows a different number)
- Add or rename the foods (e.g., “Treats”) and enter their energy values from the label (kcal/100 g, kcal/kg, kcal/cup, or kcal/can).
3) Set your percentages. We will start small (e.g., GC 5%, kibble 95%), lock any values you want fixed.
4) Choose how to view amounts:
- Per day or per meal; set meals/day if needed.
- Pick your preferred units (grams, kg).
- Use “days” to see totals for meal prep.
Regarding treats
-----
- Keep treats ≤10% (your plan is good). Larger shares can dilute the balanced portion of the diet.
- Enter treat kcal from the package (asfed) for accuracy.
- Monitor body condition and stool; adjust if needed.
What youll see
---------------
- Exact amounts for each food based on your percentages and energy labels.
- Totals per day (or per meal), and optional multiday batches.
- Everything updates instantly as you change inputs.
Good to know
------------
- Gently cooked is less caloriedense than kibble. As you add more GC, total grams may go up. This is normal.
- Treats count. I added a “Treats” source with its kcal so the plan stays balanced.
- When in doubt, doublecheck kcal values on the package (kcal/100 g or kcal/kg are most common).
Remember
-----------------
Every dog is unique. Monitor body condition, stool quality, and appetite. We will adjust percentages and amounts as Kaya grows and responds to the new plan.
+38
View File
@@ -0,0 +1,38 @@
Kaya Transition Calculator — Quick Guide
=======================================
This tool helps transition Kaya (30 kg adult) from kibble to gently cooked while keeping daily energy intake continuous. Enter Kayas age, set food sources and their energy values, then adjust percentages to see exactly how much to feed of each food.
How to use
----------
- Enter Kayas age in months (2.012.0). If outside this range, the tool adjusts to the nearest valid value.
- Confirm energy values for each food source:
- “Eukanuba, kibble” defaults to 372 kcal/100 g — change if your bag shows a different value.
- “Fred & Felia, gently cooked” defaults to 115 kcal/100 g.
- Add/rename sources (e.g., “Treats”) and enter their energy (kcal/100 g, kcal/kg, kcal/cup, or kcal/can).
- Set percentages for each food; lock any you want fixed. The total always equals 100%.
- Optional: switch units (g/kg/oz/lb; cups enabled only when kcal/cup is entered), choose perday or permeal, set meals/day, and use “days” to see batch totals.
What it calculates
------------------
- Daily energy target is derived from the 30 kg kibble feeding curve using Kayas exact age (monthlevel interpolation), not from generic MER.
- That daily kcal target is split across your foods by percentage and converted into amounts using each foods energy density.
- Results include perfood amounts and totals, perday or permeal, in your selected units.
Formulas (at a glance)
----------------------
- Kibble grams/day (30 kg) by month (g/day):
2: 250, 3: 330, 4: 365, 5: 382, 6: 400, 7: 405, 8: 410, 9: 410, 10: 410, 11: 408, 12: 405.
Linear interpolation is applied between months (e.g., 5.5 months is halfway between 5 and 6).
- Daily kcal target = kibble_g/day × (kibble_kcal_per_100g ÷ 100)
- Perfood kcal = daily_kcal × (food_percentage ÷ 100)
- Perfood grams (kcal/100 g) = perfood_kcal ÷ (kcal_per_100g ÷ 100)
- Perfood grams (kcal/kg) = perfood_kcal ÷ (kcal_per_kg ÷ 1000)
- Cups (when kcal/cup provided) = perfood_kcal ÷ kcal_per_cup
- Internal assumptions for conversions: 1 cup ≈ 120 g (dry), 1 can ≈ 450 g (wet)
Notes
-----
- Age input is limited to 212 months; values are rounded for display (permeal amounts round after splitting).
- Accuracy depends on correct energy values on your foods labels. When in doubt, confirm the kcal numbers on the packaging.
- This guide supports professional planning; your nutritionist may finetune based on Kayas body condition and response.
+143 -116
View File
@@ -1,55 +1,109 @@
# 🐕 Sundog Dog Food Calorie Calculator # 🐕 Sundog Dog Food Calorie Calculator
A professional veterinary nutrition tool for calculating dogs' daily calorie requirements and food amounts. 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; thirdparty embedding is no longer supported.
**By [Canine Nutrition and Wellness](https://caninenutritionandwellness.com)** **By [Canine Nutrition and Wellness](https://caninenutritionandwellness.com)**
## ✨ Features ## ✨ Features
### Core Calculation Features
- **Accurate Calculations**: Uses veterinary-standard RER formula: `70 × (weight in kg)^0.75` - **Accurate Calculations**: Uses veterinary-standard RER formula: `70 × (weight in kg)^0.75`
- **Multiple Activity Levels**: 11 different dog types and activity factors - **Multiple Activity Levels**: 11 different dog types and activity factors
- **Multi-Region Support**: kcal/100g (EU/UK), kcal/kg, kcal/cup, kcal/can (US/Canada) - **Multi-Region Support**: kcal/100g (EU/UK), kcal/kg, kcal/cup, kcal/can (US/Canada)
- **Food Amount Calculator**: Converts calories to grams/kg/oz/lb of food - **Unit Conversion**: Automatic conversion to grams/kg/oz/lb for food amounts
- **Scalable Widget**: Easily resize from 50% to 200% with data attributes - **Smart Unit Selection**: Auto-selects grams for metric and ounces for imperial systems
- **Unit Selection Buttons**: Intuitive button interface for choosing display units (g/kg/oz/lb)
### Multi-Food Source Management
- **Multiple Food Sources**: Add up to 5 different food sources per diet plan
- **Percentage System**: Distribute diet percentages across multiple foods with real-time validation
- **Percentage Locking**: Lock specific food source percentages to maintain fixed ratios
- **Smart Redistribution**: Automatic percentage rebalancing when sources are added/removed
- **Editable Food Names**: Click-to-edit food source names (e.g., "Morning Kibble", "Evening Wet Food")
### Food Amount Breakdown
- **Individual Food Amounts**: See exact amounts needed for each food source
- **Per-Food Calculations**: Calculate specific quantities for different food types
- **Total Summary**: Combined totals with clear breakdown by food source
- **Lock Indicators**: Visual indicators showing which percentages are locked
### User Experience
- **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, works on all devices - **Responsive Design**: Mobile-first, optimized layouts for all devices
- **Brand Integration**: Uses Canine Nutrition and Wellness color scheme
- **Two Embedding Options**: JavaScript widget and iframe
- **Accessibility**: Full keyboard navigation and screen reader support - **Accessibility**: Full keyboard navigation and screen reader support
## 🚀 Quick Start ### Brand & Integration
- **Brand Integration**: Uses Canine Nutrition and Wellness color scheme
- **Professional Design**: Clean, veterinary-grade interface
- **Brand Protection**: Complete iframe isolation option
## 🚀 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>
``` ```
## 🍽️ Multi-Food Source Usage
The calculator supports complex feeding plans with multiple food sources, perfect for mixed diets combining dry food, wet food, treats, and supplements.
### Basic Multi-Food Workflow
1. **Start with one food source** - Calculator begins with a single "Food Source 1"
2. **Add more sources** - Click "Add another food source" (up to 5 total)
3. **Customize names** - Click any food source name to edit (e.g., "Morning Kibble", "Evening Wet Food")
4. **Set energy content** - Enter kcal values and select appropriate units for each food
5. **Adjust percentages** - Use sliders or input fields to distribute diet percentages
6. **Lock percentages** - Click 🔒 to lock specific food source percentages
7. **Get individual amounts** - See exact quantities needed for each food source
### Percentage System Features
- **Real-time Validation**: Percentages always total exactly 100%
- **Smart Redistribution**: When you change one percentage, others adjust automatically
- **Percentage Locking**: Lock specific sources to maintain fixed ratios
- **Visual Feedback**: Lock indicators show which percentages are fixed
- **Bulletproof Logic**: Prevents impossible states (negative percentages, >100% totals)
### Example Usage Scenarios
**Mixed Diet Example:**
```
Royal Canin Dry Food → 70% (locked)
Blue Buffalo Wet Food → 25%
Training Treats → 5%
```
**Meal-Based Planning:**
```
Morning Kibble → 50%
Evening Wet Food → 30%
Midday Snacks → 20%
```
**Transition Diet:**
```
Old Food (reducing) → 25%
New Food (increasing) → 75% (locked)
```
### Food Amount Breakdown
The calculator provides detailed breakdowns showing:
- **Individual amounts** for each food source
- **Percentage distribution** with visual indicators
- **Lock status** for each food source
- **Total combined amount** for the specified number of days
- **Unit conversion** (grams, kg, oz, lb) for all amounts
## ⚙️ Configuration Options ## ⚙️ Configuration Options
### Theme Options ### Theme Options
@@ -59,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%)
@@ -71,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
@@ -100,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
``` ```
@@ -142,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
@@ -218,18 +225,39 @@ Your branding is completely protected in iframe mode. Users cannot:
## 🧪 Testing ## 🧪 Testing
### Manual Testing Checklist ### Manual Testing Checklist
#### Core Functionality
- [ ] All dog type selections work - [ ] All dog type selections work
- [ ] Weight validation (minimum 0.1kg) - [ ] Weight validation (minimum 0.1kg)
- [ ] RER/MER calculations accurate - [ ] RER/MER calculations accurate
- [ ] Food energy unit selector (kcal/100g, kcal/kg, kcal/cup, kcal/can)
- [ ] Food energy validation (minimum values per unit)
- [ ] Unit conversions (g/kg/oz/lb) correct - [ ] Unit conversions (g/kg/oz/lb) correct
- [ ] Theme switching (light/dark/system) - [ ] Theme switching (light/dark/system)
- [ ] Scale options (0.5x to 2.0x) work properly - [ ] Scale options (0.5x to 2.0x) work properly
- [ ] Collapsible section toggles
#### Multi-Food Source Features
- [ ] Add food sources (up to 5 maximum)
- [ ] Remove food sources (minimum 1 maintained)
- [ ] Edit food source names (click-to-edit functionality)
- [ ] Food energy content validation per source
- [ ] Food energy unit selector per source (kcal/100g, kcal/kg, kcal/cup, kcal/can)
- [ ] Percentage slider adjustments work correctly
- [ ] Percentage input field validation
- [ ] Percentage locking/unlocking (🔒 icon)
- [ ] Smart percentage redistribution when sources change
- [ ] Total percentages always equal 100%
- [ ] Individual food amount calculations
- [ ] Food amount breakdown display
- [ ] Add button states ("Add another food source" vs "Maximum 5 sources reached")
#### User Interface
- [ ] Mobile responsive layout - [ ] Mobile responsive layout
- [ ] Collapsible section toggles
- [ ] Visual lock indicators display correctly
- [ ] Percentage badges and styling
- [ ] Branded footer link works - [ ] Branded footer link works
- [ ] Box shadows consistent across all sections - [ ] Box shadows consistent across all sections
- [ ] Food source name alignment on mobile
- [ ] Proper input field sizing on mobile
### Browser Compatibility ### Browser Compatibility
- ✅ Chrome 90+ - ✅ Chrome 90+
@@ -257,8 +285,7 @@ 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)
- **Demo**: Open `embed-demo.html` in your browser - **Standalone**: Open `iframe.html` in your browser
- **Standalone**: Open `index.html` in your browser
--- ---
+118 -69
View File
@@ -1,46 +1,73 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Dog Calculator Build System - PRODUCTION VERSION * Dog Calculator Build System - ORGANIZED VERSION
* *
* This FIXED build script generates iframe.html and dog-calculator-widget.js * This build script generates iframe.html from organized source files
* with EXACTLY the same functionality from iframe.html as the single source of truth. * in the src/ directory.
*
* Source structure:
* - src/index.html - HTML template
* - src/css/main.css - Main styles
* - src/css/themes.css - Theme-specific styles
* - src/js/config.js - Configuration constants
* - src/js/calculator.js - JavaScript functionality
*
* Output files:
* - iframe.html - Standalone calculator page
* *
* Usage: node build.js * Usage: node build.js
*
* ✅ WORKS CORRECTLY - Fixed the previous broken implementation
*/ */
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
console.log('🎯 Dog Calculator Build System - FIXED & WORKING'); console.log('🎯 Dog Calculator Build System - ORGANIZED VERSION');
console.log(''); console.log('');
/** /**
* Extract and parse components from the master iframe.html * Read organized components from src directory
*/ */
function parseIframeComponents() { function readSourceComponents() {
if (!fs.existsSync('iframe.html')) { const srcDir = 'src';
throw new Error('iframe.html not found - this is the master file that should exist');
// Check if src directory exists
if (!fs.existsSync(srcDir)) {
throw new Error('src directory not found');
} }
const content = fs.readFileSync('iframe.html', 'utf8'); // Read CSS files
const mainCssPath = path.join(srcDir, 'css', 'main.css');
const themesCssPath = path.join(srcDir, 'css', 'themes.css');
if (!fs.existsSync(mainCssPath)) {
throw new Error('src/css/main.css not found');
}
if (!fs.existsSync(themesCssPath)) {
throw new Error('src/css/themes.css not found');
}
const mainCss = fs.readFileSync(mainCssPath, 'utf8').trim();
const themesCss = fs.readFileSync(themesCssPath, 'utf8').trim();
const css = mainCss + '\n\n' + themesCss;
// Extract CSS (everything between <style> and </style>) // Read HTML template
const cssMatch = content.match(/<style>([\s\S]*?)<\/style>/); const htmlPath = path.join(srcDir, 'index.html');
if (!cssMatch) throw new Error('Could not extract CSS from iframe.html'); if (!fs.existsSync(htmlPath)) {
const css = cssMatch[1].trim(); throw new Error('src/index.html not found');
}
const html = fs.readFileSync(htmlPath, 'utf8').trim();
// Extract HTML body (everything between <body> and <script>) // Read JavaScript files
const htmlMatch = content.match(/<body>([\s\S]*?)<script>/); const configPath = path.join(srcDir, 'js', 'config.js');
if (!htmlMatch) throw new Error('Could not extract HTML from iframe.html'); const calculatorPath = path.join(srcDir, 'js', 'calculator.js');
const html = htmlMatch[1].trim(); if (!fs.existsSync(configPath)) {
throw new Error('src/js/config.js not found');
// Extract JavaScript (everything between <script> and </script>) }
const jsMatch = content.match(/<script>([\s\S]*?)<\/script>/); if (!fs.existsSync(calculatorPath)) {
if (!jsMatch) throw new Error('Could not extract JavaScript from iframe.html'); throw new Error('src/js/calculator.js not found');
const js = jsMatch[1].trim(); }
const config = fs.readFileSync(configPath, 'utf8').trim();
const calculator = fs.readFileSync(calculatorPath, 'utf8').trim();
const js = config + '\n\n' + calculator;
return { css, html, js }; return { css, html, js };
} }
@@ -49,17 +76,27 @@ function parseIframeComponents() {
* Backup existing files * Backup existing files
*/ */
function backupFiles() { function backupFiles() {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupDir = 'backup';
if (fs.existsSync('sundog-dog-food-calculator.js')) { // Create backup directory if it doesn't exist
const backupName = `sundog-dog-food-calculator.js.backup-${timestamp}`; if (!fs.existsSync(backupDir)) {
fs.copyFileSync('sundog-dog-food-calculator.js', backupName); fs.mkdirSync(backupDir);
console.log(`📦 Backed up existing widget to: ${backupName}`);
} }
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filesToBackup = ['iframe.html'];
filesToBackup.forEach(file => {
if (fs.existsSync(file)) {
const backupName = path.join(backupDir, `${file}.backup-${timestamp}`);
fs.copyFileSync(file, backupName);
console.log(` 📦 Backed up ${file}`);
}
});
} }
/** /**
* Generate iframe.html (regenerate from components for consistency) * Generate iframe.html from modular components
*/ */
function generateIframe(css, html, js) { function generateIframe(css, html, js) {
const content = `<!DOCTYPE html> const content = `<!DOCTYPE html>
@@ -69,6 +106,9 @@ function generateIframe(css, html, js) {
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dog Calorie Calculator - Canine Nutrition and Wellness</title> <title>Dog Calorie Calculator - Canine Nutrition and Wellness</title>
<style> <style>
/* Sundog Dog Food Calorie Calculator Styles */
/* CSS Variables for theming */
${css} ${css}
</style> </style>
</head> </head>
@@ -80,30 +120,14 @@ function generateIframe(css, html, js) {
</body> </body>
</html>`; </html>`;
// Since iframe.html is our source, we don't overwrite it
// This function is here for consistency and testing
return content; return content;
} }
/** /**
* No transformation needed - keep original class names for consistency * Create production-ready widget JavaScript
*/
function transformCSSForWidget(css) {
return css;
}
/**
* No transformation needed - keep original class names for consistency
*/
function transformHTMLForWidget(html) {
return html;
}
/**
* Create production-ready widget JavaScript that uses the ACTUAL extracted JS from iframe.html
*/ */
function createWidgetJS(css, html, js) { function createWidgetJS(css, html, js) {
// Transform the extracted JavaScript from iframe.html to work as a widget // Transform the JavaScript from calculator.js to work as a widget
// Replace the iframe's DOMContentLoaded listener with widget initialization // Replace the iframe's DOMContentLoaded listener with widget initialization
let transformedJS = js let transformedJS = js
@@ -116,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;
@@ -161,26 +186,32 @@ 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';
} }
}$2`); }$2`);
// Add CSS variables to widget CSS
const widgetCSS = `/* Sundog Dog Food Calorie Calculator Styles */
/* CSS Variables for theming */
${css}`;
const widgetCode = `/** const widgetCode = `/**
* Dog Calorie Calculator Widget * Dog Calorie Calculator Widget
* Embeddable JavaScript widget for websites * Embeddable JavaScript widget for websites
* *
* THIS CODE IS AUTO-GENERATED FROM iframe.html - DO NOT EDIT MANUALLY * THIS CODE IS AUTO-GENERATED FROM src/ FILES - DO NOT EDIT MANUALLY
* Edit iframe.html and run 'node build.js' to update this file * Edit files in src/ directory and run 'node build.js' to update
* *
* Usage: * Usage:
* <script src="sundog-dog-food-calculator.js"></script> * <script src="sundog-dog-food-calculator.js"></script>
@@ -197,7 +228,7 @@ function createWidgetJS(css, html, js) {
'use strict'; 'use strict';
// Inject widget styles // Inject widget styles
const CSS_STYLES = \`${css}\`; const CSS_STYLES = \`${widgetCSS}\`;
function injectStyles() { function injectStyles() {
if (document.getElementById('dog-calculator-styles')) return; if (document.getElementById('dog-calculator-styles')) return;
@@ -208,7 +239,7 @@ function createWidgetJS(css, html, js) {
document.head.appendChild(style); document.head.appendChild(style);
} }
// ACTUAL JavaScript from iframe.html (transformed for widget use) // JavaScript from src/calculator.js (transformed for widget use)
${transformedJS} ${transformedJS}
// Auto-initialize widgets on page load // Auto-initialize widgets on page load
@@ -249,37 +280,55 @@ function createWidgetJS(css, html, js) {
*/ */
function build() { function build() {
try { try {
console.log('📖 Reading components from iframe.html (master source)...'); console.log('📖 Reading organized components from src/ directory...');
const { css, html, js } = parseIframeComponents(); const { css, html, js } = readSourceComponents();
console.log(' ✅ src/index.html (' + html.split('\n').length + ' lines)');
console.log(' ✅ src/css/main.css + themes.css (' + css.split('\n').length + ' lines)');
console.log(' ✅ src/js/config.js + calculator.js (' + js.split('\n').length + ' lines)');
console.log('');
console.log('📦 Backing up existing files...'); console.log('📦 Backing up existing files...');
backupFiles(); backupFiles();
console.log('🏗️ Generating sundog-dog-food-calculator.js...'); console.log('');
const widgetCode = createWidgetJS(css, html, js); console.log('🏗️ Building output files...');
fs.writeFileSync('sundog-dog-food-calculator.js', widgetCode);
console.log('✅ Generated sundog-dog-food-calculator.js'); // Generate iframe.html
const iframeContent = generateIframe(css, html, js);
fs.writeFileSync('iframe.html', iframeContent);
console.log(' ✅ Generated iframe.html');
// Embeddable widget generation removed (embedding no longer supported)
console.log(''); console.log('');
console.log('🎉 Build completed successfully!'); console.log('🎉 Build completed successfully!');
console.log(''); console.log('');
console.log('📋 Summary:'); console.log('📋 Summary:');
console.log(' ✅ iframe.html - Master source (no changes needed)'); console.log(' Source structure:');
console.log(' sundog-dog-food-calculator.js - Generated with identical functionality'); console.log(' src/index.html - HTML structure');
console.log(' • src/css/main.css - Core styles');
console.log(' • src/css/themes.css - Theme variations');
console.log(' • src/js/config.js - Configuration');
console.log(' • src/js/calculator.js - Main logic');
console.log(''); console.log('');
console.log('🔄 Your new workflow:'); console.log(' Generated files:');
console.log(' 1. Edit iframe.html for any changes (calculations, design, layout)'); console.log(' iframe.html - Standalone calculator');
console.log('');
console.log('🔄 Your workflow:');
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 files now have the same functionality!'); console.log(' 3. Output file is regenerated!');
console.log(''); console.log('');
console.log('💡 No more maintaining two separate files!'); console.log('💡 Clean, organized structure - easy to maintain!');
} catch (error) { } catch (error) {
console.error('❌ Build failed:', error.message); console.error('❌ Build failed:', error.message);
console.error(error.stack);
process.exit(1); process.exit(1);
} }
} }
// Run build if called directly
if (require.main === module) { if (require.main === module) {
build(); build();
} }
+190
View File
@@ -0,0 +1,190 @@
# Feeding Transition Calculator — Implementation Guide (30 kg adult)
## 1) Purpose & principle
- The calculator **keeps energy intake continuous** while transitioning from kibble (current) to gently cooked (GC, target).
- Internally it uses the **kibble charts month-level precision**; externally it **communicates in GC phases** (<5 mo, 56 mo, 712 mo).
* * *
## 2) Fixed scope
- Dog profile: **30 kg adult** only.
- Supported ages: **2.0 to 12.0 months** inclusive.
If the user enters a value outside this range, **clamp** to the nearest bound and show a subtle note.
- Required configuration at runtime: **kibble energy density (kcal per 100 g)**.
If missing, show an error and do not compute.
* * *
## 3) Data you must hard-code
1. **Kibble reference points (grams/day, 30 kg column):**
(2→250), (3→330), (4→365), (6→400), (8→410), (10→410), (12→405).
2. **Interpolated monthly kibble (round to whole grams):**
- 2 mo: 250
- 3 mo: 330
- 4 mo: 365
- 5 mo: 382
- 6 mo: 400
- 7 mo: 405
- 8 mo: 410
- 9 mo: 410
- 10 mo: 410
- 11 mo: 408
- 12 mo: 405
3. **GC communication buckets and ranges (g/day):**
- **< 5 months** (covers 2.04.999… mo): **9501350**
- **56 months** (covers 5.06.999… mo): **12501550**
- **712 months** (covers 7.012.0 mo): **13001500**
**Boundary rule:** exact 5.0 and 7.0 belong to the **later** bucket (5.0 → “56”, 7.0 → “712”).
* * *
## 4) How to calculate results (conceptual, no code)
### A) Kibble grams/day at any age (2.012.0)
- Use **linear interpolation** between the nearest kibble reference points listed above.
- Round the resulting kibble grams/day to **whole grams** (or to the nearest **5 g** if the user enables a rounding toggle).
### B) GC bucket assignment
- Based on the **age**, assign the corresponding GC bucket and attach that buckets **low/high range** (g/day).
### C) Energy-matched GC grams/day (the backbone)
- Convert kibble grams/day to **kcal/day** using the **user-provided** kibble energy density (kcal/100 g).
- Convert kcal/day to **GC grams/day** using GCs energy density **115 kcal per 100 g**.
(Equivalently, GC provides **1.15 kcal per gram**.)
- Round to **whole grams** (or to **nearest 5 g** if the user toggled it).
### D) Range status
- Compare the **energy-matched GC grams/day** to the GC buckets **low/high**:
- “within” if inside \[low, high\]
- “below” if under the low
- “above” if over the high
(Do **not** alter the energy-matched amount; just flag it.)
### E) Transition schedule (blended days)
- Default to **7 days** (configurable).
- Linearly ramp daily fractions from **100% kibble / 0% GC** on Day 1 to **0% kibble / 100% GC** on the last day, in equal steps.
- Each days grams = (kibble grams/day × kibble fraction) + (GC grams/day × GC fraction).
Round after multiplying (whole grams or nearest 5 g per the user setting).
* * *
## 5) What to display (UX rules)
1. **Primary number:** “Gently cooked (energy-matched): **X g/day**”.
Directly below, show the GC bucket label and its range (e.g., “712 months: 13001500 g/day”) plus a small **status chip** (within/below/above).
2. **Context line:** “Based on your kibble energy density: **Y kcal / 100 g**” with an edit control.
3. **Age input:** accept decimals (e.g., 5.5 months).
Add tick marks at 2, 3, 4, 6, 8, 10, 12 (the original kibble points).
4. **Transition widget:** a simple 57 day table or bar chart that shows **kibble g** and **GC g** per day, plus the day total.
(Totals will typically increase during the transition because GC is less energy-dense; this is expected.)
5. **Rounding toggle:** whole grams vs nearest 5 g.
6. **Download/export:** CSV with columns:
age_months, kibble_g_per_day, kibble_kcal_per_100g, kcal_per_day, gc_energy_matched_g_per_day, gc_bucket_name, gc_low_g, gc_high_g, range_status, and per-day transition grams.
* * *
## 6) Validation & edge cases
- **Missing kibble kcal density:** block calculation and display a clear prompt to enter kcal/100 g.
- **Age outside 212 months:** clamp to the nearest bound; show a subtle informational note.
- **Energy-matched GC outside bucket range:** keep the energy-matched number; display the status chip and a short educational tooltip (growth varies; this tool prioritizes energy continuity).
- **Rounding:** perform all math in floating point; **round only for display** (and for the per-day plan after multiplying by fractions).
* * *
## 7) Acceptance checks (use these to verify)
- At **5.5 months** with **380 kcal/100 g** kibble:
- Interpolated kibble ≈ **391 g/day**.
- Kcal/day ≈ **1,486 kcal**.
- Energy-matched GC ≈ **1,292 g/day**.
- GC bucket “56 months” (1,2501,550) → **within**.
- At **8.0 months** with **380 kcal/100 g** kibble:
- Kibble ≈ **410 g/day** → ≈ **1,558 kcal/day** → GC ≈ **1,355 g/day**.
- GC bucket “712 months” (1,3001,500) → **within**.
- At **2.0 months** with **380 kcal/100 g** kibble:
- Kibble **250 g/day****950 kcal/day** → GC **≈ 826 g/day**.
- GC bucket “<5 months” (9501,350) → **below** (expected for some formulas).
* * *
## 8) Deliverables checklist
- Precise monthly interpolation (ready values above).
- Age-aware GC bucket labelling and ranges.
- Energy-matched GC grams/day with status flag.
- Configurable transition length; per-day blend table.
- Rounding control.
File diff suppressed because it is too large Load Diff
+1104 -473
View File
File diff suppressed because it is too large Load Diff
+563
View File
@@ -0,0 +1,563 @@
:root {
--bg-primary: #fdfcfe;
--bg-secondary: #ffffff;
--border-color: #e8e3ed;
--text-primary: #6f3f6d;
--text-secondary: #8f7a8e;
--accent-color: #f19a5f;
--text-label: #635870; /* For form labels, secondary UI text */
--success-color: #7fa464; /* Green for success states */
--bg-tertiary: #f8f5fa; /* Light background variant */
--error-color: #e87159; /* Error states and messages */
}
body {
margin: 0;
padding: 0;
background: transparent;
overflow: hidden; /* hide internal scrollbars; parent resizes iframe */
font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.5;
color: var(--text-primary);
}
.dog-calculator-container {
max-width: 640px;
margin: 0 auto;
padding: 24px;
box-sizing: border-box;
opacity: 0;
transition: opacity 0.3s ease;
}
.dog-calculator-container.loaded {
opacity: 1;
}
.dog-calculator-container *,
.dog-calculator-container *::before,
.dog-calculator-container *::after {
box-sizing: border-box;
}
.dog-calculator-section {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px 8px 0 0;
padding: 24px;
margin-bottom: 0;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
}
.dog-calculator-section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.dog-calculator-section h2 {
margin: 0;
color: var(--text-primary);
font-size: 1.5rem;
font-weight: 600;
}
/* Unit Switch */
.dog-calculator-unit-switch {
display: flex;
align-items: center;
gap: 12px;
}
.dog-calculator-unit-label {
font-size: 0.9rem;
font-weight: 500;
color: var(--text-label);
transition: color 0.2s ease;
}
.dog-calculator-unit-label.active {
color: var(--text-primary);
font-weight: 600;
}
.dog-calculator-switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.dog-calculator-switch input {
opacity: 0;
width: 0;
height: 0;
}
.dog-calculator-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--border-color);
transition: 0.3s;
border-radius: 24px;
}
.dog-calculator-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.dog-calculator-switch input:checked + .dog-calculator-slider {
background-color: #f19a5f;
}
.dog-calculator-switch input:checked + .dog-calculator-slider:before {
transform: translateX(24px);
}
.dog-calculator-form-group {
margin-bottom: 20px;
}
.dog-calculator-form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--text-primary);
font-size: 1rem;
}
.dog-calculator-form-group select,
.dog-calculator-form-group input[type="number"],
.dog-calculator-form-group input[type="text"] {
width: 100%;
padding: 12px 16px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 1rem;
font-family: inherit;
background-color: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.2s ease;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236f3f6d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
padding-right: 40px;
}
.dog-calculator-form-group select option {
background-color: var(--bg-secondary);
color: var(--text-primary);
}
.dog-calculator-form-group input[type="number"],
.dog-calculator-form-group input[type="text"] {
background-image: none;
padding-right: 16px;
}
.dog-calculator-form-group select:focus,
.dog-calculator-form-group input[type="number"]:focus,
.dog-calculator-form-group input[type="text"]:focus {
outline: none;
border-color: #f19a5f;
background-color: var(--bg-secondary);
box-shadow: 0 0 0 3px rgba(241, 154, 95, 0.1);
}
.dog-calculator-form-group input[readonly] {
background-color: var(--bg-tertiary);
cursor: not-allowed;
color: var(--text-label);
}
/* Kaya end-weight readonly field: compact, non-editable */
#kayaEndWeight {
width: 120px;
display: inline-block;
}
.dog-calculator-results {
background: linear-gradient(135deg, rgba(241, 154, 95, 0.08) 0%, rgba(241, 154, 95, 0.04) 100%);
border: 1px solid rgba(241, 154, 95, 0.2);
border-radius: 6px;
padding: 20px;
margin-top: 24px;
}
.dog-calculator-result-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
gap: 10px; /* Add gap between label and value */
}
.dog-calculator-result-item:last-child {
margin-bottom: 0;
}
.dog-calculator-result-label {
font-weight: 500;
color: var(--text-primary);
font-size: 0.95rem;
}
.dog-calculator-result-value {
font-weight: 600;
color: var(--text-primary);
font-size: 1.1rem;
padding: 4px 12px;
background: rgba(241, 154, 95, 0.15);
border-radius: 4px;
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
}
.dog-calculator-collapsible {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-top: none;
margin-bottom: 0;
overflow: hidden;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
}
.dog-calculator-collapsible-header {
background: var(--bg-tertiary);
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
}
.dog-calculator-collapsible-header h3 {
margin: 0;
font-size: 1.25rem;
color: var(--text-primary);
font-weight: 600;
}
.dog-calculator-collapsible-content {
display: block;
}
.dog-calculator-collapsible-inner {
padding: 24px;
}
.dog-calculator-input-group {
display: flex;
gap: 16px;
align-items: flex-end;
}
.dog-calculator-input-group .dog-calculator-form-group {
flex: 1;
margin-bottom: 0;
}
.dog-calculator-unit-select {
min-width: 120px;
}
.dog-calculator-error {
color: var(--error-color);
font-size: 0.875rem;
margin-top: 6px;
font-weight: 500;
}
.dog-calculator-hidden {
display: none;
}
/* Action Buttons */
.dog-calculator-action-buttons {
display: flex;
justify-content: center;
gap: 16px;
padding: 20px;
background: var(--bg-tertiary);
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
margin-top: -1px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
}
.dog-calculator-btn {
padding: 8px 16px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.9rem;
font-weight: 500;
font-family: inherit;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
background: white;
color: var(--text-primary);
}
.dog-calculator-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.dog-calculator-btn-share:hover {
border-color: #9f5999;
color: #9f5999;
}
/* Embed button removed */
.dog-calculator-footer {
text-align: center;
padding: 20px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 0 0 8px 8px;
border-top: none;
margin-top: -1px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
}
.dog-calculator-footer a {
color: #9f5999;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: color 0.2s ease;
}
.dog-calculator-footer a:hover {
color: #f19a5f;
text-decoration: underline;
}
/* Mobile Responsive Design */
@media (max-width: 576px) {
.dog-calculator-container {
padding: 16px;
}
.dog-calculator-section,
.dog-calculator-collapsible-inner {
padding: 20px;
}
.dog-calculator-section h2,
.dog-calculator-collapsible-header h3 {
font-size: 1.3rem;
}
.dog-calculator-section-header {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.dog-calculator-section h2 {
text-align: center;
}
.dog-calculator-unit-switch {
justify-content: center;
}
.dog-calculator-action-buttons {
flex-direction: column;
padding: 16px;
}
.dog-calculator-btn {
width: 100%;
justify-content: center;
}
.dog-calculator-input-group {
flex-direction: row;
gap: 12px;
align-items: flex-end;
}
.dog-calculator-input-group .dog-calculator-form-group {
margin-bottom: 0;
}
/* First form group takes 55%, second takes 40% with some flex */
.dog-calculator-input-group .dog-calculator-form-group:first-child {
flex: 0 0 55%;
}
.dog-calculator-input-group .dog-calculator-form-group:last-child {
flex: 1 1 40%;
min-width: 100px;
}
/* Make sure number inputs don't get too wide */
.dog-calculator-input-group input[type="number"] {
max-width: 100%;
}
/* Ensure dropdowns don't overflow their containers */
.dog-calculator-input-group select {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.dog-calculator-result-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dog-calculator-result-value {
align-self: stretch;
text-align: center;
}
.dog-calculator-collapsible-header {
padding: 16px 20px;
}
}
/* Feeding Configuration Styles */
.dog-calculator-container .dog-calculator-feeding-config {
margin-top: 20px;
padding: 16px;
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
}
.dog-calculator-container .dog-calculator-frequency-row {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.dog-calculator-container .dog-calculator-frequency-row > label {
font-weight: 500;
color: var(--text-label);
margin: 0;
}
.dog-calculator-container .dog-calculator-radio-group {
display: flex;
gap: 20px;
align-items: center;
}
.dog-calculator-container .dog-calculator-radio-group label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
color: var(--text-primary);
font-size: 0.95rem;
}
.dog-calculator-container .dog-calculator-radio-group input[type="radio"] {
cursor: pointer;
margin: 0;
}
.dog-calculator-container .dog-calculator-radio-group input[type="radio"]:checked + span {
font-weight: 600;
color: var(--accent-color);
}
.dog-calculator-container .dog-calculator-meal-input {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0 auto;
}
.dog-calculator-container .dog-calculator-meal-input span {
color: var(--text-secondary);
font-size: 0.95rem;
}
.dog-calculator-container .dog-calculator-meal-input input[type="number"] {
width: 50px;
padding: 4px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.95rem;
color: var(--text-primary);
background: var(--bg-secondary);
text-align: center;
}
.dog-calculator-container .dog-calculator-meal-input input[type="number"]:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(241, 154, 95, 0.1);
}
/* Update meal note styling */
.dog-calculator-container #mealNote {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: normal;
margin-left: 4px;
}
/* Mobile responsive adjustments for feeding config */
@media (max-width: 480px) {
.dog-calculator-meal-input {
margin-left: 0;
width: 100%;
margin-top: 8px;
}
.dog-calculator-frequency-row {
flex-direction: column;
align-items: flex-start;
}
/* Stack result items vertically on small screens */
.dog-calculator-result-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dog-calculator-result-label {
margin-right: 0;
font-size: 0.9rem;
}
.dog-calculator-result-value {
font-size: 1rem;
align-self: stretch;
text-align: center;
}
}
/* Dark theme - manual override */
+1394
View File
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
<div class="dog-calculator-container" id="dogCalculator">
<div class="dog-calculator-section">
<div class="dog-calculator-section-header">
<h2>Kayas Transition</h2>
</div>
<div class="dog-calculator-form-group">
<label for="ageMonths">Kayas age (months):</label>
<input type="number" id="ageMonths" min="2" max="12" step="0.1" placeholder="Enter age in months" aria-describedby="ageHelp">
<div id="ageClampNote" class="dog-calculator-error dog-calculator-hidden">Age adjusted to the supported 212 month range.</div>
</div>
<div class="dog-calculator-form-group">
<label for="weight" id="weightLabel">Kayas current weight (kg):</label>
<input type="number" id="weight" min="0.1" step="0.1" placeholder="Enter current weight in kg" aria-describedby="weightHelp">
<div id="weightError" class="dog-calculator-error dog-calculator-hidden">Please enter a valid weight (minimum 0.1 kg)</div>
</div>
<div class="dog-calculator-form-group">
<label for="kayaEndWeight">Kayas endweight:</label>
<input type="text" id="kayaEndWeight" value="30 kg" readonly>
</div>
</div>
<div class="dog-calculator-collapsible active" id="foodCalculator">
<div class="dog-calculator-collapsible-header">
<h3>How much should I feed?</h3>
</div>
<div class="dog-calculator-collapsible-content">
<div class="dog-calculator-collapsible-inner">
<!-- Food Sources Container -->
<div class="dog-calculator-food-sources" id="foodSources">
<!-- Initial food source will be added by JavaScript -->
</div>
<!-- Add Food Source Button -->
<button class="dog-calculator-add-food-btn" id="addFoodBtn" type="button">
<span>+</span>
<span>Add another food source</span>
</button>
<!-- Feeding Configuration -->
<div class="dog-calculator-feeding-config" id="feedingConfig" style="display: none;">
<div class="dog-calculator-frequency-row">
<label>Show amounts:</label>
<div class="dog-calculator-radio-group">
<label>
<input type="radio" name="showAs" value="daily" id="showDaily" checked>
<span>Per day</span>
</label>
<label>
<input type="radio" name="showAs" value="meal" id="showPerMeal">
<span>Per meal</span>
</label>
</div>
<div class="dog-calculator-meal-input" id="mealInputGroup" style="display: none;">
<span>×</span>
<input type="number" id="mealsPerDay" value="2" min="1" max="10">
<span>meals/day</span>
</div>
</div>
</div>
<!-- Per-Food Results -->
<div class="dog-calculator-food-results" id="foodBreakdownResults" style="display: none;">
<div id="foodBreakdownList">
<!-- Individual food breakdowns will be populated by JavaScript -->
</div>
</div>
<!-- Unit Selection Buttons -->
<div class="dog-calculator-unit-buttons" id="unitButtons" style="display: none;">
<button type="button" class="dog-calculator-unit-btn active" data-unit="g">g</button>
<button type="button" class="dog-calculator-unit-btn" data-unit="kg">kg</button>
</div>
<!-- Daily Total Results -->
<div class="dog-calculator-results" id="dailyFoodResults" style="display: none;">
<div class="dog-calculator-result-item">
<span class="dog-calculator-result-label">Total Daily Amount:</span>
<span class="dog-calculator-result-value" id="dailyFoodValue">- g/day</span>
</div>
</div>
<!-- Hidden select for compatibility -->
<select id="unit" class="dog-calculator-unit-select-hidden" aria-describedby="unitHelp">
<option value="g">grams (g)</option>
<option value="kg">kilograms (kg)</option>
</select>
<div class="dog-calculator-food-amounts-section" id="foodAmountsSection" style="display: none;">
<h4 class="dog-calculator-section-title">
Calculate amounts for
<input type="number" id="days" min="1" step="1" value="1" placeholder="1" aria-describedby="daysHelp" class="dog-calculator-inline-days">
<span id="dayLabel">day</span><span id="mealNote" style="display: none;"></span>:
</h4>
<div id="daysError" class="dog-calculator-error dog-calculator-hidden">Please enter a valid number of days (minimum 1)</div>
<div id="foodAmountsList" class="dog-calculator-food-amounts-list">
<!-- Individual food amounts will be populated here -->
</div>
<div class="dog-calculator-total-row" id="totalAmountRow">
<span class="dog-calculator-total-label">Total Amount:</span>
<span class="dog-calculator-total-value" id="totalAmountDisplay"></span>
</div>
</div>
</div>
</div>
</div>
<div class="dog-calculator-footer">
<a href="https://caninenutritionandwellness.com" target="_blank" rel="noopener noreferrer">
by caninenutritionandwellness.com
</a>
</div>
</div>
+1991
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
/**
* Configuration constants for Dog Calorie Calculator
*/
const CALCULATOR_CONFIG = {
defaultTheme: 'system',
defaultScale: 1.0,
maxFoodSources: 5,
minScale: 0.5,
maxScale: 2.0,
// Kaya fork: Fred & Felia uses MER; kibble stays chart-based (30 kg column).
// Used only when weight input is not present (back-compat).
kayaMerDefaultWeightKg: 30,
kayaMerFactorUnder4Months: 3.0,
kayaMerFactorFrom4Months: 2.0
};
File diff suppressed because it is too large Load Diff
-37
View File
@@ -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>