diff --git a/build-system.js b/build-system.js new file mode 100644 index 0000000..99f86b3 --- /dev/null +++ b/build-system.js @@ -0,0 +1,307 @@ +#!/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(/