Checklist styling update #53
+34
-22
@@ -44,11 +44,6 @@
|
|||||||
.navbar-toggler-icon {
|
.navbar-toggler-icon {
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
/* New styles for table */
|
|
||||||
.table-hover tbody tr:hover {
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light page-loading">
|
<body class="bg-light page-loading">
|
||||||
@@ -90,25 +85,42 @@
|
|||||||
|
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<h2>Checklist</h2>
|
<h2>Checklist</h2>
|
||||||
<div id="watched-ratio" class="ratio"></div>
|
|
||||||
<h3 class="fs-6" style="margin-bottom: -1px;">Temps avant les Oscars (<span id="oscar-date-day-month">—</span> <span id="oscar-year">—</span>) - <span id="time-left"></span></h3>
|
<!-- Summary card (match Stats page spacing/structure) -->
|
||||||
<h3 class="fs-6"><span id="movies-per-day"></span></h3>
|
<div class="card shadow-sm mt-3">
|
||||||
<div class="progress my-3">
|
<div class="card-body">
|
||||||
<div id="progress-bar" class="progress-bar" role="progressbar" style="width: 0%; background-color: red;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
<div class="d-flex flex-column gap-1">
|
||||||
|
<div id="watched-ratio" class="fw-semibold">—</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
Temps avant les Oscars (<span id="oscar-date-day-month">—</span> <span id="oscar-year">—</span>) — <span id="time-left">—</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small"><span id="movies-per-day"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress mt-3" style="height: 10px;">
|
||||||
|
<div id="progress-bar" class="progress-bar" role="progressbar" style="width: 0%; background-color: red;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-busy="true"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Table for movies -->
|
<!-- Table for movies -->
|
||||||
<table class="table table-hover movie-table">
|
<div class="table-responsive mt-3">
|
||||||
<thead>
|
<table class="table table-striped table-hover align-middle movie-table mb-0">
|
||||||
<tr>
|
<thead>
|
||||||
<th scope="col">Vu</th>
|
<tr>
|
||||||
<th scope="col">Titre</th>
|
<th scope="col">Vu</th>
|
||||||
<th scope="col">Date</th>
|
<th scope="col">Titre</th>
|
||||||
</tr>
|
<th scope="col">Date</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="movie-table-body">
|
</thead>
|
||||||
<!-- Movie rows will be inserted here -->
|
<tbody id="movie-table-body">
|
||||||
</tbody>
|
<tr>
|
||||||
</table>
|
<td colspan="3" class="text-center py-4 text-muted" aria-live="polite" aria-busy="true">Chargement…</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -164,4 +164,23 @@ footer {
|
|||||||
|
|
||||||
.app-version-list .list-group-item:last-child {
|
.app-version-list .list-group-item:last-child {
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Movies page helpers (Bootstrap-first, minimal custom) */
|
||||||
|
.movie-poster {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 360px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imdb-icon {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watched-badge {
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
@@ -453,12 +453,9 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
<td id="watched-date-${movie.imdb_id}">${watchedDate}</td>
|
<td id="watched-date-${movie.imdb_id}">${watchedDate}</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Set initial row color based on watched state
|
// Use Bootstrap table state classes (matches Stats page styling better than custom backgrounds)
|
||||||
if (watchedMovie) {
|
movieRow.classList.toggle('table-success', Boolean(watchedMovie));
|
||||||
movieRow.style.backgroundColor = 'rgba(144, 238, 144, 0.6)'; // Lighter light green with transparency
|
movieRow.classList.toggle('table-danger', !watchedMovie);
|
||||||
} else {
|
|
||||||
movieRow.style.backgroundColor = 'rgba(255, 99, 71, 0.6)'; // Lighter light red with transparency
|
|
||||||
}
|
|
||||||
|
|
||||||
movieTableBody.appendChild(movieRow);
|
movieTableBody.appendChild(movieRow);
|
||||||
|
|
||||||
@@ -478,10 +475,16 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
watchedDateCell.textContent = new Date().toLocaleString();
|
watchedDateCell.textContent = new Date().toLocaleString();
|
||||||
row.style.backgroundColor = 'rgba(144, 238, 144, 0.6)'; // Apply light green with transparency
|
if (row) {
|
||||||
|
row.classList.add('table-success');
|
||||||
|
row.classList.remove('table-danger');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
watchedDateCell.textContent = '';
|
watchedDateCell.textContent = '';
|
||||||
row.style.backgroundColor = 'rgba(255, 99, 71, 0.6)'; // Apply light red with transparency
|
if (row) {
|
||||||
|
row.classList.add('table-danger');
|
||||||
|
row.classList.remove('table-success');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch('/api/movies/users/updateWatchedMovies', {
|
await fetch('/api/movies/users/updateWatchedMovies', {
|
||||||
|
|||||||
+22
-13
@@ -359,22 +359,31 @@ window.addEventListener('DOMContentLoaded', async function () {
|
|||||||
: (hasLegacy ? movie.vod_link : (movie && movie._id ? `/player.html?id=${encodeURIComponent(movie._id)}` : '#'));
|
: (hasLegacy ? movie.vod_link : (movie && movie._id ? `/player.html?id=${encodeURIComponent(movie._id)}` : '#'));
|
||||||
|
|
||||||
const movieDiv = document.createElement('div');
|
const movieDiv = document.createElement('div');
|
||||||
movieDiv.classList.add('col-md-4', 'mb-4', 'movie-card');
|
movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card');
|
||||||
movieDiv.setAttribute('data-imdb-id', movie.imdb_id);
|
movieDiv.setAttribute('data-imdb-id', movie.imdb_id);
|
||||||
movieDiv.innerHTML = `
|
movieDiv.innerHTML = `
|
||||||
<div class="card">
|
<div class="card h-100 shadow-sm">
|
||||||
${isChecked ? '<div class="watched-banner">VISIONNÉ</div>' : ''}
|
${isChecked ? '<span class="badge bg-success position-absolute top-0 start-0 m-2 watched-badge">VISIONNÉ</span>' : ''}
|
||||||
<a href="${playerUrl}" target="_self" rel="noopener noreferrer">
|
<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none">
|
||||||
<img src="${movie.poster}" class="card-img-top" alt="${movie.title}" style="width: 75%; display: block; margin: 0 auto; padding: 10px;">
|
<div class="text-center pt-3 px-3">
|
||||||
|
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="card-body">
|
<div class="card-body d-flex flex-column">
|
||||||
<h5 class="card-title d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-start gap-2">
|
||||||
<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none text-dark">${movie.title}</a>
|
<h5 class="card-title mb-1 flex-grow-1">
|
||||||
<span>⭐ ${movie.rating} <a href="https://www.imdb.com/title/${movie.imdb_id}/"><img src="/img/imdb.png"></a></span>
|
<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none text-dark">${movie.title}</a>
|
||||||
</h5>
|
</h5>
|
||||||
<p class="fw-bold fst-italic card-text" style="font-size: 0.75rem;">${movie.category}</p>
|
<div class="text-nowrap small text-muted">
|
||||||
<p class="card-text">${movie.description}</p>
|
⭐ ${movie.rating}
|
||||||
${isChecked ? `<p class="card-text"><strong>Regardé le:</strong> ${watchedDate}</p>` : ''}
|
<a href="https://www.imdb.com/title/${movie.imdb_id}/" target="_blank" rel="noopener noreferrer" aria-label="Voir sur IMDb">
|
||||||
|
<img src="/img/imdb.png" class="imdb-icon" alt="IMDb">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small fw-semibold fst-italic mb-2">${movie.category}</div>
|
||||||
|
<p class="card-text small flex-grow-1 mb-2">${movie.description}</p>
|
||||||
|
${isChecked ? `<div class="text-muted small"><span class="fw-semibold text-dark">Regardé le:</span> ${watchedDate}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
moviesList.appendChild(movieDiv);
|
moviesList.appendChild(movieDiv);
|
||||||
|
|||||||
@@ -7,6 +7,110 @@ function decodeJwt(token) {
|
|||||||
return JSON.parse(atob(padded));
|
return JSON.parse(atob(padded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createPageLoader(options = {}) {
|
||||||
|
const title = String(options.title || 'Chargement…');
|
||||||
|
let progress = 0;
|
||||||
|
let removed = false;
|
||||||
|
let navResizeObserver = null;
|
||||||
|
|
||||||
|
// Reuse a preloaded overlay if present (prevents initial "clear" flash).
|
||||||
|
const preloadedOverlay = document.getElementById('page-loader-overlay');
|
||||||
|
const overlay = preloadedOverlay || document.createElement('div');
|
||||||
|
if (!preloadedOverlay) {
|
||||||
|
overlay.className = 'page-loader-overlay';
|
||||||
|
overlay.setAttribute('role', 'status');
|
||||||
|
overlay.setAttribute('aria-live', 'polite');
|
||||||
|
overlay.setAttribute('aria-busy', 'true');
|
||||||
|
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="page-loader-card page-loader-card--baronly">
|
||||||
|
<span class="visually-hidden">${title}</span>
|
||||||
|
<div class="page-loader-progress" aria-hidden="true">
|
||||||
|
<div class="page-loader-bar" id="page-loader-bar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
const label = overlay.querySelector('.visually-hidden');
|
||||||
|
if (label) label.textContent = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
const barEl = () => overlay.querySelector('#page-loader-bar');
|
||||||
|
|
||||||
|
function clampPct(p) {
|
||||||
|
const n = Number(p);
|
||||||
|
if (!Number.isFinite(n)) return 0;
|
||||||
|
return Math.max(0, Math.min(100, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProgress(p) {
|
||||||
|
progress = clampPct(p);
|
||||||
|
const bar = barEl();
|
||||||
|
if (bar) bar.style.width = `${progress}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNavHeightPx() {
|
||||||
|
const nav = document.querySelector('nav.navbar');
|
||||||
|
if (!nav) return 0;
|
||||||
|
const rect = nav.getBoundingClientRect();
|
||||||
|
const h = Number(rect?.height) || 0;
|
||||||
|
return h > 0 ? Math.round(h) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOverlayTopOffset() {
|
||||||
|
overlay.style.setProperty('--page-loader-top', `${getNavHeightPx()}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureMounted() {
|
||||||
|
if (removed) return;
|
||||||
|
updateOverlayTopOffset();
|
||||||
|
document.body.classList.add('page-loading');
|
||||||
|
if (!overlay.isConnected) document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
if (!navResizeObserver) {
|
||||||
|
const nav = document.querySelector('nav.navbar');
|
||||||
|
if (nav && typeof ResizeObserver !== 'undefined') {
|
||||||
|
try {
|
||||||
|
navResizeObserver = new ResizeObserver(() => updateOverlayTopOffset());
|
||||||
|
navResizeObserver.observe(nav);
|
||||||
|
} catch (_) {
|
||||||
|
navResizeObserver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAndRemoveSoon() {
|
||||||
|
if (removed) return;
|
||||||
|
overlay.classList.add('page-loader-hide');
|
||||||
|
overlay.setAttribute('aria-busy', 'false');
|
||||||
|
document.body.classList.remove('page-loading');
|
||||||
|
window.setTimeout(() => {
|
||||||
|
removed = true;
|
||||||
|
if (navResizeObserver) {
|
||||||
|
try { navResizeObserver.disconnect(); } catch (_) {}
|
||||||
|
navResizeObserver = null;
|
||||||
|
}
|
||||||
|
try { overlay.remove(); } catch (_) {}
|
||||||
|
}, 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
setProgress(100);
|
||||||
|
hideAndRemoveSoon();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail() {
|
||||||
|
setProgress(Math.max(progress, 95));
|
||||||
|
window.setTimeout(() => hideAndRemoveSoon(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureMounted();
|
||||||
|
setProgress(8);
|
||||||
|
|
||||||
|
return { setProgress, done, fail };
|
||||||
|
}
|
||||||
|
|
||||||
function safeJsonParse(v) {
|
function safeJsonParse(v) {
|
||||||
try { return JSON.parse(v); } catch (_) { return null; }
|
try { return JSON.parse(v); } catch (_) { return null; }
|
||||||
}
|
}
|
||||||
@@ -754,12 +858,15 @@ function resolveMovieSource(movie) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.onload = async function () {
|
window.onload = async function () {
|
||||||
|
const pageLoader = createPageLoader({ title: 'Chargement du lecteur' });
|
||||||
|
|
||||||
// Basic auth UX: require a valid local token (like the rest of the app).
|
// Basic auth UX: require a valid local token (like the rest of the app).
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
pageLoader.setProgress(14);
|
||||||
|
|
||||||
// Load the active Oscar year in parallel with the movie fetch.
|
// Load the active Oscar year in parallel with the movie fetch.
|
||||||
const activeYearPromise = fetchActiveOscarYear();
|
const activeYearPromise = fetchActiveOscarYear();
|
||||||
@@ -782,6 +889,8 @@ window.onload = async function () {
|
|||||||
if (decoded.admin) {
|
if (decoded.admin) {
|
||||||
const adminLink = document.getElementById('admin-control-link');
|
const adminLink = document.getElementById('admin-control-link');
|
||||||
if (adminLink) adminLink.classList.remove('d-none');
|
if (adminLink) adminLink.classList.remove('d-none');
|
||||||
|
const adminStatus = document.getElementById('player-admin-status');
|
||||||
|
if (adminStatus) adminStatus.classList.remove('d-none');
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoffBtn = document.getElementById('log-off');
|
const logoffBtn = document.getElementById('log-off');
|
||||||
@@ -796,14 +905,17 @@ window.onload = async function () {
|
|||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
pageLoader.setProgress(26);
|
||||||
|
|
||||||
const id = getQueryParam('id');
|
const id = getQueryParam('id');
|
||||||
if (!id) {
|
if (!id) {
|
||||||
showAlert('Missing movie id.');
|
showAlert('Missing movie id.');
|
||||||
|
pageLoader.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
pageLoader.setProgress(36);
|
||||||
const res = await fetch(`/api/movies/${encodeURIComponent(id)}`, {
|
const res = await fetch(`/api/movies/${encodeURIComponent(id)}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : undefined,
|
headers: token ? { 'Authorization': `Bearer ${token}` } : undefined,
|
||||||
@@ -811,17 +923,20 @@ window.onload = async function () {
|
|||||||
const movie = await res.json().catch(() => ({}));
|
const movie = await res.json().catch(() => ({}));
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showAlert(movie?.message || movie?.error || `Failed to load movie (${res.status}).`);
|
showAlert(movie?.message || movie?.error || `Failed to load movie (${res.status}).`);
|
||||||
|
pageLoader.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeYear = await activeYearPromise;
|
const activeYear = await activeYearPromise;
|
||||||
setHeader(movie, { activeYear });
|
setHeader(movie, { activeYear });
|
||||||
|
pageLoader.setProgress(58);
|
||||||
|
|
||||||
const resolved = resolveMovieSource(movie);
|
const resolved = resolveMovieSource(movie);
|
||||||
if (!resolved?.src) {
|
if (!resolved?.src) {
|
||||||
setSourceLabel('—');
|
setSourceLabel('—');
|
||||||
hideAllPlayers();
|
hideAllPlayers();
|
||||||
showAlert('No playable source configured for this movie.');
|
showAlert('No playable source configured for this movie.');
|
||||||
|
pageLoader.fail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,10 +947,13 @@ window.onload = async function () {
|
|||||||
setSourceLabel('Legacy link');
|
setSourceLabel('Legacy link');
|
||||||
hideAllPlayers();
|
hideAllPlayers();
|
||||||
showAlertVariant('Legacy source: this link is not playable in the in-app player.', 'warning');
|
showAlertVariant('Legacy source: this link is not playable in the in-app player.', 'warning');
|
||||||
|
pageLoader.done();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageLoader.setProgress(70);
|
||||||
await playVodLink(resolved.src, { token });
|
await playVodLink(resolved.src, { token });
|
||||||
|
pageLoader.setProgress(82);
|
||||||
|
|
||||||
// Only the <video> player supports reliable progress tracking (mp4/hls).
|
// Only the <video> player supports reliable progress tracking (mp4/hls).
|
||||||
const videoEl = document.getElementById('video');
|
const videoEl = document.getElementById('video');
|
||||||
@@ -851,8 +969,11 @@ window.onload = async function () {
|
|||||||
} else {
|
} else {
|
||||||
setProgressUi({ state: 'info', text: '—', showText: true });
|
setProgressUi({ state: 'info', text: '—', showText: true });
|
||||||
}
|
}
|
||||||
|
pageLoader.setProgress(92);
|
||||||
|
pageLoader.done();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlert(err.message || 'Network error.');
|
showAlert(err.message || 'Network error.');
|
||||||
|
pageLoader.fail();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+27
-56
@@ -21,42 +21,9 @@
|
|||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for the 'WATCHED' banner */
|
|
||||||
.watched-banner {
|
|
||||||
background-color: green;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Centering checkbox and making card look better */
|
|
||||||
.movie-card {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.card-body {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#movies-list {
|
#movies-list {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for the filter button */
|
|
||||||
#filter-button {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style for the ratio display */
|
|
||||||
.ratio {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.navbar-toggler {
|
.navbar-toggler {
|
||||||
border-color: white; /* Make the border white */
|
border-color: white; /* Make the border white */
|
||||||
}
|
}
|
||||||
@@ -104,31 +71,35 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<h2>Films</h2>
|
||||||
<h2>Films</h2>
|
|
||||||
|
<!-- Summary card (match Statistiques/Checklist structure) -->
|
||||||
|
<div class="card shadow-sm mt-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-column gap-1">
|
||||||
|
<div class="text-muted small">
|
||||||
|
Dernière mise à jour des films: <span class="fw-semibold text-dark" id="movies-last-updated">—</span>
|
||||||
|
</div>
|
||||||
|
<div id="watched-ratio" class="fw-semibold">—</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
Temps avant les Oscars (<span id="oscar-date-day-month">—</span> <span id="oscar-year">—</span>) — <span id="time-left">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column flex-md-row align-items-start align-items-md-center justify-content-between gap-2 mt-3">
|
||||||
|
<div class="form-check form-switch m-0">
|
||||||
|
<input class="form-check-input" type="checkbox" id="flexSwitchCheckDefault">
|
||||||
|
<label class="form-check-label" for="flexSwitchCheckDefault">Films non regardés seulement</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">Clique sur l’affiche ou le titre pour ouvrir le lecteur.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Displaying last updated vod_links -->
|
<div id="movies-list" class="row g-3 mt-1">
|
||||||
<div class="alert alert-warning fade show" role="alert">
|
<div class="col-12">
|
||||||
Dernière mise à jour des films: <strong id="movies-last-updated">—</strong>
|
<div class="text-center py-4 text-muted" aria-live="polite" aria-busy="true">Chargement…</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Displaying the ratio of watched movies -->
|
|
||||||
<div id="watched-ratio" class="ratio">
|
|
||||||
<!-- This will display the ratio -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="fs-6">Temps avant les Oscars (<span id="oscar-date-day-month">—</span> <span id="oscar-year">—</span>) - <span id="time-left"></span></h3>
|
|
||||||
<h3 class="fs-6 mb-0">
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
<br>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" id="flexSwitchCheckDefault">
|
|
||||||
<label class="form-check-label" for="flexSwitchCheckDefault">Films non regardés seulement</label>
|
|
||||||
</div>
|
|
||||||
<div id="movies-list" class="row">
|
|
||||||
<!-- Movies will be displayed here -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+55
-31
@@ -102,7 +102,17 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light page-loading">
|
||||||
|
<!-- Preloaded loader overlay to avoid initial "clear" flash -->
|
||||||
|
<div id="page-loader-overlay" class="page-loader-overlay" data-preloaded="true" role="status" aria-live="polite" aria-busy="true">
|
||||||
|
<div class="page-loader-card page-loader-card--baronly">
|
||||||
|
<span class="visually-hidden">Chargement…</span>
|
||||||
|
<div class="page-loader-progress" aria-hidden="true">
|
||||||
|
<div class="page-loader-bar" id="page-loader-bar" style="width: 8%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Black banner with buttons and user info -->
|
<!-- Black banner with buttons and user info -->
|
||||||
<nav class="navbar navbar-expand-lg navbar-custom">
|
<nav class="navbar navbar-expand-lg navbar-custom">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -128,39 +138,53 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container mt-5 pb-4 player-wrap">
|
<div class="container mt-5 pb-4 player-wrap">
|
||||||
<div class="mb-3">
|
<div id="alert" class="alert alert-danger d-none" role="alert"></div>
|
||||||
<h2 id="movie-title" class="mb-1">Loading…</h2>
|
|
||||||
<div class="meta-muted">
|
<!-- Header / metadata -->
|
||||||
<span id="movie-category"></span>
|
<div class="card shadow-sm mt-3">
|
||||||
<span id="movie-rating"></span>
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-column flex-lg-row justify-content-between gap-3">
|
||||||
|
<div>
|
||||||
|
<h2 id="movie-title" class="mb-1">Chargement…</h2>
|
||||||
|
<div class="text-muted small">
|
||||||
|
<span id="movie-category"></span><span id="movie-rating"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin-only debug/status (Source + Progress) -->
|
||||||
|
<div id="player-admin-status" class="d-none d-flex flex-column gap-2">
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
<span class="badge bg-secondary">Source</span>
|
||||||
|
<span id="source-label" class="badge bg-light text-dark badge-src">—</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
<span class="badge bg-secondary">Progress</span>
|
||||||
|
<span id="progress-status" class="text-muted small progress-indicator">
|
||||||
|
<span id="progress-status-icon" class="progress-icon" aria-hidden="true"></span>
|
||||||
|
<span id="progress-status-text" class="progress-text">—</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="alert" class="alert alert-danger d-none" role="alert"></div>
|
<!-- Player surface -->
|
||||||
|
<div class="card shadow-sm mt-3">
|
||||||
<div class="player-surface">
|
<div class="card-body p-0">
|
||||||
<video id="video" class="d-none" controls playsinline></video>
|
<div class="player-surface">
|
||||||
<iframe
|
<video id="video" class="d-none" controls playsinline></video>
|
||||||
id="embed"
|
<iframe
|
||||||
class="d-none"
|
id="embed"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
class="d-none"
|
||||||
allowfullscreen
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
referrerpolicy="no-referrer-when-downgrade"
|
allowfullscreen
|
||||||
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-presentation"
|
referrerpolicy="no-referrer-when-downgrade"
|
||||||
></iframe>
|
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-presentation"
|
||||||
</div>
|
></iframe>
|
||||||
|
</div>
|
||||||
<div class="mt-3 d-flex flex-wrap gap-2 align-items-center">
|
</div>
|
||||||
<span class="badge bg-secondary">Source</span>
|
|
||||||
<span id="source-label" class="badge bg-light text-dark badge-src">—</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-2 d-flex flex-wrap gap-2 align-items-center">
|
|
||||||
<span class="badge bg-secondary">Progress</span>
|
|
||||||
<span id="progress-status" class="text-muted small progress-indicator">
|
|
||||||
<span id="progress-status-icon" class="progress-icon" aria-hidden="true"></span>
|
|
||||||
<span id="progress-status-text" class="progress-text">—</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user