Modal preview: click Preview opens fullscreen video player

Previously: clicking Preview in jobs list showed a small inline video
within the job card row.

Now: clicking Preview opens a centered fullscreen modal with:
- Large video player (up to 95vw × 85vh) — same experience as bottom
  live-preview but accessible from jobs list
- Auto-play, controls, native HTML5 video player
- Title shown below video for context
- Download button + Close button
- Click outside or ESC key to close
- Backdrop blur for focus

Removes the obsolete inline <video> element that was rendered hidden
in each job card. Body scroll locked while modal open.
This commit is contained in:
Sebastjan Artič 2026-04-29 13:21:36 +00:00
parent 4efd726176
commit 389c26d012

View File

@ -180,6 +180,89 @@
}
@keyframes spin { to { transform: rotate(360deg); } }
.progress-bar.smooth { transition: width 0.4s ease; }
/* ─── Video preview modal ─── */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.85);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
backdrop-filter: blur(4px);
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal-content {
position: relative;
max-width: 95vw;
max-height: 95vh;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.modal-content video {
max-width: 100%;
max-height: 85vh;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
background: black;
}
.modal-title {
color: #fff;
font-weight: 600;
text-align: center;
max-width: 600px;
padding: 0 12px;
font-size: 14px;
}
.modal-close {
position: absolute;
top: -8px;
right: -8px;
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--accent);
color: white;
border: none;
font-size: 20px;
font-weight: 700;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
transition: transform 0.15s ease;
z-index: 1;
}
.modal-close:hover { transform: scale(1.1); background: var(--accent-2); }
.modal-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
.modal-actions button {
padding: 10px 18px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--panel);
color: var(--text);
font-weight: 500;
cursor: pointer;
font-size: 14px;
}
.modal-actions button.primary {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.modal-actions button:hover { background: var(--panel-2); }
.modal-actions button.primary:hover { background: var(--accent-2); }
</style>
</head>
<body>
@ -687,7 +770,7 @@
const actions = [];
if (job.status === "done") {
actions.push(`<button class="small" onclick="window.open('/api/download/${job.id}')">⬇ Download</button>`);
actions.push(`<button class="small ghost" onclick="previewJob('${job.id}')">▶ Preview</button>`);
actions.push(`<button class="small ghost" onclick="previewJob('${job.id}', ${JSON.stringify(title)})">▶ Preview</button>`);
}
actions.push(`<button class="small ghost" onclick="deleteJob('${job.id}')"></button>`);
@ -706,7 +789,6 @@
${job.lang ? `<span>${job.lang}</span>` : ""}
</div>
<div class="actions">${actions.join("")}</div>
${job.status === "done" ? `<video id="video-${job.id}" class="hidden" controls></video>` : ""}
`;
return el;
}
@ -717,11 +799,55 @@
refreshJobs();
}
function previewJob(id) {
const v = document.getElementById(`video-${id}`);
v.src = `/api/preview/${id}`;
v.classList.remove("hidden");
v.play();
function previewJob(id, title) {
// Odpre velik modal z reel videom
const overlay = document.createElement("div");
overlay.className = "modal-overlay";
overlay.innerHTML = `
<div class="modal-content" onclick="event.stopPropagation()">
<button class="modal-close" title="Zapri (ESC)">×</button>
<video src="/api/preview/${id}" controls autoplay playsinline></video>
${title ? `<div class="modal-title">${title}</div>` : ""}
<div class="modal-actions">
<button class="primary" onclick="window.open('/api/download/${id}')">⬇ Prenesi reel</button>
<button onclick="closeModal()">Zapri</button>
</div>
</div>
`;
const close = () => closeModal();
overlay.addEventListener("click", close);
overlay.querySelector(".modal-close").addEventListener("click", (e) => {
e.stopPropagation();
close();
});
// ESC key to close
const escHandler = (e) => {
if (e.key === "Escape") {
close();
document.removeEventListener("keydown", escHandler);
}
};
document.addEventListener("keydown", escHandler);
document.body.appendChild(overlay);
document.body.style.overflow = "hidden"; // prevent scroll behind modal
}
function closeModal() {
const overlay = document.querySelector(".modal-overlay");
if (overlay) {
// Stop video before removing (prevents memory leak)
const video = overlay.querySelector("video");
if (video) {
video.pause();
video.removeAttribute("src");
video.load();
}
overlay.remove();
document.body.style.overflow = "";
}
}
refreshJobs();