201 lines
9.0 KiB
HTML
201 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Group Size Calculator – ShooterHub</title>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||
<link rel="stylesheet" href="/css/app.css">
|
||
<style>
|
||
.upload-area {
|
||
border: 2px dashed #ced4da; border-radius: 10px;
|
||
padding: 3.5rem 2rem; text-align: center; color: #6c757d;
|
||
cursor: pointer; transition: border-color .2s, color .2s;
|
||
}
|
||
.upload-area:hover, .upload-area.drag-over { border-color: #0d6efd; color: #0d6efd; }
|
||
#annotCanvas { display: block; width: 100%; border-radius: 6px; border: 1px solid #dee2e6; }
|
||
.input-section { background: #f8f9fa; border-radius: 8px; padding: .85rem 1rem; margin-bottom: .6rem; }
|
||
.input-section h6 { margin-bottom: .6rem; font-size: .85rem; }
|
||
.legend-dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; vertical-align: middle; }
|
||
.result-row td:first-child { color: #6c757d; font-size: .85rem; width: 170px; }
|
||
.result-row td:last-child { font-weight: 500; font-size: .9rem; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="navbar"></div>
|
||
|
||
<div class="container-fluid py-3">
|
||
<div class="row g-3">
|
||
|
||
<!-- ══ Left: controls ═══════════════════════════════════════════════════ -->
|
||
<div class="col-md-4 col-xl-3">
|
||
<div id="backBtnWrap" class="d-none mb-2">
|
||
<a id="backBtn" href="/chrono.html" class="btn btn-sm btn-outline-secondary">
|
||
<i class="bi bi-arrow-left me-1"></i>Back to analysis
|
||
</a>
|
||
</div>
|
||
<h5 class="fw-bold mb-1">
|
||
<i class="bi bi-crosshair2 me-2 text-primary"></i>Group Size Calculator
|
||
</h5>
|
||
<p class="text-muted small mb-3">Annotate a target photo to measure group size, mean radius, and correction.</p>
|
||
|
||
<!-- Unit switch -->
|
||
<div class="input-section">
|
||
<h6 class="fw-semibold mb-2"><i class="bi bi-rulers me-1"></i>Display units</h6>
|
||
<div class="btn-group btn-group-sm w-100" id="gsDistUnitBtns">
|
||
<button class="btn btn-outline-secondary" data-dist-unit="mm">mm</button>
|
||
<button class="btn btn-outline-secondary" data-dist-unit="moa">MOA</button>
|
||
<button class="btn btn-outline-secondary" data-dist-unit="mrad">MRAD</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Setup -->
|
||
<div class="input-section">
|
||
<h6 class="fw-semibold"><i class="bi bi-sliders me-1"></i>Setup</h6>
|
||
<div class="mb-2">
|
||
<label class="form-label form-label-sm mb-1">Distance (m)</label>
|
||
<input type="number" class="form-control form-control-sm" id="distanceM"
|
||
placeholder="e.g. 100" min="1" step="1">
|
||
<div class="form-text">Required for MOA values</div>
|
||
</div>
|
||
<div class="mb-2">
|
||
<label class="form-label form-label-sm mb-1">
|
||
Reference length (mm) <span class="text-danger">*</span>
|
||
</label>
|
||
<input type="number" class="form-control form-control-sm" id="refLength"
|
||
placeholder="e.g. 100" min="1" step="0.5">
|
||
<div class="form-text">Real-world length of the line you'll draw</div>
|
||
</div>
|
||
<div>
|
||
<label class="form-label form-label-sm mb-1">Bullet ⌀ (mm)</label>
|
||
<input type="number" class="form-control form-control-sm" id="bulletDia"
|
||
placeholder="e.g. 7.62" min="0" step="0.1">
|
||
<div class="form-text">Draws bullet holes at correct scale</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Annotation buttons -->
|
||
<div class="input-section">
|
||
<h6 class="fw-semibold"><i class="bi bi-cursor me-1"></i>Annotation</h6>
|
||
<div class="d-grid gap-2 mb-2">
|
||
<button class="btn btn-sm btn-primary w-100" id="btnRef" onclick="btnRef()">
|
||
<i class="bi bi-rulers me-1"></i>① Draw reference line
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary w-100" id="btnPoa" onclick="btnPoa()">
|
||
<i class="bi bi-crosshair me-1"></i>② Set POA
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-secondary w-100" id="btnPoi" onclick="btnPoi()">
|
||
<i class="bi bi-plus-circle me-1"></i>③ Add POI (bullet holes)
|
||
</button>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-sm btn-outline-secondary flex-fill" onclick="btnUndo()">
|
||
<i class="bi bi-arrow-counterclockwise"></i> Undo
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-danger flex-fill" onclick="btnReset()">
|
||
<i class="bi bi-trash"></i> Reset
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Legend -->
|
||
<div class="input-section">
|
||
<h6 class="fw-semibold"><i class="bi bi-palette me-1"></i>Legend</h6>
|
||
<div class="small vstack gap-1">
|
||
<div><span class="legend-dot me-2" style="background:#3b82f6"></span>Reference line</div>
|
||
<div><span class="legend-dot me-2" style="background:#22c55e"></span>POA — Point of Aim</div>
|
||
<div><span class="legend-dot me-2" style="background:#ef4444"></span>POI — Bullet holes</div>
|
||
<div><span class="legend-dot me-2" style="background:#f97316"></span>Group centroid</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn btn-sm btn-outline-secondary w-100" onclick="loadNewPhoto()">
|
||
<i class="bi bi-image me-1"></i>Load different photo
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ══ Right: canvas + results ══════════════════════════════════════════ -->
|
||
<div class="col-md-8 col-xl-9">
|
||
|
||
<!-- Upload section -->
|
||
<div id="uploadSection">
|
||
<div class="upload-area" id="dropArea">
|
||
<i class="bi bi-image fs-1 d-block mb-3 opacity-40"></i>
|
||
<p class="fw-semibold mb-1">Drop your target photo here</p>
|
||
<p class="small mb-0 text-muted">or click to browse — JPEG, PNG, WebP</p>
|
||
<input type="file" id="fileInput" accept="image/jpeg,image/png,image/webp" class="d-none">
|
||
</div>
|
||
<div id="uploadError" class="alert alert-danger mt-2 d-none small"></div>
|
||
</div>
|
||
|
||
<!-- Annotation section -->
|
||
<div id="annotSection" class="d-none">
|
||
|
||
<!-- Status + linked badge -->
|
||
<div class="d-flex align-items-center gap-2 mb-2 flex-wrap">
|
||
<span id="statusMsg" class="text-primary small fw-semibold"></span>
|
||
<span id="linkedBadge" class="badge bg-success d-none"></span>
|
||
</div>
|
||
|
||
<!-- Canvas -->
|
||
<div id="canvasWrap" class="mb-3">
|
||
<canvas id="annotCanvas"></canvas>
|
||
</div>
|
||
|
||
<!-- Results -->
|
||
<div class="card border-0 shadow-sm">
|
||
<div class="card-body">
|
||
<h6 class="fw-semibold mb-2"><i class="bi bi-bar-chart-line me-1 text-primary"></i>Results</h6>
|
||
<p id="resultsHint" class="text-muted small mb-0">Draw a reference line on the image.</p>
|
||
<div id="resultsSection" class="d-none">
|
||
<table class="table table-sm mb-2">
|
||
<tbody>
|
||
<tr class="result-row">
|
||
<td>Shots (POIs)</td>
|
||
<td id="resPOICount">—</td>
|
||
</tr>
|
||
<tr class="result-row">
|
||
<td>Group size (ES)</td>
|
||
<td id="resGroupSize">—</td>
|
||
</tr>
|
||
<tr class="result-row">
|
||
<td>Mean radius</td>
|
||
<td id="resMeanRadius">—</td>
|
||
</tr>
|
||
<tr class="result-row">
|
||
<td><i class="bi bi-dot text-warning"></i> Group offset from POA</td>
|
||
<td id="resOffset">—</td>
|
||
</tr>
|
||
<tr class="result-row">
|
||
<td><i class="bi bi-arrow-right text-danger"></i> Correction needed</td>
|
||
<td id="resCorrection">—</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p id="resMoaNote" class="text-muted small d-none mb-2">
|
||
<i class="bi bi-info-circle me-1"></i>Enter the distance above to see MOA values.
|
||
</p>
|
||
<button id="saveBtn" class="btn btn-sm btn-outline-primary d-none" onclick="saveToApi()">
|
||
<i class="bi bi-floppy me-1"></i>Save POIs & compute server-side
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toastContainer"></div>
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="/js/api.js"></script>
|
||
<script src="/js/utils.js"></script>
|
||
<script src="/js/i18n.js"></script>
|
||
<script src="/js/nav.js"></script>
|
||
<script src="/js/group-size.js"></script>
|
||
</body>
|
||
</html>
|