2025-06-15 21:57:27 +02:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-18 09:05:00 +02:00
|
|
|
* Dog Calculator Build System - ORGANIZED VERSION
|
2025-06-15 21:57:27 +02:00
|
|
|
*
|
2025-10-28 09:58:20 +01:00
|
|
|
* This build script generates iframe.html from organized source files
|
|
|
|
|
* in the src/ directory.
|
2025-06-15 21:57:27 +02:00
|
|
|
*
|
2025-08-18 09:05:00 +02:00
|
|
|
* 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
|
2025-08-17 21:20:05 +02:00
|
|
|
*
|
|
|
|
|
* Output files:
|
|
|
|
|
* - iframe.html - Standalone calculator page
|
2025-06-15 21:57:27 +02:00
|
|
|
*
|
2025-08-17 21:20:05 +02:00
|
|
|
* Usage: node build.js
|
2025-06-15 21:57:27 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
2025-08-18 09:05:00 +02:00
|
|
|
console.log('🎯 Dog Calculator Build System - ORGANIZED VERSION');
|
2025-06-15 21:57:27 +02:00
|
|
|
console.log('');
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-18 09:05:00 +02:00
|
|
|
* Read organized components from src directory
|
2025-06-15 21:57:27 +02:00
|
|
|
*/
|
2025-08-17 21:20:05 +02:00
|
|
|
function readSourceComponents() {
|
|
|
|
|
const srcDir = 'src';
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
// Check if src directory exists
|
|
|
|
|
if (!fs.existsSync(srcDir)) {
|
2025-08-18 09:05:00 +02:00
|
|
|
throw new Error('src directory not found');
|
2025-08-17 21:20:05 +02:00
|
|
|
}
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-08-18 09:05:00 +02:00
|
|
|
// 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');
|
2025-08-17 21:20:05 +02:00
|
|
|
}
|
2025-08-18 09:05:00 +02:00
|
|
|
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;
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
// Read HTML template
|
2025-08-18 09:05:00 +02:00
|
|
|
const htmlPath = path.join(srcDir, 'index.html');
|
2025-08-17 21:20:05 +02:00
|
|
|
if (!fs.existsSync(htmlPath)) {
|
2025-08-18 09:05:00 +02:00
|
|
|
throw new Error('src/index.html not found');
|
2025-08-17 21:20:05 +02:00
|
|
|
}
|
|
|
|
|
const html = fs.readFileSync(htmlPath, 'utf8').trim();
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-08-18 09:05:00 +02:00
|
|
|
// Read JavaScript files
|
|
|
|
|
const configPath = path.join(srcDir, 'js', 'config.js');
|
|
|
|
|
const calculatorPath = path.join(srcDir, 'js', 'calculator.js');
|
|
|
|
|
if (!fs.existsSync(configPath)) {
|
|
|
|
|
throw new Error('src/js/config.js not found');
|
|
|
|
|
}
|
|
|
|
|
if (!fs.existsSync(calculatorPath)) {
|
|
|
|
|
throw new Error('src/js/calculator.js not found');
|
2025-08-17 21:20:05 +02:00
|
|
|
}
|
2025-08-18 09:05:00 +02:00
|
|
|
const config = fs.readFileSync(configPath, 'utf8').trim();
|
|
|
|
|
const calculator = fs.readFileSync(calculatorPath, 'utf8').trim();
|
|
|
|
|
const js = config + '\n\n' + calculator;
|
2025-06-15 21:57:27 +02:00
|
|
|
|
|
|
|
|
return { css, html, js };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Backup existing files
|
|
|
|
|
*/
|
|
|
|
|
function backupFiles() {
|
2025-08-18 09:21:54 +02:00
|
|
|
const backupDir = 'backup';
|
|
|
|
|
|
|
|
|
|
// Create backup directory if it doesn't exist
|
|
|
|
|
if (!fs.existsSync(backupDir)) {
|
|
|
|
|
fs.mkdirSync(backupDir);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-15 21:57:27 +02:00
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
2025-10-28 09:58:20 +01:00
|
|
|
const filesToBackup = ['iframe.html'];
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
filesToBackup.forEach(file => {
|
|
|
|
|
if (fs.existsSync(file)) {
|
2025-08-18 09:21:54 +02:00
|
|
|
const backupName = path.join(backupDir, `${file}.backup-${timestamp}`);
|
2025-08-17 21:20:05 +02:00
|
|
|
fs.copyFileSync(file, backupName);
|
2025-08-18 09:21:54 +02:00
|
|
|
console.log(` 📦 Backed up ${file}`);
|
2025-08-17 21:20:05 +02:00
|
|
|
}
|
|
|
|
|
});
|
2025-06-15 21:57:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-17 21:20:05 +02:00
|
|
|
* Generate iframe.html from modular components
|
2025-06-15 21:57:27 +02:00
|
|
|
*/
|
|
|
|
|
function generateIframe(css, html, js) {
|
|
|
|
|
const content = `<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
<title>Dog Calorie Calculator - Canine Nutrition and Wellness</title>
|
|
|
|
|
<style>
|
2025-08-17 21:20:05 +02:00
|
|
|
/* Sundog Dog Food Calorie Calculator Styles */
|
|
|
|
|
|
|
|
|
|
/* CSS Variables for theming */
|
2025-06-15 21:57:27 +02:00
|
|
|
${css}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
${html}
|
|
|
|
|
<script>
|
|
|
|
|
${js}
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>`;
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-17 21:20:05 +02:00
|
|
|
* Create production-ready widget JavaScript
|
2025-06-15 21:57:27 +02:00
|
|
|
*/
|
|
|
|
|
function createWidgetJS(css, html, js) {
|
2025-08-17 21:20:05 +02:00
|
|
|
// Transform the JavaScript from calculator.js to work as a widget
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-06-19 10:50:09 +02:00
|
|
|
// Replace the iframe's DOMContentLoaded listener with widget initialization
|
|
|
|
|
let transformedJS = js
|
|
|
|
|
// Replace the iframe class name with widget class name
|
|
|
|
|
.replace(/class DogCalorieCalculator/g, 'class DogCalorieCalculatorWidget')
|
|
|
|
|
// Replace document.getElementById with scoped selectors within the widget
|
|
|
|
|
.replace(/document\.getElementById\('([^']+)'\)/g, 'this.container.querySelector(\'#$1\')')
|
|
|
|
|
// Replace direct document queries in the class with container-scoped queries
|
|
|
|
|
.replace(/document\.querySelector\(/g, 'this.container.querySelector(')
|
|
|
|
|
.replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(')
|
|
|
|
|
// Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper
|
|
|
|
|
.replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '')
|
2025-08-18 14:54:25 +02:00
|
|
|
// Remove theme/scale assignments that would override widget options
|
|
|
|
|
.replace(/this\.theme = this\.getThemeFromURL\(\) \|\| CALCULATOR_CONFIG\.defaultTheme;/g, '')
|
|
|
|
|
.replace(/this\.scale = this\.getScaleFromURL\(\) \|\| CALCULATOR_CONFIG\.defaultScale;/g, '')
|
2025-06-19 10:50:09 +02:00
|
|
|
// Add widget initialization methods
|
|
|
|
|
.replace(/constructor\(\) \{/, `constructor(container, options = {}) {
|
2025-06-15 21:57:27 +02:00
|
|
|
this.container = container;
|
|
|
|
|
this.options = {
|
|
|
|
|
theme: options.theme || this.getThemeFromURL() || 'system',
|
|
|
|
|
scale: options.scale || this.getScaleFromURL() || 1.0,
|
|
|
|
|
...options
|
2025-06-19 11:11:33 +02:00
|
|
|
};
|
|
|
|
|
this.theme = this.options.theme;
|
|
|
|
|
this.scale = this.options.scale;`)
|
2025-06-19 10:50:09 +02:00
|
|
|
// Replace the init() method to inject HTML and apply widget settings
|
|
|
|
|
.replace(/init\(\) \{/, `init() {
|
|
|
|
|
// Inject the calculator HTML into the container
|
|
|
|
|
this.container.innerHTML = \`${html}\`;
|
2025-06-15 21:57:27 +02:00
|
|
|
|
|
|
|
|
// Apply widget-specific settings
|
|
|
|
|
this.applyTheme();
|
|
|
|
|
this.applyScale();
|
|
|
|
|
|
2025-06-19 11:11:33 +02:00
|
|
|
// Continue with original init logic`)
|
|
|
|
|
// Remove duplicate applyTheme/applyScale calls
|
|
|
|
|
.replace(/this\.applyTheme\(\);\s*\n\s*this\.applyScale\(\);\s*\n\s*this\.bindEvents/g, 'this.bindEvents');
|
2025-06-19 10:50:09 +02:00
|
|
|
|
|
|
|
|
// Add widget-specific methods before the class closing brace
|
|
|
|
|
transformedJS = transformedJS.replace(/(\s+)(\}\s*$)/, `$1
|
2025-06-15 21:57:27 +02:00
|
|
|
getThemeFromURL() {
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
|
const theme = urlParams.get('theme');
|
|
|
|
|
return ['light', 'dark', 'system'].includes(theme) ? theme : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getScaleFromURL() {
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
|
const scale = parseFloat(urlParams.get('scale'));
|
|
|
|
|
return (!isNaN(scale) && scale >= 0.5 && scale <= 2.0) ? scale : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyTheme() {
|
2025-06-19 11:11:33 +02:00
|
|
|
const calculatorContainer = this.container.querySelector('#dogCalculator');
|
|
|
|
|
if (calculatorContainer) {
|
|
|
|
|
// Remove existing theme classes
|
|
|
|
|
calculatorContainer.classList.remove('theme-light', 'theme-dark', 'theme-system');
|
|
|
|
|
// Add the selected theme class
|
2025-08-18 14:54:25 +02:00
|
|
|
if (['light', 'dark', 'system'].includes(this.theme)) {
|
|
|
|
|
calculatorContainer.classList.add('theme-' + this.theme);
|
2025-06-19 11:11:33 +02:00
|
|
|
}
|
2025-06-15 21:57:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyScale() {
|
2025-08-18 14:54:25 +02:00
|
|
|
const scale = Math.max(0.5, Math.min(2.0, this.scale));
|
2025-06-15 21:57:27 +02:00
|
|
|
if (scale !== 1.0) {
|
|
|
|
|
this.container.style.transform = \`scale(\${scale})\`;
|
|
|
|
|
this.container.style.transformOrigin = 'top left';
|
|
|
|
|
}
|
2025-06-19 10:50:09 +02:00
|
|
|
}$2`);
|
|
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
// Add CSS variables to widget CSS
|
|
|
|
|
const widgetCSS = `/* Sundog Dog Food Calorie Calculator Styles */
|
|
|
|
|
|
|
|
|
|
/* CSS Variables for theming */
|
|
|
|
|
${css}`;
|
|
|
|
|
|
2025-06-19 10:50:09 +02:00
|
|
|
const widgetCode = `/**
|
|
|
|
|
* Dog Calorie Calculator Widget
|
|
|
|
|
* Embeddable JavaScript widget for websites
|
|
|
|
|
*
|
2025-08-17 21:20:05 +02:00
|
|
|
* THIS CODE IS AUTO-GENERATED FROM src/ FILES - DO NOT EDIT MANUALLY
|
|
|
|
|
* Edit files in src/ directory and run 'node build.js' to update
|
2025-06-19 10:50:09 +02:00
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* <script src="sundog-dog-food-calculator.js"></script>
|
|
|
|
|
* <div id="dog-calorie-calculator"></div>
|
|
|
|
|
*
|
|
|
|
|
* Or with options:
|
|
|
|
|
* <div id="dog-calorie-calculator" data-theme="dark" data-scale="1.2"></div>
|
|
|
|
|
*
|
|
|
|
|
* By Canine Nutrition and Wellness
|
|
|
|
|
* https://caninenutritionandwellness.com
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
// Inject widget styles
|
2025-08-17 21:20:05 +02:00
|
|
|
const CSS_STYLES = \`${widgetCSS}\`;
|
2025-06-19 10:50:09 +02:00
|
|
|
|
|
|
|
|
function injectStyles() {
|
|
|
|
|
if (document.getElementById('dog-calculator-styles')) return;
|
|
|
|
|
|
|
|
|
|
const style = document.createElement('style');
|
|
|
|
|
style.id = 'dog-calculator-styles';
|
|
|
|
|
style.textContent = CSS_STYLES;
|
|
|
|
|
document.head.appendChild(style);
|
2025-06-15 21:57:27 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
// JavaScript from src/calculator.js (transformed for widget use)
|
2025-06-19 10:50:09 +02:00
|
|
|
${transformedJS}
|
|
|
|
|
|
2025-06-15 21:57:27 +02:00
|
|
|
// Auto-initialize widgets on page load
|
|
|
|
|
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 DogCalorieCalculatorWidget(container, options);
|
|
|
|
|
container.dataset.initialized = 'true';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize when DOM is ready
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeWidget);
|
|
|
|
|
} else {
|
|
|
|
|
initializeWidget();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Export for manual initialization
|
|
|
|
|
window.DogCalorieCalculatorWidget = DogCalorieCalculatorWidget;
|
|
|
|
|
|
|
|
|
|
})();`;
|
|
|
|
|
|
|
|
|
|
return widgetCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Main build function
|
|
|
|
|
*/
|
|
|
|
|
function build() {
|
|
|
|
|
try {
|
2025-08-18 09:05:00 +02:00
|
|
|
console.log('📖 Reading organized components from src/ directory...');
|
2025-08-17 21:20:05 +02:00
|
|
|
const { css, html, js } = readSourceComponents();
|
2025-08-18 09:05:00 +02:00
|
|
|
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)');
|
2025-06-15 21:57:27 +02:00
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
console.log('');
|
2025-06-15 21:57:27 +02:00
|
|
|
console.log('📦 Backing up existing files...');
|
|
|
|
|
backupFiles();
|
|
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
console.log('');
|
|
|
|
|
console.log('🏗️ Building output files...');
|
|
|
|
|
|
|
|
|
|
// Generate iframe.html
|
|
|
|
|
const iframeContent = generateIframe(css, html, js);
|
|
|
|
|
fs.writeFileSync('iframe.html', iframeContent);
|
|
|
|
|
console.log(' ✅ Generated iframe.html');
|
|
|
|
|
|
2025-10-28 09:58:20 +01:00
|
|
|
// Embeddable widget generation removed (embedding no longer supported)
|
2025-06-15 21:57:27 +02:00
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
|
console.log('🎉 Build completed successfully!');
|
|
|
|
|
console.log('');
|
|
|
|
|
console.log('📋 Summary:');
|
2025-08-18 09:05:00 +02:00
|
|
|
console.log(' Source structure:');
|
|
|
|
|
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');
|
2025-08-17 21:20:05 +02:00
|
|
|
console.log('');
|
|
|
|
|
console.log(' Generated files:');
|
|
|
|
|
console.log(' • iframe.html - Standalone calculator');
|
2025-06-15 21:57:27 +02:00
|
|
|
console.log('');
|
2025-08-18 09:05:00 +02:00
|
|
|
console.log('🔄 Your workflow:');
|
|
|
|
|
console.log(' 1. Edit organized files in src/');
|
2025-06-15 21:57:27 +02:00
|
|
|
console.log(' 2. Run: node build.js');
|
2025-10-28 09:58:20 +01:00
|
|
|
console.log(' 3. Output file is regenerated!');
|
2025-06-15 21:57:27 +02:00
|
|
|
console.log('');
|
2025-08-18 09:05:00 +02:00
|
|
|
console.log('💡 Clean, organized structure - easy to maintain!');
|
2025-06-15 21:57:27 +02:00
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('❌ Build failed:', error.message);
|
2025-08-17 21:20:05 +02:00
|
|
|
console.error(error.stack);
|
2025-06-15 21:57:27 +02:00
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 21:20:05 +02:00
|
|
|
// Run build if called directly
|
2025-06-15 21:57:27 +02:00
|
|
|
if (require.main === module) {
|
|
|
|
|
build();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 09:58:20 +01:00
|
|
|
module.exports = { build };
|