Rewrite as static webapp (#1)
Reviewed-on: #1 Co-authored-by: localhorst <localhorst@mosad.xyz> Co-committed-by: localhorst <localhorst@mosad.xyz>
This commit is contained in:
337
index.html
Normal file
337
index.html
Normal file
@ -0,0 +1,337 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LiXX Cell Pack Matcher</title>
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<link rel="icon" href="data/favicon.svg" type="image/svg+xml">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="logo">
|
||||
<img src="data/favicon.svg" width="40" />
|
||||
<h1>LiXX Cell Pack Matcher</h1>
|
||||
</div>
|
||||
<p class="subtitle">Optimal cell matching for lithium battery packs</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Configuration Section -->
|
||||
<section class="card" aria-labelledby="config-heading">
|
||||
<h2 id="config-heading">Pack Configuration</h2>
|
||||
<div class="config-grid">
|
||||
<div class="form-group">
|
||||
<label for="cells-serial">Series (S)</label>
|
||||
<input type="number" id="cells-serial" min="1" max="42" value="6"
|
||||
aria-describedby="serial-help">
|
||||
<small id="serial-help">Number of cells in series</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cells-parallel">Parallel (P)</label>
|
||||
<input type="number" id="cells-parallel" min="1" max="42" value="2"
|
||||
aria-describedby="parallel-help">
|
||||
<small id="parallel-help">Number of cells in parallel</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config-display">Configuration</label>
|
||||
<output id="config-display" class="config-badge">6S2P</output>
|
||||
<small>Total: <span id="total-cells-needed">12</span> cells</small>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cell Input Section -->
|
||||
<section class="card" aria-labelledby="cells-heading">
|
||||
<h2 id="cells-heading">Cell Data</h2>
|
||||
<div class="cell-input-header">
|
||||
<p>Enter cell data: Label, Capacity (mAh), Internal Resistance (mΩ, optional)</p>
|
||||
<div class="button-group">
|
||||
<button type="button" id="btn-add-cell" class="btn btn-secondary">
|
||||
<span aria-hidden="true">+</span> Add Cell
|
||||
</button>
|
||||
<button type="button" id="btn-load-example" class="btn btn-ghost">
|
||||
Load Example
|
||||
</button>
|
||||
<button type="button" id="btn-clear-all" class="btn btn-ghost btn-danger">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell-table-wrapper">
|
||||
<table class="cell-table" id="cell-table" role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Label</th>
|
||||
<th scope="col">Capacity (mAh)</th>
|
||||
<th scope="col">IR (mΩ)</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="cell-tbody">
|
||||
<!-- Cells will be added dynamically -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="cell-stats" id="cell-stats" aria-live="polite">
|
||||
<span>Cells: <strong id="stat-count">0</strong></span>
|
||||
<span>Avg Capacity: <strong id="stat-avg-cap">-</strong></span>
|
||||
<span>Avg IR: <strong id="stat-avg-ir">-</strong></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Algorithm Settings -->
|
||||
<section class="card" aria-labelledby="algo-heading">
|
||||
<h2 id="algo-heading">Matching Settings</h2>
|
||||
<div class="settings-grid">
|
||||
<div class="form-group">
|
||||
<label for="weight-capacity">Capacity Weight</label>
|
||||
<input type="range" id="weight-capacity" min="0" max="100" value="70"
|
||||
aria-describedby="weight-cap-value">
|
||||
<output id="weight-cap-value">70%</output>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weight-ir">IR Weight</label>
|
||||
<input type="range" id="weight-ir" min="0" max="100" value="30"
|
||||
aria-describedby="weight-ir-value">
|
||||
<output id="weight-ir-value">30%</output>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="algorithm-select">Algorithm</label>
|
||||
<select id="algorithm-select">
|
||||
<option value="exhaustive">Exhaustive Search (Small packs only)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="max-iterations">Max Iterations</label>
|
||||
<input type="number" id="max-iterations" min="100" max="100000" value="5000">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="btn-start-matching" class="btn btn-primary btn-large">
|
||||
<span class="btn-icon" aria-hidden="true">⚡</span>
|
||||
Start Matching
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Progress Section -->
|
||||
<section class="card" id="progress-section" hidden aria-labelledby="progress-heading">
|
||||
<h2 id="progress-heading">Matching Progress</h2>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progress-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="progress-stats-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Iteration</span>
|
||||
<span class="stat-value" id="progress-iteration">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Combinations</span>
|
||||
<span class="stat-value" id="progress-combinations">-</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Best Score</span>
|
||||
<span class="stat-value stat-highlight" id="progress-score">-</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Elapsed</span>
|
||||
<span class="stat-value" id="progress-time">0s</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Speed</span>
|
||||
<span class="stat-value" id="progress-speed">-</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">ETA</span>
|
||||
<span class="stat-value" id="progress-eta">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live Best Configuration Preview -->
|
||||
<div class="live-preview" id="live-preview" hidden>
|
||||
<h3>Current Best Configuration</h3>
|
||||
<div class="live-preview-content" id="live-preview-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="btn-stop-matching" class="btn btn-ghost btn-danger">
|
||||
Stop Matching
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Results Section -->
|
||||
<section class="card" id="results-section" hidden aria-labelledby="results-heading">
|
||||
<h2 id="results-heading">Matching Results</h2>
|
||||
|
||||
<!-- Warning Banner -->
|
||||
<div class="warning-banner" role="alert">
|
||||
<div class="warning-icon" aria-hidden="true">⚠️</div>
|
||||
<div class="warning-content">
|
||||
<strong>Safety Warning - Used Lithium Cells</strong>
|
||||
<ul>
|
||||
<li>Used cells may have hidden defects not detectable by capacity/IR testing</li>
|
||||
<li>Internal resistance mismatch >20% can reduce cycle life by up to 40%</li>
|
||||
<li>Always use a BMS with cell-level monitoring and balancing</li>
|
||||
<li>Use only same model of cells in a pack</li>
|
||||
<li>Never charge unattended; use fireproof storage</li>
|
||||
<li>Cells with significantly different ages may degrade unpredictably</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="results-summary" id="results-summary">
|
||||
<div class="result-metric" tabindex="0" data-tooltip="match-score">
|
||||
<span class="metric-value" id="result-score">-</span>
|
||||
<span class="metric-label">Match Score</span>
|
||||
<div class="metric-tooltip">
|
||||
<strong>Match Score</strong>
|
||||
<p>Combined score from capacity and IR variance, weighted by your settings.</p>
|
||||
<div class="tooltip-scale">
|
||||
<span class="scale-good">< 0.5 = Excellent</span>
|
||||
<span class="scale-ok">0.5 - 2.0 = Good</span>
|
||||
<span class="scale-bad">> 2.0 = Poor</span>
|
||||
</div>
|
||||
<p class="tooltip-hint">Lower is better</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-metric" tabindex="0" data-tooltip="cap-cv">
|
||||
<span class="metric-value" id="result-cap-variance">-</span>
|
||||
<span class="metric-label">Capacity CV%</span>
|
||||
<div class="metric-tooltip">
|
||||
<strong>Capacity Coefficient of Variation</strong>
|
||||
<p>Measures how evenly matched the total capacity of each parallel group is. CV = (σ / μ) ×
|
||||
100%</p>
|
||||
<div class="tooltip-scale">
|
||||
<span class="scale-good">< 1% = Excellent</span>
|
||||
<span class="scale-ok">1 - 3% = Acceptable</span>
|
||||
<span class="scale-bad">> 3% = Poor balance</span>
|
||||
</div>
|
||||
<p class="tooltip-hint">Lower is better – ensures even discharge across series groups</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-metric" tabindex="0" data-tooltip="ir-cv">
|
||||
<span class="metric-value" id="result-ir-variance">-</span>
|
||||
<span class="metric-label">IR CV%</span>
|
||||
<div class="metric-tooltip">
|
||||
<strong>Internal Resistance Variation</strong>
|
||||
<p>Average variation of internal resistance within parallel groups. High mismatch causes
|
||||
uneven current distribution.</p>
|
||||
<div class="tooltip-scale">
|
||||
<span class="scale-good">< 5% = Excellent</span>
|
||||
<span class="scale-ok">5 - 15% = Acceptable</span>
|
||||
<span class="scale-bad">> 20% = Risk of 40% lifetime reduction</span>
|
||||
</div>
|
||||
<p class="tooltip-hint">Lower is better – critical for high-drain applications</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-metric" tabindex="0" data-tooltip="pack-cap">
|
||||
<span class="metric-value" id="result-pack-capacity">-</span>
|
||||
<span class="metric-label">Pack Capacity</span>
|
||||
<div class="metric-tooltip">
|
||||
<strong>Effective Pack Capacity</strong>
|
||||
<p>The usable capacity of your pack, limited by the smallest parallel group (weakest link).
|
||||
</p>
|
||||
<p>Formula: min(group capacities)</p>
|
||||
<p class="tooltip-hint">Higher is better – well-matched cells maximize this value</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visual Pack Layout -->
|
||||
<div class="pack-visualization" id="pack-visualization">
|
||||
<h3>Pack Layout</h3>
|
||||
<div class="pack-grid" id="pack-grid">
|
||||
<!-- Generated dynamically -->
|
||||
</div>
|
||||
<div class="pack-legend">
|
||||
<span><span class="legend-color" style="background: var(--cell-low)"></span> Lower
|
||||
Capacity</span>
|
||||
<span><span class="legend-color" style="background: var(--cell-mid)"></span> Average</span>
|
||||
<span><span class="legend-color" style="background: var(--cell-high)"></span> Higher
|
||||
Capacity</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed Results Table -->
|
||||
<div class="results-table-wrapper">
|
||||
<h3>Parallel Group Details</h3>
|
||||
<table class="results-table" id="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Cells</th>
|
||||
<th>Total Capacity</th>
|
||||
<th>Avg IR</th>
|
||||
<th>Deviation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="results-tbody">
|
||||
<!-- Generated dynamically -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Excluded Cells -->
|
||||
<div class="excluded-cells" id="excluded-cells-section" hidden>
|
||||
<h3>Excluded Cells</h3>
|
||||
<p id="excluded-cells-list"></p>
|
||||
</div>
|
||||
|
||||
<!-- Export Buttons -->
|
||||
<div class="export-buttons">
|
||||
<button type="button" id="btn-export-json" class="btn btn-secondary">
|
||||
Export JSON
|
||||
</button>
|
||||
<button type="button" id="btn-export-csv" class="btn btn-secondary">
|
||||
Export CSV
|
||||
</button>
|
||||
<button type="button" id="btn-copy-results" class="btn btn-secondary">
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
<a href="https://git.mosad.xyz/localhorst/LiXX_Cell_Pack_Matcher" target="_blank" rel="noopener">Git</a>
|
||||
·
|
||||
Based on research by
|
||||
<a href="https://doi.org/10.1016/j.jpowsour.2013.11.064" target="_blank" rel="noopener">Wang et al.,
|
||||
2013</a>
|
||||
</p>
|
||||
<p class="disclaimer">
|
||||
This tool is for educational purposes. Always consult professional guidance for battery pack assembly.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Keyboard Shortcuts Modal -->
|
||||
<dialog id="shortcuts-dialog">
|
||||
<h2>Keyboard Shortcuts</h2>
|
||||
<dl class="shortcuts-list">
|
||||
<dt><kbd>Alt</kbd> + <kbd>A</kbd></dt>
|
||||
<dd>Add new cell</dd>
|
||||
<dt><kbd>Alt</kbd> + <kbd>S</kbd></dt>
|
||||
<dd>Start matching</dd>
|
||||
<dt><kbd>Alt</kbd> + <kbd>E</kbd></dt>
|
||||
<dd>Load example data</dd>
|
||||
<dt><kbd>Esc</kbd></dt>
|
||||
<dd>Stop matching / Close dialog</dd>
|
||||
<dt><kbd>?</kbd></dt>
|
||||
<dd>Show this help</dd>
|
||||
</dl>
|
||||
<button type="button" class="btn btn-primary" id="btn-close-shortcuts">Close</button>
|
||||
</dialog>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user