Initial commit — FreightCalculatorPro Electron app
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Freight Calculator Pro</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #2563eb; --primary-hover: #1d4ed8; --bg-color: #f1f5f9;
|
||||
--card-bg: #ffffff; --text-main: #1e293b; --text-muted: #64748b;
|
||||
--border-radius: 12px; --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--input-bg: #ffffff; --input-border: #e2e8f0;
|
||||
--input-focus-ring: rgba(37, 99, 235, 0.1); --border-color: #e2e8f0;
|
||||
--table-header-bg: #f8fafc; --table-hover: #f1f5f9;
|
||||
--highlight-bg: #dbeafe; --highlight-text: #1e3a8a;
|
||||
--modal-bg: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
body.dark-mode {
|
||||
--primary: #60a5fa; --primary-hover: #3b82f6; --bg-color: #0f172a;
|
||||
--card-bg: #1e293b; --text-main: #f1f5f9; --text-muted: #94a3b8;
|
||||
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.5); --input-bg: #0f172a;
|
||||
--input-border: #334155; --input-focus-ring: rgba(96, 165, 250, 0.2);
|
||||
--border-color: #334155; --table-header-bg: #0f172a; --table-hover: #334155;
|
||||
--highlight-bg: #1e3a8a; --highlight-text: #ffffff;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', sans-serif; transition: background-color 0.3s, color 0.3s; }
|
||||
body { background-color: var(--bg-color); color: var(--text-main); display: flex; justify-content: center; min-height: 100vh; padding: 20px; }
|
||||
.container { width: 100%; max-width: 900px; display: flex; flex-direction: column; }
|
||||
|
||||
/* Layout */
|
||||
.branding-header { display: flex; align-items: center; gap: 15px; margin-bottom: 10px; min-height: 60px; }
|
||||
#business-logo { max-height: 60px; max-width: 200px; object-fit: contain; display: none; }
|
||||
#business-name-display { font-size: 1.5rem; font-weight: 700; color: var(--primary); display: none; }
|
||||
.main-title { text-align: center; margin-bottom: 24px; }
|
||||
h1 { font-size: 1.8rem; margin-bottom: 4px; }
|
||||
.content-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 24px; }
|
||||
|
||||
/* Controls */
|
||||
.top-controls { position: absolute; top: 20px; right: 20px; display: flex; gap: 12px; }
|
||||
.icon-btn { background: var(--card-bg); color: var(--text-main); border: 1px solid var(--input-border); width: 40px; height: 40px; border-radius: 50%; cursor: pointer; font-size: 1.1rem; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow); }
|
||||
|
||||
/* Cards & Inputs */
|
||||
.card { background: var(--card-bg); border-radius: var(--border-radius); box-shadow: var(--shadow); padding: 32px; display: flex; flex-direction: column; gap: 20px; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 8px; }
|
||||
label { font-weight: 600; font-size: 0.9rem; color: var(--text-muted); }
|
||||
.input-wrapper { position: relative; display: flex; align-items: center; }
|
||||
input[type="number"], input[type="text"] { width: 100%; padding: 12px; border: 2px solid var(--input-border); border-radius: 8px; font-size: 1rem; background: var(--input-bg); color: var(--text-main); }
|
||||
input:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 3px var(--input-focus-ring); }
|
||||
.unit { position: absolute; right: 16px; color: var(--text-muted); font-size: 0.85rem; pointer-events: none; }
|
||||
.dimensions-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
|
||||
button.calculate-btn { background: var(--primary); color: white; border: none; padding: 16px; border-radius: 8px; font-weight: 600; cursor: pointer; margin-top: 10px; }
|
||||
button.calculate-btn:hover { background: var(--primary-hover); }
|
||||
|
||||
/* Results */
|
||||
.results-container { display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; height: 100%; opacity: 0.5; }
|
||||
.results-container.active { opacity: 1; }
|
||||
.result-box { background: var(--bg-color); border: 2px dashed var(--border-color); border-radius: var(--border-radius); padding: 40px; width: 100%; }
|
||||
.result-value { font-size: 4rem; font-weight: 800; color: var(--primary); margin: 16px 0; }
|
||||
.result-label { font-size: 1.1rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; }
|
||||
|
||||
/* Table */
|
||||
.reference-section { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 12px; border-bottom: 1px solid var(--border-color); text-align: left; }
|
||||
th { background: var(--table-header-bg); color: var(--text-muted); }
|
||||
.highlight-row { background: var(--highlight-bg) !important; font-weight: bold; color: var(--highlight-text); }
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay { position: fixed; inset: 0; background: var(--modal-bg); z-index: 2000; display: none; justify-content: center; align-items: center; }
|
||||
.modal-overlay.open { display: flex; }
|
||||
.modal-content { background: var(--card-bg); width: 90%; max-width: 600px; border-radius: var(--border-radius); box-shadow: 0 10px 25px rgba(0,0,0,0.2); display: flex; flex-direction: column; max-height: 85vh; }
|
||||
.modal-header { padding: 20px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; }
|
||||
.modal-body { padding: 20px; overflow-y: auto; }
|
||||
.tabs { display: flex; gap: 10px; margin-bottom: 20px; border-bottom: 1px solid var(--border-color); }
|
||||
.tab-btn { background: none; border: none; padding: 10px 20px; cursor: pointer; color: var(--text-muted); border-bottom: 3px solid transparent; }
|
||||
.tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 600; }
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
.settings-group { margin-bottom: 20px; }
|
||||
.btn-secondary { background: transparent; border: 1px solid var(--border-color); color: var(--text-muted); padding: 8px 16px; border-radius: 6px; cursor: pointer; }
|
||||
.btn-danger { color: #ef4444; border-color: #ef4444; }
|
||||
|
||||
/* History */
|
||||
.history-list { list-style: none; max-height: 300px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 8px; }
|
||||
.history-item { padding: 12px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; cursor: pointer; }
|
||||
.history-item:hover { background: var(--table-hover); }
|
||||
.history-class { font-weight: bold; color: var(--primary); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-controls">
|
||||
<button id="theme-toggle" class="icon-btn">🌙</button>
|
||||
<button id="settings-btn" class="icon-btn">⚙️</button>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="branding-header">
|
||||
<img id="business-logo" src="" alt="Logo">
|
||||
<div id="business-name-display"></div>
|
||||
</div>
|
||||
|
||||
<div class="main-title">
|
||||
<h1>Freight Class Calculator</h1>
|
||||
</div>
|
||||
|
||||
<div class="content-grid">
|
||||
<div class="card">
|
||||
<div class="form-group">
|
||||
<label>Dimensions</label>
|
||||
<div class="dimensions-row">
|
||||
<div class="input-wrapper"><input type="number" id="length" placeholder="L" min="0" step="0.1"><span class="unit">in</span></div>
|
||||
<div class="input-wrapper"><input type="number" id="width" placeholder="W" min="0" step="0.1"><span class="unit">in</span></div>
|
||||
<div class="input-wrapper"><input type="number" id="height" placeholder="H" min="0" step="0.1"><span class="unit">in</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Total Weight</label>
|
||||
<div class="input-wrapper"><input type="number" id="weight" placeholder="0" min="0" step="0.1"><span class="unit">lbs</span></div>
|
||||
</div>
|
||||
<button class="calculate-btn" onclick="calculateFreightClass()">Calculate Class</button>
|
||||
</div>
|
||||
|
||||
<div class="card results-container" id="results-area">
|
||||
<div class="result-box">
|
||||
<div class="result-label">Freight Class</div>
|
||||
<div class="result-value" id="class-result">--</div>
|
||||
<div style="margin-bottom:20px; color:var(--text-muted)">Density: <span id="density-result">0.00</span> lb/ft³</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card reference-section">
|
||||
<h3>Density Reference</h3>
|
||||
<table><thead><tr><th>Class</th><th>Density</th><th>Items</th></tr></thead><tbody id="ref-table-body"></tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div id="settings-modal" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header"><h2>Settings</h2><button class="btn-secondary" onclick="document.getElementById('settings-modal').classList.remove('open')">X</button></div>
|
||||
<div class="modal-body">
|
||||
<div class="tabs"><button class="tab-btn active" onclick="switchTab('branding')">Branding</button><button class="tab-btn" onclick="switchTab('history')">History</button></div>
|
||||
<div id="tab-branding" class="tab-content active">
|
||||
<div class="settings-group"><label>Business Name</label><input type="text" id="setting-name"></div>
|
||||
<div class="settings-group"><label>Color</label><input type="color" id="setting-color"></div>
|
||||
<div class="settings-group"><label>Logo</label><input type="file" id="setting-logo" accept="image/*"></div>
|
||||
<button class="btn-secondary" onclick="clearBranding()">Reset</button>
|
||||
</div>
|
||||
<div id="tab-history" class="tab-content">
|
||||
<button class="btn-secondary btn-danger" onclick="appState.history=[]; saveState(); renderHistory();">Clear History</button>
|
||||
<div id="history-list" class="history-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const densityRanges = [
|
||||
{ min: 50, class: 50, desc: "Heavy machinery" }, { min: 35, class: 55, desc: "Bricks, cement" },
|
||||
{ min: 30, class: 60, desc: "Car parts" }, { min: 22.5, class: 65, desc: "Car parts, books" },
|
||||
{ min: 15, class: 70, desc: "Food items" }, { min: 13.5, class: 77.5, desc: "Tires" },
|
||||
{ min: 12, class: 85, desc: "Crated machinery" }, { min: 10.5, class: 92.5, desc: "Computers" },
|
||||
{ min: 9, class: 100, desc: "Boat motors" }, { min: 8, class: 110, desc: "TVs" },
|
||||
{ min: 7, class: 125, desc: "Furniture" }, { min: 6, class: 150, desc: "Sheet metal" },
|
||||
{ min: 5, class: 175, desc: "Clothing" }, { min: 4, class: 200, desc: "Aircraft parts" },
|
||||
{ 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:[] };
|
||||
|
||||
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.documentElement.style.setProperty('--primary', appState.branding.color);
|
||||
applyBranding();
|
||||
renderHistory();
|
||||
densityRanges.forEach(r => {
|
||||
const row = document.createElement('tr'); row.id=`row-${r.class}`;
|
||||
row.innerHTML = `<td>Class ${r.class}</td><td>${r.min}+ lbs/ft³</td><td>${r.desc}</td>`;
|
||||
document.getElementById('ref-table-body').appendChild(row);
|
||||
});
|
||||
};
|
||||
|
||||
// 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' ? '☀️' : '🌙';
|
||||
saveState();
|
||||
};
|
||||
|
||||
// Branding
|
||||
function applyBranding() {
|
||||
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('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(); };
|
||||
document.getElementById('setting-logo').onchange = (e) => {
|
||||
const reader = new FileReader(); reader.onload = evt => { appState.branding.logo = evt.target.result; applyBranding(); saveState(); };
|
||||
reader.readAsDataURL(e.target.files[0]);
|
||||
};
|
||||
function clearBranding() { appState.branding = {name:'',color:'#2563eb',logo:''}; applyBranding(); saveState(); }
|
||||
|
||||
// History
|
||||
function renderHistory() {
|
||||
const list = document.getElementById('history-list'); list.innerHTML = '';
|
||||
appState.history.forEach(item => {
|
||||
const div = document.createElement('div'); div.className='history-item';
|
||||
div.innerHTML = `<span>${item.L}"x${item.W}"x${item.H}" | ${item.Weight}lbs</span><span class="history-class">Class ${item.Class}</span>`;
|
||||
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('results-area').classList.add('active');
|
||||
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');
|
||||
};
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// Calc
|
||||
function calculateFreightClass() {
|
||||
const L = parseFloat(document.getElementById('length').value);
|
||||
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;
|
||||
|
||||
const density = WT / ((L*W*H)/1728);
|
||||
let cls = 500;
|
||||
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();
|
||||
saveState(); renderHistory();
|
||||
|
||||
document.getElementById('density-result').innerText = density.toFixed(2);
|
||||
document.getElementById('class-result').innerText = cls;
|
||||
document.getElementById('results-area').classList.add('active');
|
||||
|
||||
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');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,34 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 950,
|
||||
height: 950,
|
||||
resizable: true,
|
||||
minWidth: 800,
|
||||
minHeight: 700,
|
||||
icon: path.join(__dirname, 'icon.png'), // Window icon
|
||||
autoHideMenuBar: true, // Hides the top menu bar for a cleaner look
|
||||
webPreferences: {
|
||||
nodeIntegration: true, // Allows the HTML to use Node features if needed
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
win.loadFile('index.html');
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
Generated
+4103
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "freight-calculator-pro",
|
||||
"version": "1.0.0",
|
||||
"description": "Professional Freight Class Calculator",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build": "electron-builder --win"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.4"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.freightcalc.pro",
|
||||
"productName": "Freight Calculator Pro",
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "icon.ico"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user