diff --git a/index.html b/index.html index 20f5119..74df471 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ Freight Calculator Pro +
- - + +
@@ -124,7 +167,7 @@
Freight Class
--
-
Density: 0.00 lb/ft³
+
Density: 0.00 lb/ft³
@@ -138,20 +181,58 @@ @@ -167,67 +248,159 @@ { min: 3, class: 250, desc: "Mattresses" }, { min: 2, class: 300, desc: "Cabinets" }, { min: 1, class: 400, desc: "Deer antlers" }, { min: -1, class: 500, desc: "Gold dust" } ]; + let appState = JSON.parse(localStorage.getItem('freightCalcState')) || { theme: 'light', branding: { name:'', color:'#2563eb', logo:'' }, history:[] }; + let pendingBranding = null; function saveState() { localStorage.setItem('freightCalcState', JSON.stringify(appState)); } - + // Init window.onload = () => { document.body.className = appState.theme === 'dark' ? 'dark-mode' : ''; - document.getElementById('theme-toggle').innerText = appState.theme === 'dark' ? '☀️' : '🌙'; + document.getElementById('theme-toggle').innerHTML = appState.theme === 'dark' ? '' : ''; document.documentElement.style.setProperty('--primary', appState.branding.color); - applyBranding(); + applyBrandingToPage(); renderHistory(); densityRanges.forEach(r => { - const row = document.createElement('tr'); row.id=`row-${r.class}`; + const row = document.createElement('tr'); row.id = `row-${r.class}`; row.innerHTML = `Class ${r.class}${r.min}+ lbs/ft³${r.desc}`; document.getElementById('ref-table-body').appendChild(row); }); + lucide.createIcons(); }; // Theme document.getElementById('theme-toggle').onclick = () => { document.body.classList.toggle('dark-mode'); appState.theme = document.body.classList.contains('dark-mode') ? 'dark' : 'light'; - document.getElementById('theme-toggle').innerText = appState.theme === 'dark' ? '☀️' : '🌙'; + document.getElementById('theme-toggle').innerHTML = appState.theme === 'dark' ? '' : ''; + lucide.createIcons(); saveState(); }; - // Branding - function applyBranding() { + // Apply branding visuals to page (header logo/name) + function applyBrandingToPage() { + const b = appState.branding; + const header = document.querySelector('.branding-header'); + const logo = document.getElementById('business-logo'); + const nameEl = document.getElementById('business-name-display'); + logo.src = b.logo || ''; + logo.style.display = b.logo ? 'block' : 'none'; + nameEl.textContent = b.name; + nameEl.style.display = b.name ? 'block' : 'none'; + header.style.display = (b.logo || b.name) ? 'flex' : 'none'; + } + + // Populate settings form fields from appState + function populateSettingsForm() { const b = appState.branding; document.getElementById('setting-name').value = b.name; document.getElementById('setting-color').value = b.color; - - const logo = document.getElementById('business-logo'); - const name = document.getElementById('business-name-display'); - - logo.src = b.logo; logo.style.display = b.logo ? 'block' : 'none'; - name.innerText = b.name; name.style.display = b.name ? 'block' : 'none'; + document.getElementById('color-hex-label').textContent = b.color; + const preview = document.getElementById('logo-preview'); + if (b.logo) { preview.src = b.logo; preview.style.display = 'block'; } + else { preview.src = ''; preview.style.display = 'none'; } } - document.getElementById('setting-name').oninput = (e) => { appState.branding.name = e.target.value; applyBranding(); saveState(); }; - document.getElementById('setting-color').oninput = (e) => { appState.branding.color = e.target.value; document.documentElement.style.setProperty('--primary', e.target.value); saveState(); }; + + // Live input handlers — update appState and preview but don't persist + document.getElementById('setting-name').oninput = (e) => { + appState.branding.name = e.target.value; + applyBrandingToPage(); + }; + document.getElementById('setting-color').oninput = (e) => { + appState.branding.color = e.target.value; + document.getElementById('color-hex-label').textContent = e.target.value; + document.documentElement.style.setProperty('--primary', e.target.value); + }; document.getElementById('setting-logo').onchange = (e) => { - const reader = new FileReader(); reader.onload = evt => { appState.branding.logo = evt.target.result; applyBranding(); saveState(); }; + if (!e.target.files[0]) return; + const reader = new FileReader(); + reader.onload = evt => { + appState.branding.logo = evt.target.result; + const preview = document.getElementById('logo-preview'); + preview.src = evt.target.result; + preview.style.display = 'block'; + applyBrandingToPage(); + }; reader.readAsDataURL(e.target.files[0]); }; - function clearBranding() { appState.branding = {name:'',color:'#2563eb',logo:''}; applyBranding(); saveState(); } + + // Reset branding (staged — requires Save to persist) + document.getElementById('reset-branding-btn').onclick = () => { + appState.branding = { name: '', color: '#2563eb', logo: '' }; + document.documentElement.style.setProperty('--primary', '#2563eb'); + applyBrandingToPage(); + populateSettingsForm(); + }; + + // Clear history (immediate) + document.getElementById('clear-history-btn').onclick = () => { + appState.history = []; + saveState(); + renderHistory(); + }; + + // Modal open + document.getElementById('settings-btn').onclick = () => { + pendingBranding = JSON.parse(JSON.stringify(appState.branding)); + populateSettingsForm(); + switchTab('branding'); + document.getElementById('settings-modal').classList.add('open'); + lucide.createIcons(); + }; + + // Cancel / close — revert unsaved changes + function revertAndClose() { + if (pendingBranding) { + appState.branding = pendingBranding; + document.documentElement.style.setProperty('--primary', pendingBranding.color); + applyBrandingToPage(); + pendingBranding = null; + } + document.getElementById('settings-modal').classList.remove('open'); + } + document.getElementById('modal-close-btn').onclick = revertAndClose; + document.getElementById('modal-cancel-btn').onclick = revertAndClose; + + // Save — persist to localStorage + document.getElementById('modal-save-btn').onclick = () => { + saveState(); + pendingBranding = null; + document.getElementById('settings-modal').classList.remove('open'); + }; + + // Tabs + function switchTab(t, e) { + document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); + document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active')); + document.getElementById('tab-' + t).classList.add('active'); + const btn = e ? e.target : document.querySelector(`.tab-btn[data-tab="${t}"]`); + if (btn) btn.classList.add('active'); + document.getElementById('modal-footer').style.display = t === 'branding' ? 'flex' : 'none'; + } // History function renderHistory() { - const list = document.getElementById('history-list'); list.innerHTML = ''; + const list = document.getElementById('history-list'); + list.innerHTML = ''; + if (appState.history.length === 0) { + list.innerHTML = '
No calculations yet
'; + return; + } appState.history.forEach(item => { - const div = document.createElement('div'); div.className='history-item'; - div.innerHTML = `${item.L}"x${item.W}"x${item.H}" | ${item.Weight}lbsClass ${item.Class}`; + const div = document.createElement('div'); div.className = 'history-item'; + div.innerHTML = `${item.L}"×${item.W}"×${item.H}" · ${item.Weight} lbsClass ${item.Class}`; div.onclick = () => { - document.getElementById('length').value=item.L; document.getElementById('width').value=item.W; - document.getElementById('height').value=item.H; document.getElementById('weight').value=item.Weight; - document.getElementById('density-result').innerText=item.Density; - document.getElementById('class-result').innerText=item.Class; + document.getElementById('length').value = item.L; + document.getElementById('width').value = item.W; + document.getElementById('height').value = item.H; + document.getElementById('weight').value = item.Weight; + document.getElementById('density-result').textContent = item.Density; + document.getElementById('class-result').textContent = item.Class; document.getElementById('results-area').classList.add('active'); - document.querySelectorAll('tbody tr').forEach(r=>r.classList.remove('highlight-row')); + document.querySelectorAll('tbody tr').forEach(r => r.classList.remove('highlight-row')); document.getElementById(`row-${item.Class}`).classList.add('highlight-row'); - document.getElementById('settings-modal').classList.remove('open'); + revertAndClose(); }; list.appendChild(div); }); @@ -239,33 +412,22 @@ const W = parseFloat(document.getElementById('width').value); const H = parseFloat(document.getElementById('height').value); const WT = parseFloat(document.getElementById('weight').value); - - if(!L || !W || !H || !WT) return; + if (!L || !W || !H || !WT) return; - const density = WT / ((L*W*H)/1728); + const density = WT / ((L * W * H) / 1728); let cls = 500; - for(let r of densityRanges) if(density >= r.min) { cls = r.class; break; } + for (let r of densityRanges) if (density >= r.min) { cls = r.class; break; } - appState.history.unshift({L, W, H, Weight: WT, Density: density.toFixed(2), Class: cls}); - if(appState.history.length > 20) appState.history.pop(); + appState.history.unshift({ L, W, H, Weight: WT, Density: density.toFixed(2), Class: cls }); + if (appState.history.length > 20) appState.history.pop(); saveState(); renderHistory(); - document.getElementById('density-result').innerText = density.toFixed(2); - document.getElementById('class-result').innerText = cls; + document.getElementById('density-result').textContent = density.toFixed(2); + document.getElementById('class-result').textContent = cls; document.getElementById('results-area').classList.add('active'); - - document.querySelectorAll('tbody tr').forEach(r=>r.classList.remove('highlight-row')); + document.querySelectorAll('tbody tr').forEach(r => r.classList.remove('highlight-row')); document.getElementById(`row-${cls}`).classList.add('highlight-row'); } - - // Modal - document.getElementById('settings-btn').onclick = () => document.getElementById('settings-modal').classList.add('open'); - function switchTab(t) { - document.querySelectorAll('.tab-content').forEach(e=>e.classList.remove('active')); - document.querySelectorAll('.tab-btn').forEach(e=>e.classList.remove('active')); - document.getElementById('tab-'+t).classList.add('active'); - event.target.classList.add('active'); - } - \ No newline at end of file + diff --git a/package.json b/package.json index c4acd1f..11b0199 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "main.js", "scripts": { "start": "electron .", - "build": "electron-builder --win" + "build": "electron-builder --win", + "serve": "node server.js" }, "author": "", "license": "ISC", diff --git a/server.js b/server.js new file mode 100644 index 0000000..1027bbb --- /dev/null +++ b/server.js @@ -0,0 +1,34 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +const PORT = 3010; + +const mimeTypes = { + '.html': 'text/html', + '.js': 'application/javascript', + '.css': 'text/css', + '.png': 'image/png', + '.ico': 'image/x-icon', + '.json': 'application/json', +}; + +const server = http.createServer((req, res) => { + const filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url); + const ext = path.extname(filePath); + const contentType = mimeTypes[ext] || 'application/octet-stream'; + + fs.readFile(filePath, (err, data) => { + if (err) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('404 Not Found'); + return; + } + res.writeHead(200, { 'Content-Type': contentType }); + res.end(data); + }); +}); + +server.listen(PORT, () => { + console.log(`Freight Calculator Pro running at http://localhost:${PORT}`); +});