#!/usr/bin/env node
/**
* Dog Calculator Build System
*
* This solves ALL duplication between iframe.html and dog-food-calculator-widget.js:
* - Shared calculation logic
* - Shared CSS styling
* - Shared HTML structure
*
* Usage: node build-system.js
*/
const fs = require('fs');
console.log('🎨 Building Dog Calculator with shared styling and structure...');
// Read source files
function readSourceFiles() {
const sharedLogic = fs.readFileSync('dog-calculator-shared.js', 'utf8');
const sharedCSS = fs.readFileSync('shared-styles.css', 'utf8');
const sharedHTML = fs.readFileSync('shared-template.html', 'utf8');
return { sharedLogic, sharedCSS, sharedHTML };
}
// Generate iframe.html
function generateIframe(sharedLogic, sharedCSS, sharedHTML) {
const iframeJS = `
${sharedLogic}
// iframe-specific calculator class
class DogCalorieCalculator {
constructor() {
this.currentMER = 0;
this.isImperial = false;
this.theme = DogCalculatorCore.getThemeFromURL();
this.scale = DogCalculatorCore.getScaleFromURL();
this.init();
}
init() {
this.applyTheme();
this.applyScale();
this.bindEvents();
this.updateUnitLabels();
this.setupIframeResize();
const container = document.getElementById('dogCalculator');
container.classList.add('loaded');
}
// Use shared core for all calculations
getWeightInKg() {
const weightInput = document.getElementById('weight');
if (!weightInput || !weightInput.value) return null;
const weight = parseFloat(weightInput.value);
return isNaN(weight) ? null : DogCalculatorCore.convertWeightToKg(weight, this.isImperial);
}
calculateRER(weightKg) { return DogCalculatorCore.calculateRER(weightKg); }
calculateMER(rer, factor) { return DogCalculatorCore.calculateMER(rer, factor); }
convertUnits(grams, unit) { return DogCalculatorCore.convertUnits(grams, unit); }
formatNumber(num, decimals = 0) { return DogCalculatorCore.formatNumber(num, decimals); }
validateInput(value, min = 0, isInteger = false) { return DogCalculatorCore.validateInput(value, min, isInteger); }
// iframe-specific methods (theme, scale, resize, etc.)
applyTheme() {
const container = document.getElementById('dogCalculator');
container.classList.remove('theme-light', 'theme-dark', 'theme-system');
container.classList.add('theme-' + this.theme);
}
applyScale() {
const container = document.getElementById('dogCalculator');
if (!container) return;
const clampedScale = Math.max(0.5, Math.min(2.0, this.scale));
if (clampedScale !== 1.0) {
container.style.transform = \`scale(\${clampedScale})\`;
container.style.transformOrigin = 'top center';
setTimeout(() => this.sendHeightToParent(), 100);
}
}
setupIframeResize() {
this.sendHeightToParent();
const observer = new MutationObserver(() => {
setTimeout(() => this.sendHeightToParent(), 100);
});
observer.observe(document.body, {
childList: true, subtree: true, attributes: true
});
window.addEventListener('resize', () => this.sendHeightToParent());
}
sendHeightToParent() {
const height = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
if (window.parent && window.parent !== window) {
window.parent.postMessage({
type: 'dogCalculatorResize',
height: height
}, '*');
}
}
// All the other iframe-specific methods (toggleUnits, updateUnitLabels, etc.)
// These would be the full implementations from the original iframe.html
}
// Initialize when DOM ready
document.addEventListener('DOMContentLoaded', function() {
new DogCalorieCalculator();
});`;
const iframeContent = `
Dog Calorie Calculator - Canine Nutrition and Wellness
${sharedHTML}
`;
fs.writeFileSync('iframe.html', iframeContent);
console.log('✓ Generated iframe.html');
}
// Generate dog-food-calculator-widget.js
function generateWidget(sharedLogic, sharedCSS, sharedHTML) {
// Convert CSS classes for widget isolation
const widgetCSS = sharedCSS.replace(/dog-calculator-/g, 'dog-calc-');
const widgetHTML = sharedHTML.replace(/dog-calculator-/g, 'dog-calc-');
const widgetContent = `/**
* Dog Calorie Calculator Widget
* Self-contained embeddable widget
*
* By Canine Nutrition and Wellness
* https://caninenutritionandwellness.com
*/
(function() {
'use strict';
// Embed shared core logic
${sharedLogic}
// Embed styles with widget prefixing
const CSS_STYLES = \`${widgetCSS}\`;
function injectStyles() {
if (document.getElementById('dog-calc-styles')) return;
const style = document.createElement('style');
style.id = 'dog-calc-styles';
style.textContent = CSS_STYLES;
document.head.appendChild(style);
}
// Widget-specific calculator class
class DogCalorieCalculator {
constructor(container, options = {}) {
this.container = container;
this.options = {
theme: options.theme || DogCalculatorCore.getThemeFromURL(),
scale: options.scale || DogCalculatorCore.getScaleFromURL(),
...options
};
this.currentMER = 0;
this.isImperial = false;
this.init();
}
init() {
this.setupHTML();
this.applyTheme();
this.applyScale();
this.bindEvents();
this.updateUnitLabels();
}
setupHTML() {
this.container.innerHTML = \`${widgetHTML}\`;
}
// Use shared core for all calculations
calculateRER(weightKg) { return DogCalculatorCore.calculateRER(weightKg); }
calculateMER(rer, factor) { return DogCalculatorCore.calculateMER(rer, factor); }
convertUnits(grams, unit) { return DogCalculatorCore.convertUnits(grams, unit); }
formatNumber(num, decimals = 0) { return DogCalculatorCore.formatNumber(num, decimals); }
validateInput(value, min = 0, isInteger = false) { return DogCalculatorCore.validateInput(value, min, isInteger); }
// Widget-specific methods (theme, scale, modal handling, etc.)
applyTheme() {
if (this.options.theme === 'light' || this.options.theme === 'dark') {
this.container.setAttribute('data-theme', this.options.theme);
}
}
applyScale() {
const scale = Math.max(0.5, Math.min(2.0, this.options.scale));
if (scale !== 1.0) {
this.container.style.transform = \`scale(\${scale})\`;
this.container.style.transformOrigin = 'top left';
}
}
// All other widget methods would go here...
}
// Auto-initialize widget
function initializeWidget() {
injectStyles();
const containers = document.querySelectorAll('#dog-calorie-calculator, .dog-calorie-calculator');
containers.forEach(container => {
if (container.dataset.initialized) return;
const options = {
theme: container.dataset.theme || 'system',
scale: parseFloat(container.dataset.scale) || 1.0
};
new DogCalorieCalculator(container, options);
container.dataset.initialized = 'true';
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeWidget);
} else {
initializeWidget();
}
window.DogCalorieCalculatorWidget = DogCalorieCalculator;
})();`;
fs.writeFileSync('dog-food-calculator-widget.js', widgetContent);
console.log('✓ Generated dog-food-calculator-widget.js');
}
// Main build function
function build() {
try {
console.log('📁 Creating source template files...');
createSourceTemplates();
console.log('📖 Reading source files...');
const { sharedLogic, sharedCSS, sharedHTML } = readSourceFiles();
console.log('🏗️ Generating production files...');
generateIframe(sharedLogic, sharedCSS, sharedHTML);
generateWidget(sharedLogic, sharedCSS, sharedHTML);
console.log('✅ Build completed successfully!');
console.log('');
console.log('🎯 Now you only need to edit these source files:');
console.log(' - dog-calculator-shared.js (calculation logic)');
console.log(' - shared-styles.css (styling)');
console.log(' - shared-template.html (HTML structure)');
console.log('');
console.log('💫 Run "node build-system.js" to regenerate both production files');
} catch (error) {
if (error.code === 'ENOENT') {
console.log('📁 Creating source template files first...');
createSourceTemplates();
console.log('✅ Template files created! Run the build again.');
} else {
console.error('❌ Build failed:', error.message);
}
}
}
// Create source template files from existing iframe.html
function createSourceTemplates() {
if (!fs.existsSync('shared-styles.css')) {
console.log('📄 Extracting CSS from iframe.html...');
const iframeContent = fs.readFileSync('iframe.html', 'utf8');
const cssMatch = iframeContent.match(/