#!/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(/