463 lines
23 KiB
HTML
463 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Sessions – 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>
|
||
.session-item { cursor: pointer; border-radius: 6px; padding: .5rem .75rem; }
|
||
.session-item:hover { background: #f0f4ff; }
|
||
.session-item.active { background: #0d6efd; color: #fff; }
|
||
.session-item.active .text-muted { color: rgba(255,255,255,.7) !important; }
|
||
#rightPanel { min-height: 60vh; }
|
||
.stage-card { border-left: 3px solid #dee2e6; }
|
||
.stage-card.has-results { border-left-color: #198754; }
|
||
.phase-label { font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
|
||
.corr-field { max-width: 90px; }
|
||
.photo-thumb { width: 80px; height: 60px; object-fit: cover; border-radius: 4px; cursor: pointer; }
|
||
.btn-xs { font-size: .75rem; padding: .1rem .35rem; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="navbar"></div>
|
||
|
||
<div class="container-fluid py-4">
|
||
<div class="container mb-3 d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||
<div>
|
||
<h2 class="fw-bold mb-0"><i class="bi bi-activity me-2"></i><span data-i18n="sessions.title">Shooting Sessions</span></h2>
|
||
<p class="text-muted small mb-0" data-i18n="sessions.subtitle">Prepare and record PRS, free practice, or speed shooting sessions.</p>
|
||
</div>
|
||
<button class="btn btn-primary" id="newSessionBtn">
|
||
<i class="bi bi-plus-lg me-1"></i><span data-i18n="sessions.new">New Session</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Type tabs -->
|
||
<div class="container mb-3">
|
||
<ul class="nav nav-tabs" id="sessionTabs">
|
||
<li class="nav-item">
|
||
<a class="nav-link active" href="#" data-type="prs">
|
||
<i class="bi bi-trophy me-1"></i><span data-i18n="sessions.tab.prs">PRS Match</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="#" data-type="free-practice">
|
||
<i class="bi bi-bullseye me-1"></i><span data-i18n="sessions.tab.fp">Free Practice</span>
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="#" data-type="speed-shooting">
|
||
<i class="bi bi-lightning me-1"></i><span data-i18n="sessions.tab.speed">Speed Shooting</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="row g-0" style="min-height: 70vh;">
|
||
|
||
<!-- ── Left: session list ─────────────────────────────────────────────── -->
|
||
<div class="col-md-3 border-end px-3">
|
||
<div id="listSpinner" class="text-center py-3">
|
||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||
</div>
|
||
<div id="listEmpty" class="text-muted small d-none" data-i18n="sessions.empty">No sessions yet.</div>
|
||
<div id="sessionsList"></div>
|
||
</div>
|
||
|
||
<!-- ── Right: session detail ──────────────────────────────────────────── -->
|
||
<div class="col-md-9 px-4" id="rightPanel">
|
||
|
||
<div class="text-center text-muted py-5" id="noSessionMsg">
|
||
<i class="bi bi-arrow-left fs-3 d-block mb-2"></i>
|
||
<span data-i18n="sessions.none">Select a session or create a new one</span>
|
||
</div>
|
||
|
||
<div id="sessionDetail" class="d-none">
|
||
|
||
<!-- Header -->
|
||
<div class="d-flex justify-content-between align-items-start mb-3 flex-wrap gap-2">
|
||
<div>
|
||
<h4 class="fw-bold mb-0" id="detailTitle"></h4>
|
||
<span class="text-muted small" id="detailMeta"></span>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-sm btn-outline-secondary" id="togglePublicBtn" title="Make public/private">
|
||
<i class="bi bi-globe me-1" id="togglePublicIcon"></i><span id="togglePublicLabel">Public</span>
|
||
</button>
|
||
<button class="btn btn-sm btn-outline-danger" id="deleteSessionBtn">
|
||
<i class="bi bi-trash me-1"></i><span data-i18n="btn.delete">Delete</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Session meta card -->
|
||
<div class="card border-0 shadow-sm mb-3" id="metaCard">
|
||
<div class="card-body">
|
||
<div class="row g-2 small" id="metaFields"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Weather (collapsible) -->
|
||
<div class="mb-3">
|
||
<a class="text-decoration-none text-secondary small fw-semibold" data-bs-toggle="collapse" href="#weatherCollapse">
|
||
<i class="bi bi-cloud-sun me-1"></i><span data-i18n="sessions.weather.title">Weather conditions</span> <i class="bi bi-chevron-down"></i>
|
||
</a>
|
||
<div class="collapse mt-2" id="weatherCollapse">
|
||
<div class="card border-0 bg-light">
|
||
<div class="card-body">
|
||
<div class="row g-2" id="weatherFields"></div>
|
||
<button class="btn btn-sm btn-outline-primary mt-2" id="saveWeatherBtn">
|
||
<i class="bi bi-floppy me-1"></i><span data-i18n="sessions.weather.save">Save weather</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Linked analysis -->
|
||
<div class="mb-3">
|
||
<h6 class="fw-semibold text-secondary small mb-2">
|
||
<i class="bi bi-speedometer2 me-1"></i><span data-i18n="sessions.analysis.title">Linked analysis</span>
|
||
</h6>
|
||
<div class="card border-0 bg-light">
|
||
<div class="card-body" id="analysisBody">
|
||
<!-- filled by renderAnalysisSection() -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PRS stages section -->
|
||
<div id="stagesSection" class="d-none">
|
||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||
<h5 class="fw-semibold mb-0"><i class="bi bi-list-ol me-2"></i><span data-i18n="sessions.stages.title">Stages</span></h5>
|
||
<button class="btn btn-sm btn-outline-primary" id="addStageBtn">
|
||
<i class="bi bi-plus-lg me-1"></i><span data-i18n="sessions.stages.add">Add Stage</span>
|
||
</button>
|
||
</div>
|
||
<div id="stagesList"></div>
|
||
</div>
|
||
|
||
<!-- Free practice / speed shooting summary -->
|
||
<div id="genericDetail"></div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Create Session Modal ──────────────────────────────────────────────── -->
|
||
<div class="modal fade" id="createModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="createModalTitle"><i class="bi bi-plus-lg me-2"></i><span data-i18n="sessions.new">New Session</span></h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.name">Session name</label>
|
||
<input type="text" class="form-control" id="createName" placeholder="e.g. Range day – 308">
|
||
</div>
|
||
<div class="row g-2 mb-3">
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.date">Date</label>
|
||
<input type="date" class="form-control" id="createDate">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.location">Location</label>
|
||
<input type="text" class="form-control" id="createLocation" placeholder="Range name">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PRS-specific fields -->
|
||
<div id="prsFields" class="d-none">
|
||
<div class="row g-2 mb-3">
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.competition">Competition name</label>
|
||
<input type="text" class="form-control" id="createCompetition" placeholder="e.g. PRS Open Bretagne">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.category">Category</label>
|
||
<input type="text" class="form-control" id="createCategory" placeholder="e.g. Production">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Free practice fields -->
|
||
<div id="freePracticeFields" class="d-none">
|
||
<div class="row g-2 mb-3">
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.distance">Distance (m)</label>
|
||
<input type="number" class="form-control" id="createDistance" min="1" placeholder="100">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.target">Target description</label>
|
||
<input type="text" class="form-control" id="createTarget" placeholder="e.g. A4 paper">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Speed shooting fields -->
|
||
<div id="speedFields" class="d-none">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.format">Format</label>
|
||
<input type="text" class="form-control" id="createFormat" placeholder="e.g. IPSC, IDPA">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.rig">Rig</label>
|
||
<select class="form-select" id="createRig">
|
||
<option value="">— None —</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.create.ammo">Ammo</label>
|
||
<div class="btn-group btn-group-sm w-100 mb-2" role="group">
|
||
<input type="radio" class="btn-check" name="ammoType" id="ammoNone" value="none" checked>
|
||
<label class="btn btn-outline-secondary" for="ammoNone" data-i18n="sessions.create.ammo.none">None</label>
|
||
<input type="radio" class="btn-check" name="ammoType" id="ammoFactory" value="factory">
|
||
<label class="btn btn-outline-secondary" for="ammoFactory" data-i18n="sessions.create.ammo.factory">Factory</label>
|
||
<input type="radio" class="btn-check" name="ammoType" id="ammoReload" value="reload">
|
||
<label class="btn btn-outline-secondary" for="ammoReload" data-i18n="sessions.create.ammo.reload">Reloaded batch</label>
|
||
</div>
|
||
<select class="form-select d-none" id="createAmmo"></select>
|
||
<select class="form-select d-none" id="createBatch"></select>
|
||
<!-- Suggest ammo link (shown when factory is selected) -->
|
||
<div id="suggestAmmoWrap" class="d-none mt-2">
|
||
<a href="#" id="toggleSuggestAmmo" class="small text-primary">
|
||
<i class="bi bi-plus-circle me-1"></i><span data-i18n="sessions.create.ammo.suggest">Can't find your ammo? Suggest it</span>
|
||
</a>
|
||
<div id="suggestAmmoForm" class="d-none mt-2 p-3 border rounded bg-light small">
|
||
<p class="text-muted mb-2" data-i18n="sessions.create.ammo.suggest.note">Your suggestion will be visible to all once an admin verifies it.</p>
|
||
<div class="row g-2 mb-2">
|
||
<div class="col"><input type="text" class="form-control form-control-sm" id="saBrand" placeholder="Brand"></div>
|
||
<div class="col"><input type="text" class="form-control form-control-sm" id="saName" placeholder="Name / model"></div>
|
||
</div>
|
||
<div class="row g-2 mb-2">
|
||
<div class="col"><select class="form-select form-select-sm" id="saCaliber"><option value="">Loading…</option></select></div>
|
||
<div class="col"><input type="number" class="form-control form-control-sm" id="saBulletWeight" placeholder="Bullet weight (gr) *" step="0.1" min="0"></div>
|
||
</div>
|
||
<div class="mb-2">
|
||
<select class="form-select form-select-sm" id="saBulletType">
|
||
<option value="">Bullet type *</option>
|
||
<option value="FMJ">Full Metal Jacket (FMJ)</option>
|
||
<option value="HP">Hollow Point (HP)</option>
|
||
<option value="BTHP">Boat Tail Hollow Point (BTHP)</option>
|
||
<option value="SP">Soft Point (SP)</option>
|
||
<option value="HPBT">Hollow Point Boat Tail (HPBT)</option>
|
||
<option value="SMK">Sierra MatchKing (SMK)</option>
|
||
<option value="A_TIP">Hornady A-Tip</option>
|
||
<option value="MONO">Monolithic / Solid</option>
|
||
</select>
|
||
</div>
|
||
<div id="saAlert" class="alert alert-danger d-none py-1 small mb-2"></div>
|
||
<button class="btn btn-sm btn-primary" id="submitSuggestAmmo">
|
||
<i class="bi bi-send me-1"></i>Submit suggestion
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Analysis -->
|
||
<div class="mb-2">
|
||
<label class="form-label fw-semibold">Chronograph analysis</label>
|
||
<div class="btn-group btn-group-sm w-100 mb-2" role="group">
|
||
<input type="radio" class="btn-check" name="analysisMode" id="analysisModeNone" value="none" checked>
|
||
<label class="btn btn-outline-secondary" for="analysisModeNone">None</label>
|
||
<input type="radio" class="btn-check" name="analysisMode" id="analysisModeLink" value="link">
|
||
<label class="btn btn-outline-secondary" for="analysisModeLink">Link existing</label>
|
||
<input type="radio" class="btn-check" name="analysisMode" id="analysisModeUpload" value="upload">
|
||
<label class="btn btn-outline-secondary" for="analysisModeUpload"><i class="bi bi-upload me-1"></i>Upload CSV</label>
|
||
</div>
|
||
|
||
<!-- Link existing -->
|
||
<select class="form-select d-none" id="createAnalysisPicker"></select>
|
||
|
||
<!-- Upload new -->
|
||
<div id="createAnalysisUpload" class="d-none border rounded p-3 bg-light small">
|
||
<div class="mb-2">
|
||
<label class="form-label mb-1">CSV file <span class="text-danger">*</span></label>
|
||
<input type="file" class="form-control form-control-sm" id="createAnalysisCsv" accept=".csv,.txt">
|
||
</div>
|
||
<div class="mb-2">
|
||
<label class="form-label mb-1">Analysis name <span class="text-muted fw-normal">(optional — defaults to session name)</span></label>
|
||
<input type="text" class="form-control form-control-sm" id="createAnalysisName" placeholder="e.g. 308 match">
|
||
</div>
|
||
<div class="row g-2 mb-2">
|
||
<div class="col">
|
||
<label class="form-label mb-1">Chronograph model</label>
|
||
<select class="form-select form-select-sm" id="createAnalysisChronoType">
|
||
<option value="garmin_xero_c1_pro">Garmin Xero C1 Pro</option>
|
||
</select>
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label mb-1">Velocity unit</label>
|
||
<div class="btn-group btn-group-sm w-100" role="group">
|
||
<input type="radio" class="btn-check" name="createAnalysisVelUnit" id="caVelFps" value="fps" checked>
|
||
<label class="btn btn-outline-secondary" for="caVelFps">fps</label>
|
||
<input type="radio" class="btn-check" name="createAnalysisVelUnit" id="caVelMps" value="mps">
|
||
<label class="btn btn-outline-secondary" for="caVelMps">m/s</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="createAlert" class="alert alert-danger d-none small"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn.cancel">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="createSubmitBtn">
|
||
<span id="createSpinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
|
||
<span data-i18n="btn.submit">Create</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Add Stage Modal (PRS) ─────────────────────────────────────────────── -->
|
||
<div class="modal fade" id="addStageModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title"><i class="bi bi-plus-lg me-2"></i><span data-i18n="sessions.stage.modal.title">Add Stage</span></h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="row g-2 mb-3">
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.order">Stage #</label>
|
||
<input type="number" class="form-control" id="stageOrder" min="1" value="1">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.position">Position</label>
|
||
<select class="form-select" id="stagePosition">
|
||
<option value="PRONE">Prone</option>
|
||
<option value="STANDING">Standing</option>
|
||
<option value="SITTING">Sitting</option>
|
||
<option value="KNEELING">Kneeling</option>
|
||
<option value="BARRICADE">Barricade</option>
|
||
<option value="UNSUPPORTED">Unsupported</option>
|
||
<option value="OTHER">Other</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="row g-2 mb-3">
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.distance">Distance (m)</label>
|
||
<input type="number" class="form-control" id="stageDistance" min="1" placeholder="300">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.maxtime">Max time (s)</label>
|
||
<input type="number" class="form-control" id="stageMaxTime" min="1" placeholder="90">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.shots">Shots</label>
|
||
<input type="number" class="form-control" id="stageShots" min="1" value="1">
|
||
</div>
|
||
</div>
|
||
<div class="row g-2 mb-3">
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.tw">Target W (cm)</label>
|
||
<input type="number" class="form-control" id="stageTargetW" step="0.1" placeholder="20">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.th">Target H (cm)</label>
|
||
<input type="number" class="form-control" id="stageTargetH" step="0.1" placeholder="30">
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold" data-i18n="sessions.stage.modal.notes">Prep notes</label>
|
||
<textarea class="form-control" id="stageNotePrep" rows="2" placeholder="Wind call, dope…"></textarea>
|
||
</div>
|
||
<div id="stageAlert" class="alert alert-danger d-none small"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="btn.cancel">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="stageSubmitBtn" data-i18n="sessions.stages.add">Add Stage</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Session Photo Upload Modal ───────────────────────────────────────── -->
|
||
<div class="modal fade" id="sessionPhotoModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title"><i class="bi bi-image me-2"></i>Add photo</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold">Photo</label>
|
||
<input type="file" class="form-control" id="sessionPhotoFileInput" accept="image/jpeg,image/png,image/webp">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold">Caption <span class="text-muted fw-normal">(optional)</span></label>
|
||
<input type="text" class="form-control" id="sessionPhotoCaption" placeholder="e.g. 100m cold bore">
|
||
</div>
|
||
<div id="sessionPhotoAlert" class="alert alert-danger d-none small"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="sessionPhotoSubmitBtn">
|
||
<span id="sessionPhotoSpinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
|
||
Upload
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Group Photo Modal ─────────────────────────────────────────────────── -->
|
||
<div class="modal fade" id="groupPhotoModal" tabindex="-1">
|
||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||
<div class="modal-content bg-dark border-0">
|
||
<div class="modal-header border-0 py-2 px-3">
|
||
<span class="text-white fw-semibold" id="gpModalTitle"></span>
|
||
<div class="d-flex gap-2 ms-auto me-2">
|
||
<a id="gpModalMeasureBtn" href="#" class="btn btn-sm btn-outline-light">
|
||
<i class="bi bi-crosshair2 me-1"></i>Measure
|
||
</a>
|
||
<a id="gpModalOpenBtn" href="#" target="_blank" class="btn btn-sm btn-outline-light">
|
||
<i class="bi bi-box-arrow-up-right"></i>
|
||
</a>
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<!-- Stats bar + unit switch -->
|
||
<div class="px-3 py-2 border-top border-secondary d-flex flex-wrap align-items-center gap-2">
|
||
<div id="gpModalStats" class="small text-white-50 d-flex flex-wrap gap-3 flex-grow-1"></div>
|
||
<div class="btn-group btn-group-sm ms-auto flex-shrink-0" id="gpModalUnitBtns">
|
||
<button class="btn btn-outline-light" data-dist-unit="mm">mm</button>
|
||
<button class="btn btn-outline-light" data-dist-unit="moa">MOA</button>
|
||
<button class="btn btn-outline-light" data-dist-unit="mrad">MRAD</button>
|
||
</div>
|
||
</div>
|
||
<!-- Photo -->
|
||
<div class="text-center" style="max-height:75vh;overflow:hidden;background:#111">
|
||
<img id="gpModalImg" src="" alt="" style="max-width:100%;max-height:75vh;object-fit:contain">
|
||
</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/calibers.js"></script>
|
||
<script src="/js/nav.js"></script>
|
||
<script src="/js/sessions.js"></script>
|
||
</body>
|
||
</html>
|