pcb-retrace/docs/coil.html

245 lines
7.8 KiB
HTML

<!--
~ Copyright (c) 2025-2026 Taras Greben
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial-pcb-retrace
~ See LICENSE file for details.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("consent","default",{ad_storage:"granted",ad_user_data:"granted",ad_personalization:"granted",analytics_storage:"granted"});gtag("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",region:"AT,BE,BG,HR,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IE,IT,LV,LT,LU,MT,NL,PL,PT,RO,SK,SI,ES,SE,IS,LI,NO,GB,CH,UA".split(",")});</script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-X1MKL6C4B0"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("js",new Date());gtag("config","G-X1MKL6C4B0");</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="favicon.ico">
<title>Air Core Coil</title>
<script src="tool-bridge.js"></script>
<script src="spyglass.js"></script>
<link rel="stylesheet" href="tool-style.css">
<style>
:root {
/* Copper Theme */
--accent: #ea580c;
--copper-dark: #9a3412;
--bg: #fff7ed;
--border: #fed7aa;
--focus-ring: #ffedd5;
}
/* Layout overrides */
.coil-viz {
width: 100%; height: 120px; display: flex; align-items: center; justify-content: center;
background: linear-gradient(to bottom, #fff7ed, #fff);
border: 1px solid #fed7aa; border-radius: 0.5rem;
overflow: hidden; position: relative; flex-shrink: 0; margin-bottom: 1rem;
}
.coil-viz svg {
width: 100%;
height: 100%;
display: block;
}
svg path {
fill: none; stroke: url(#copperGradient); stroke-width: 4; stroke-linecap: round;
filter: drop-shadow(0px 2px 2px rgba(0,0,0,0.2));
}
/* Specific Label Color from original file */
label { color: #78350f !important; opacity: 1 !important; }
/* MOBILE OVERRIDES */
@media (max-width: 600px) {
.result-val { font-size: 1.5rem; }
/* NEW: Smaller Coil Graphic on Mobile */
.coil-viz {
height: 80px; /* Reduced from 120px */
margin-bottom: 0.5rem;
}
}
</style>
</head>
<body>
<div class="container">
<h1 id="app-title">Air Core Calc</h1>
<div class="top-section">
<div id="spyglass-col" class="col-spyglass">
<div class="spyglass-wrapper">
<canvas id="tool-canvas" class="spyglass-canvas" width="600" height="600"></canvas>
<div id="tool-zoom" class="zoom-badge">2x</div>
</div>
<div class="spyglass-hint">Scroll to zoom</div>
<div id="view-selector"></div>
</div>
<div class="col-controls">
<div class="input-grid">
<div class="form-group">
<label>Turns (N)</label>
<div class="input-wrapper">
<input type="number" id="inp-turns" value="10" min="1" step="1" oninput="updateLength()">
<span class="unit">T</span>
</div>
</div>
<div class="form-group">
<label>Diameter (D)</label>
<div class="input-wrapper">
<input type="number" id="inp-dia" value="6" min="0.1" step="0.1" oninput="calculate()">
<span class="unit">mm</span>
</div>
</div>
<div class="form-group">
<label>Wire Ø (d)</label>
<div class="input-wrapper">
<input type="number" id="inp-wire" value="0.5" min="0.05" step="0.05" oninput="updateLength()">
<span class="unit">mm</span>
</div>
</div>
<div class="form-group">
<label>Length (L)</label>
<div class="input-wrapper">
<input type="number" id="inp-len" value="5" min="0.1" step="0.1" oninput="calculate()" style="background:#f9fafb; color:#666">
<span class="unit">mm</span>
</div>
</div>
</div>
</div>
</div>
<div class="coil-viz">
<svg width="300" height="100" viewBox="0 0 300 100" id="coil-svg">
<defs>
<linearGradient id="copperGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#fdba74;stop-opacity:1" />
<stop offset="50%" style="stop-color:#ea580c;stop-opacity:1" />
<stop offset="100%" style="stop-color:#7c2d12;stop-opacity:1" />
</linearGradient>
</defs>
<path id="coil-path" d="" />
</svg>
</div>
<div class="result-box">
<div class="result-row" style="background:#fff7ed; border:1px solid #fed7aa;">
<div class="val-group">
<span class="val-text" id="res-val" style="color:#9a3412">---</span>
<span class="meta-text" id="res-desc" style="color:#c2410c">Adjust parameters above</span>
</div>
<button id="btn-select" class="btn-select" style="display:none;" onclick="sendResult()">Select</button>
</div>
</div>
<div class="app-footer">Copyright © 2025 Taras Greben — <a href="https://pcb.etaras.com">pcb.eTaras.com</a></div>
</div>
<script>
let isEmbed = false;
let calculatedValues = { val: '', desc: '' };
let bridge = null;
function init() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('embed') || window.self !== window.top) {
isEmbed = true;
document.getElementById('btn-select').style.display = 'block';
document.getElementById('app-title').style.display = 'none';
}
calculate();
}
function parseDescription(desc) {
try {
const regex = /([\d\.]+)T,\s*([\d\.]+)mm ID,\s*([\d\.]+)mm Cu/;
const match = desc.match(regex);
if (match) {
document.getElementById('inp-turns').value = match[1];
document.getElementById('inp-dia').value = match[2];
document.getElementById('inp-wire').value = match[3];
updateLength();
}
} catch(e) {}
}
function updateLength() {
const N = parseFloat(document.getElementById('inp-turns').value) || 0;
const d_wire = parseFloat(document.getElementById('inp-wire').value) || 0;
const len = (N * d_wire * 1.05).toFixed(2);
document.getElementById('inp-len').value = len;
calculate();
}
function calculate() {
const N = parseFloat(document.getElementById('inp-turns').value);
const D_mm = parseFloat(document.getElementById('inp-dia').value);
const l_mm = parseFloat(document.getElementById('inp-len').value);
const w_mm = parseFloat(document.getElementById('inp-wire').value);
if (!N || !D_mm || !l_mm) return;
const d_in = D_mm / 25.4;
const l_in = l_mm / 25.4;
const num = Math.pow(d_in, 2) * Math.pow(N, 2);
const den = (18 * d_in) + (40 * l_in);
let uH = num / den;
let displayVal = "", cleanVal = "";
if (uH < 1) {
let nH = uH * 1000;
displayVal = nH.toFixed(0) + " nH";
cleanVal = nH.toFixed(0) + "nH";
} else {
displayVal = uH.toFixed(2) + " µH";
cleanVal = uH.toFixed(2) + "uH";
}
const desc = `Air Core: ${N}T, ${D_mm}mm ID, ${w_mm}mm Cu`;
document.getElementById('res-val').innerText = displayVal;
document.getElementById('res-desc').innerText = desc;
calculatedValues = { val: cleanVal, desc: desc };
drawCoil(N);
}
function drawCoil(turns) {
const path = document.getElementById('coil-path');
const width = 300;
const height = 100;
const padding = 20;
const visualTurns = Math.min(Math.max(turns, 1), 20);
const pitch = (width - padding*2) / visualTurns;
const radius = 24;
const midY = 60;
let d = `M ${padding} ${midY + radius}`;
for (let i = 0; i < visualTurns; i++) {
const x = padding + (i * pitch);
// The magic number 2.5 defines the 'height' of the back loop perspective
d += ` C ${x + pitch*0.2} ${midY - radius*2.5}, ${x + pitch*0.8} ${midY - radius*2.5}, ${x + pitch} ${midY + radius}`;
}
path.setAttribute('d', d);
}
function sendResult() {
if(bridge) bridge.sendResult();
}
init();
bridge = new ToolBridge({
onData: (d) => {
if(d.description) parseDescription(d.description);
calculate();
},
onNominal: () => calculatedValues.val,
onDescription: () => calculatedValues.desc
});
</script>
</body>
</html>