feat: Add WFW-Aushang web app with PWA support, offline caching, and dark mode

- 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.
This commit is contained in:
thrhymes
2025-12-24 16:59:51 +01:00
parent 5577407f97
commit b049dded72
24 changed files with 4167 additions and 0 deletions

BIN
CableCalc/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

481
CableCalc/index.php Normal file
View File

@@ -0,0 +1,481 @@
<?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 &amp; 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 &amp; 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>