252 lines
7.4 KiB
HTML
252 lines
7.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>TeslaCam Video Player</title>
|
|
<link
|
|
rel="stylesheet"
|
|
href="{{ url_for('static', filename='css/style.css') }}"
|
|
/>
|
|
</head>
|
|
<body>
|
|
<!-- Header -->
|
|
<header>
|
|
<div class="brand">
|
|
<span class="logo" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24">
|
|
<path
|
|
d="M12 3C7 3 3.2 4.4 3.2 4.4l.9 1.7s3.1-1 5-.9l.7 1.3C7.9 7.7 6 9 6 9s.6 2.4 3 4.6c2.5-2.2 3-4.6 3-4.6s-1.9-1.3-3.7-1.5l.6-1.3c1.9-.1 5 .9 5 .9l.9-1.7S17 3 12 3z"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
<div>
|
|
TeslaCam Player
|
|
<small>Dashcam & Sentry Archive</small>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Layout: single column, centered -->
|
|
<div class="layout">
|
|
<!-- Region filter bar -->
|
|
<nav class="region-bar" id="regionBar">
|
|
<span class="region-label">Shortcuts:</span>
|
|
<button
|
|
class="region-btn"
|
|
id="firstBtn"
|
|
title="First clip"
|
|
aria-label="First clip"
|
|
>
|
|
Start
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Video Player -->
|
|
<section class="player-panel">
|
|
<div class="video-stage">
|
|
{% set src = "src='" ~ default_url ~ "'" if default_url else '' %} {%
|
|
set hidden = 'hidden' if not default_url else '' %}
|
|
<video
|
|
id="player"
|
|
controls
|
|
preload="metadata"
|
|
playsinline
|
|
{{src}}
|
|
></video>
|
|
<canvas id="noiseCanvas" class="noise-canvas"></canvas>
|
|
<div class="video-badge" id="videoBadge">
|
|
<span class="dot"></span><span id="badgeText">Front</span>
|
|
</div>
|
|
<div class="video-placeholder" id="videoPlaceholder" {{hidden}}>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="1.5"
|
|
>
|
|
<rect x="2" y="6" width="14" height="12" rx="2" />
|
|
<path d="M16 10l6-3v10l-6-3" />
|
|
</svg>
|
|
<div>No front camera footage found</div>
|
|
</div>
|
|
</div>
|
|
{% set phidden = 'hidden' if not default_url else '' %}
|
|
<div class="now-playing" id="nowPlaying" {{phidden}}>
|
|
<button
|
|
class="nav-btn"
|
|
id="prevBtn"
|
|
title="Previous clip (←)"
|
|
aria-label="Previous clip"
|
|
>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<polyline points="15 18 9 12 15 6" />
|
|
</svg>
|
|
</button>
|
|
<span
|
|
>Now Playing:
|
|
<strong id="npName"
|
|
>{% if default_url %}Front — oldest clip{% else %}—{% endif
|
|
%}</strong
|
|
></span
|
|
>
|
|
<span class="ts" id="npTime">—</span>
|
|
<span class="loc" id="npLocation"></span>
|
|
<button
|
|
class="nav-btn"
|
|
id="nextBtn"
|
|
title="Next clip (→)"
|
|
aria-label="Next clip"
|
|
>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<polyline points="9 18 15 12 9 6" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Camera Angle Selector: 3-column grid around Model Y graphic -->
|
|
<section class="cam-selector">
|
|
<h2 class="cs-heading">Camera Angles</h2>
|
|
<div class="cs-grid">
|
|
{% for col in camera_list %}
|
|
<div class="cs-col">
|
|
{% for cam in col %} {% set disabled = 'disabled' if not
|
|
cam.first_url else '' %}
|
|
<button
|
|
class="cs-btn{% if cam.key == 'front' %} active{% endif %}"
|
|
data-cam="{{ cam.key }}"
|
|
data-url="{{ cam.first_url or '' }}"
|
|
{{
|
|
disabled
|
|
}}
|
|
title="{{ cam.display }}{% if not cam.first_url %} — no footage{% endif %}"
|
|
>
|
|
<span class="cs-btn-label">{{ cam.display }}</span>
|
|
<span class="cs-btn-count">{{ cam.count }}</span>
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Model Y Top-Down SVG -->
|
|
<div class="car-graphic" aria-hidden="true">
|
|
<svg viewBox="0 0 200 400" class="car-svg">
|
|
<!-- Car body -->
|
|
<rect
|
|
x="55"
|
|
y="80"
|
|
width="90"
|
|
height="240"
|
|
rx="30"
|
|
ry="30"
|
|
fill="none"
|
|
stroke="var(--text-muted)"
|
|
stroke-width="2"
|
|
/>
|
|
<!-- Windshield -->
|
|
<line
|
|
x1="65"
|
|
y1="110"
|
|
x2="135"
|
|
y2="110"
|
|
stroke="var(--text-muted)"
|
|
stroke-width="1.5"
|
|
/>
|
|
<!-- Back view -->
|
|
<line
|
|
x1="65"
|
|
y1="290"
|
|
x2="135"
|
|
y2="290"
|
|
stroke="var(--text-muted)"
|
|
stroke-width="1.5"
|
|
/>
|
|
<!-- Front view camera dot -->
|
|
<circle cx="100" cy="78" r="5" fill="var(--accent)" />
|
|
<!-- Back view camera dot -->
|
|
<circle cx="100" cy="322" r="5" fill="var(--accent)" />
|
|
<!-- Left repeater dot -->
|
|
<circle cx="53" cy="130" r="4" fill="var(--accent)" />
|
|
<!-- Right repeater dot -->
|
|
<circle cx="147" cy="130" r="4" fill="var(--accent)" />
|
|
<!-- Left pillar dot -->
|
|
<circle cx="53" cy="200" r="4" fill="var(--accent)" />
|
|
<!-- Right pillar dot -->
|
|
<circle cx="147" cy="200" r="4" fill="var(--accent)" />
|
|
<!-- Labels -->
|
|
<text
|
|
x="100"
|
|
y="68"
|
|
text-anchor="middle"
|
|
fill="var(--text-muted)"
|
|
font-size="9"
|
|
>
|
|
Front
|
|
</text>
|
|
<text
|
|
x="100"
|
|
y="342"
|
|
text-anchor="middle"
|
|
fill="var(--text-muted)"
|
|
font-size="9"
|
|
>
|
|
Back
|
|
</text>
|
|
<text
|
|
x="42"
|
|
y="134"
|
|
text-anchor="end"
|
|
fill="var(--text-muted)"
|
|
font-size="8"
|
|
>
|
|
L Rep
|
|
</text>
|
|
<text
|
|
x="158"
|
|
y="134"
|
|
text-anchor="start"
|
|
fill="var(--text-muted)"
|
|
font-size="8"
|
|
>
|
|
R Rep
|
|
</text>
|
|
<text
|
|
x="42"
|
|
y="204"
|
|
text-anchor="end"
|
|
fill="var(--text-muted)"
|
|
font-size="8"
|
|
>
|
|
L Pil
|
|
</text>
|
|
<text
|
|
x="158"
|
|
y="204"
|
|
text-anchor="start"
|
|
fill="var(--text-muted)"
|
|
font-size="8"
|
|
>
|
|
R Pil
|
|
</text>
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Camera metadata loaded via /api/videos by app.js -->
|
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
|
</body>
|
|
</html>
|