- Created index.html for the main application interface with responsive design and dark mode support. - Added manifest.webmanifest for PWA configuration, including app icons and display settings. - Implemented service worker (sw.js) for offline caching of assets and network-first strategy for versioning. - Introduced version.json to manage app versioning.
482 lines
15 KiB
PHP
482 lines
15 KiB
PHP
<?php
|
||
// index.php - simple cable size calculator
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>CableCalc – Cable Size Calculator</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style>
|
||
:root {
|
||
--bg: #f4f4f4;
|
||
--card-bg: #ffffff;
|
||
--text: #222;
|
||
--text-muted: #555;
|
||
--border: #ccc;
|
||
--accent: #0078d7;
|
||
--result-bg: #f0f7ff;
|
||
--result-border: #c5ddff;
|
||
--error-bg: #ffecec;
|
||
--error-border: #ffb3b3;
|
||
}
|
||
|
||
/* DARK MODE VARIABLES */
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--bg: #111;
|
||
--card-bg: #1b1b1b;
|
||
--text: #eee;
|
||
--text-muted: #bbb;
|
||
--border: #444;
|
||
--accent: #3ea0ff;
|
||
--result-bg: #102233;
|
||
--result-border: #1a3a57;
|
||
--error-bg: #331111;
|
||
--error-border: #883333;
|
||
}
|
||
}
|
||
|
||
/* USER OVERRIDE – DARK */
|
||
body.dark {
|
||
--bg: #111;
|
||
--card-bg: #1b1b1b;
|
||
--text: #eee;
|
||
--text-muted: #bbb;
|
||
--border: #444;
|
||
--accent: #3ea0ff;
|
||
--result-bg: #102233;
|
||
--result-border: #1a3a57;
|
||
--error-bg: #331111;
|
||
--error-border: #883333;
|
||
}
|
||
|
||
/* USER OVERRIDE – LIGHT */
|
||
body.light {
|
||
--bg: #f4f4f4;
|
||
--card-bg: #ffffff;
|
||
--text: #222;
|
||
--text-muted: #555;
|
||
--border: #ccc;
|
||
--accent: #0078d7;
|
||
--result-bg: #f0f7ff;
|
||
--result-border: #c5ddff;
|
||
--error-bg: #ffecec;
|
||
--error-border: #ffb3b3;
|
||
}
|
||
|
||
/* Base styles */
|
||
body {
|
||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
margin: 0;
|
||
padding: 0;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
transition: background 0.3s, color 0.3s;
|
||
}
|
||
|
||
.container {
|
||
max-width: 800px;
|
||
margin: 20px auto 40px;
|
||
background: var(--card-bg);
|
||
padding: 20px 24px 28px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 15px rgba(0,0,0,0.20);
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
h1 {
|
||
margin-top: 0;
|
||
font-size: 1.8rem;
|
||
}
|
||
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||
gap: 16px 24px;
|
||
}
|
||
|
||
.field label {
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
color: var(--text);
|
||
}
|
||
|
||
.field input,
|
||
.field select {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 6px 8px;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--border);
|
||
background: var(--card-bg);
|
||
color: var(--text);
|
||
transition: background 0.3s, color 0.3s, border 0.3s;
|
||
}
|
||
|
||
.inline-options label {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
button {
|
||
margin-top: 12px;
|
||
padding: 8px 16px;
|
||
border-radius: 999px;
|
||
border: none;
|
||
font-size: 0.95rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
background: var(--accent);
|
||
color: white;
|
||
transition: opacity 0.2s;
|
||
}
|
||
button:hover {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.result {
|
||
margin-top: 20px;
|
||
padding: 12px 16px;
|
||
border-radius: 8px;
|
||
background: var(--result-bg);
|
||
border: 1px solid var(--result-border);
|
||
}
|
||
|
||
.error {
|
||
margin-top: 12px;
|
||
padding: 10px 14px;
|
||
border-radius: 8px;
|
||
background: var(--error-bg);
|
||
border: 1px solid var(--error-border);
|
||
color: #ff8080;
|
||
}
|
||
|
||
.notes {
|
||
margin-top: 14px;
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.badge {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
font-size: 0.75rem;
|
||
background: var(--border);
|
||
color: var(--text);
|
||
}
|
||
|
||
/* Toggle button */
|
||
#modeToggle {
|
||
float: right;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
background: var(--border);
|
||
color: var(--text);
|
||
font-size: 0.8rem;
|
||
cursor: pointer;
|
||
}
|
||
</style>
|
||
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>CableCalc <span class="badge">beta</span></h1>
|
||
<p style="font-size:0.9rem; color:#555;">
|
||
Enter your data – the calculator suggests the smallest copper cable size that keeps current
|
||
within a basic ampacity and voltage drop limit.
|
||
</p>
|
||
|
||
<div class="grid">
|
||
<!-- AC / DC -->
|
||
<div class="field">
|
||
<label for="acdc">AC / DC</label>
|
||
<select id="acdc">
|
||
<option value="AC">AC</option>
|
||
<option value="DC">DC</option>
|
||
</select>
|
||
<small>AC/DC is currently only shown for reference; calculation uses the same resistive model.</small>
|
||
</div>
|
||
|
||
<!-- Voltage -->
|
||
<div class="field">
|
||
<label for="voltage">Voltage [V]</label>
|
||
<input type="number" id="voltage" min="0" step="0.1" value="230">
|
||
</div>
|
||
|
||
<!-- Current / Power selector -->
|
||
<div class="field">
|
||
<label>What is known?</label>
|
||
<div class="inline-options">
|
||
<label>
|
||
<input type="radio" name="loadType" value="current" checked>
|
||
Current (A) known
|
||
</label>
|
||
<label>
|
||
<input type="radio" name="loadType" value="power">
|
||
Power (W) known
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Current input -->
|
||
<div class="field" id="field-current">
|
||
<label for="current">Current [A]</label>
|
||
<input type="number" id="current" min="0" step="0.01">
|
||
</div>
|
||
|
||
<!-- Power input -->
|
||
<div class="field" id="field-power" style="display:none;">
|
||
<label for="power">Power [W]</label>
|
||
<input type="number" id="power" min="0" step="1">
|
||
<small>Assumes cos φ / power factor = 1.0 for simplicity.</small>
|
||
</div>
|
||
|
||
<!-- Length -->
|
||
<div class="field">
|
||
<label for="length">Cable length (one way)</label>
|
||
<input type="number" id="length" min="0" step="0.1" value="10">
|
||
</div>
|
||
|
||
<!-- Length unit -->
|
||
<div class="field">
|
||
<label for="lengthUnit">Length unit</label>
|
||
<select id="lengthUnit">
|
||
<option value="m">Meters</option>
|
||
<option value="ft">Feet</option>
|
||
</select>
|
||
<small>Voltage drop is calculated over the round-trip (out & back).</small>
|
||
</div>
|
||
|
||
<!-- Max voltage drop -->
|
||
<div class="field">
|
||
<label for="maxDrop">Max voltage drop [%]</label>
|
||
<input type="number" id="maxDrop" min="0.5" step="0.1" value="3.0">
|
||
</div>
|
||
|
||
<!-- Output units -->
|
||
<div class="field">
|
||
<label for="unitSystem">Preferred output</label>
|
||
<select id="unitSystem">
|
||
<option value="mixed">AWG + mm²</option>
|
||
<option value="awg">AWG only</option>
|
||
<option value="mm2">mm² only</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="button" onclick="calculateCable()">Calculate cable size</button>
|
||
|
||
<div id="error" class="error" style="display:none;"></div>
|
||
<div id="result" class="result" style="display:none;"></div>
|
||
|
||
<div id="modeToggle" onclick="toggleMode()">🌓</div>
|
||
|
||
<div class="notes">
|
||
<strong>Notes & limitations:</strong>
|
||
<ul>
|
||
<li>Assumes copper conductors, single circuit, free air / light installation.</li>
|
||
<li>Uses generic ampacity values (conservative but not a substitute for local electrical codes).</li>
|
||
<li>For mains or critical installations always have a qualified electrician verify the result.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Cable data: copper, approx. resistance at 20°C and conservative ampacity in A.
|
||
// Values are generic and simplified – not a substitute for national standards.
|
||
const cableSizes = [
|
||
// AWG, mm², diameter(mm, bare conductor approx), R[ohm/km], ampacity[A]
|
||
{ awg: 18, mm2: 0.82, diameter: 1.02, r_km: 21.0, amp: 14 },
|
||
{ awg: 16, mm2: 1.31, diameter: 1.29, r_km: 13.3, amp: 18 },
|
||
{ awg: 14, mm2: 2.08, diameter: 1.63, r_km: 8.4, amp: 24 },
|
||
{ awg: 12, mm2: 3.31, diameter: 2.05, r_km: 5.3, amp: 32 },
|
||
{ awg: 10, mm2: 5.26, diameter: 2.59, r_km: 3.3, amp: 45 },
|
||
{ awg: 8, mm2: 8.37, diameter: 3.26, r_km: 2.1, amp: 60 },
|
||
{ awg: 6, mm2: 13.3, diameter: 4.11, r_km: 1.3, amp: 80 },
|
||
{ awg: 4, mm2: 21.1, diameter: 5.19, r_km: 0.8, amp: 105 },
|
||
{ awg: 2, mm2: 33.6, diameter: 6.54, r_km: 0.51, amp: 140 },
|
||
{ awg: 1, mm2: 42.4, diameter: 7.35, r_km: 0.40, amp: 165 },
|
||
{ awg: 0, mm2: 53.5, diameter: 8.25, r_km: 0.32, amp: 195 }
|
||
];
|
||
|
||
// Toggle current / power fields
|
||
document.querySelectorAll('input[name="loadType"]').forEach(r => {
|
||
r.addEventListener('change', function () {
|
||
const type = this.value;
|
||
document.getElementById('field-current').style.display = (type === 'current') ? 'block' : 'none';
|
||
document.getElementById('field-power').style.display = (type === 'power') ? 'block' : 'none';
|
||
});
|
||
});
|
||
|
||
function showError(msg) {
|
||
const err = document.getElementById('error');
|
||
err.textContent = msg;
|
||
err.style.display = 'block';
|
||
document.getElementById('result').style.display = 'none';
|
||
}
|
||
|
||
function clearError() {
|
||
const err = document.getElementById('error');
|
||
err.style.display = 'none';
|
||
err.textContent = '';
|
||
}
|
||
|
||
function toggleMode() {
|
||
if (document.body.classList.contains("dark")) {
|
||
document.body.classList.remove("dark");
|
||
document.body.classList.add("light");
|
||
localStorage.setItem("cablecalc-theme", "light");
|
||
} else if (document.body.classList.contains("light")) {
|
||
document.body.classList.remove("light");
|
||
document.body.classList.add("dark");
|
||
localStorage.setItem("cablecalc-theme", "dark");
|
||
} else {
|
||
// No override yet → force dark
|
||
document.body.classList.add("dark");
|
||
localStorage.setItem("cablecalc-theme", "dark");
|
||
}
|
||
}
|
||
|
||
// Load saved preference
|
||
(function() {
|
||
const saved = localStorage.getItem("cablecalc-theme");
|
||
if (saved === "dark") document.body.classList.add("dark");
|
||
if (saved === "light") document.body.classList.add("light");
|
||
})();
|
||
|
||
function calculateCable() {
|
||
clearError();
|
||
const voltage = parseFloat(document.getElementById('voltage').value);
|
||
const lengthOneWay = parseFloat(document.getElementById('length').value);
|
||
const lengthUnit = document.getElementById('lengthUnit').value;
|
||
const maxDropPct = parseFloat(document.getElementById('maxDrop').value);
|
||
const unitSystem = document.getElementById('unitSystem').value;
|
||
const loadType = document.querySelector('input[name="loadType"]:checked').value;
|
||
|
||
if (isNaN(voltage) || voltage <= 0) {
|
||
showError('Please enter a valid voltage.');
|
||
return;
|
||
}
|
||
if (isNaN(lengthOneWay) || lengthOneWay <= 0) {
|
||
showError('Please enter a valid cable length.');
|
||
return;
|
||
}
|
||
if (isNaN(maxDropPct) || maxDropPct <= 0) {
|
||
showError('Please enter a valid maximum voltage drop percentage.');
|
||
return;
|
||
}
|
||
|
||
let current;
|
||
if (loadType === 'current') {
|
||
current = parseFloat(document.getElementById('current').value);
|
||
if (isNaN(current) || current <= 0) {
|
||
showError('Please enter a valid current.');
|
||
return;
|
||
}
|
||
} else {
|
||
const power = parseFloat(document.getElementById('power').value);
|
||
if (isNaN(power) || power <= 0) {
|
||
showError('Please enter a valid power.');
|
||
return;
|
||
}
|
||
// Power factor assumed 1.0
|
||
current = power / voltage;
|
||
}
|
||
|
||
// Convert length to meters (one way)
|
||
let length_m = lengthOneWay;
|
||
if (lengthUnit === 'ft') {
|
||
length_m = lengthOneWay * 0.3048;
|
||
}
|
||
|
||
// Total circuit length = out + back
|
||
const totalLength_m = length_m * 2;
|
||
|
||
const maxDrop_V = voltage * (maxDropPct / 100.0);
|
||
|
||
let chosen = null;
|
||
let worstCase = null; // largest size if nothing meets both criteria
|
||
|
||
for (let i = 0; i < cableSizes.length; i++) {
|
||
const c = cableSizes[i];
|
||
|
||
const rPerMeter = c.r_km / 1000.0; // ohm/m
|
||
const rTotal = rPerMeter * totalLength_m; // ohm
|
||
const vDrop = current * rTotal; // V
|
||
const vDropPct = (vDrop / voltage) * 100;
|
||
|
||
const okAmpacity = current <= c.amp;
|
||
const okDrop = vDrop <= maxDrop_V;
|
||
|
||
// Remember largest size for fallback
|
||
worstCase = {
|
||
cable: c,
|
||
vDrop: vDrop,
|
||
vDropPct: vDropPct,
|
||
okAmpacity: okAmpacity,
|
||
okDrop: okDrop
|
||
};
|
||
|
||
if (okAmpacity && okDrop) {
|
||
chosen = {
|
||
cable: c,
|
||
vDrop: vDrop,
|
||
vDropPct: vDropPct
|
||
};
|
||
break; // smallest that satisfies
|
||
}
|
||
}
|
||
|
||
const res = document.getElementById('result');
|
||
res.style.display = 'block';
|
||
|
||
if (!chosen && worstCase) {
|
||
const c = worstCase.cable;
|
||
const vDrop = worstCase.vDrop;
|
||
const vDropPct = worstCase.vDropPct;
|
||
|
||
res.innerHTML = `
|
||
<strong>No cable size in the built-in table fully meets the limits.</strong><br><br>
|
||
Suggested <em>minimum</em> size (table maximum):<br>
|
||
AWG ${c.awg}, approx. ${c.mm2.toFixed(1)} mm², conductor diameter ≈ ${c.diameter.toFixed(2)} mm<br>
|
||
Ampacity (approx.): ${c.amp} A<br>
|
||
Estimated voltage drop: ${vDrop.toFixed(2)} V (${vDropPct.toFixed(2)} %)<br><br>
|
||
<span style="color:#a40000;">Warning:</span> Either the desired voltage drop or ampacity is exceeded.
|
||
Consider using a larger cable than listed here or shortening the run.
|
||
`;
|
||
return;
|
||
}
|
||
|
||
if (!chosen) {
|
||
showError('No valid cable size found. Please check inputs.');
|
||
return;
|
||
}
|
||
|
||
const c = chosen.cable;
|
||
const vDrop = chosen.vDrop;
|
||
const vDropPct = chosen.vDropPct;
|
||
|
||
let mainLine = '';
|
||
if (unitSystem === 'awg') {
|
||
mainLine = `Recommended cable size: <strong>AWG ${c.awg}</strong>`;
|
||
} else if (unitSystem === 'mm2') {
|
||
mainLine = `Recommended cable size: <strong>${c.mm2.toFixed(1)} mm²</strong>`;
|
||
} else {
|
||
mainLine = `Recommended cable size: <strong>AWG ${c.awg} (~${c.mm2.toFixed(1)} mm²)</strong>`;
|
||
}
|
||
|
||
res.innerHTML = `
|
||
${mainLine}<br>
|
||
Conductor diameter (approx.): <strong>${c.diameter.toFixed(2)} mm</strong><br>
|
||
Ampacity (approx.): <strong>${c.amp} A</strong><br>
|
||
Estimated voltage drop over ${length_m.toFixed(1)} m one-way (round-trip ${totalLength_m.toFixed(1)} m):<br>
|
||
<strong>${vDrop.toFixed(2)} V (${vDropPct.toFixed(2)} %)</strong>
|
||
`;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|