WIP: App faq page #62

Closed
quoije wants to merge 1 commits from cursor/app-faq-page-5247 into 2026
7 changed files with 367 additions and 0 deletions
Showing only changes of commit 6760e0fc80 - Show all commits
+1
View File
@@ -72,6 +72,7 @@
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a> <a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a> <a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a> <a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a href="faq.html" class="nav-link btn btn-outline-light me-2">FAQ</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a> <a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a>
</div> </div>
<!-- Move user info inside collapsible navbar and align it properly --> <!-- Move user info inside collapsible navbar and align it properly -->
+1
View File
@@ -43,6 +43,7 @@
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a> <a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a> <a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a> <a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a href="faq.html" class="nav-link btn btn-outline-light me-2">FAQ</a>
<a href="control.html" class="nav-link btn btn-warning me-2">Admin</a> <a href="control.html" class="nav-link btn btn-warning me-2">Admin</a>
</div> </div>
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center"> <div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center">
+178
View File
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pool Oscars - FAQ</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./css/app.css" rel="stylesheet">
<link rel="preload" as="image" href="img/logo_w.png">
<script src="./js/app_header_boot.js" defer></script>
<style>
.navbar-custom {
background-color: black;
color: white;
}
.navbar-custom .navbar-brand,
.navbar-custom .nav-link {
color: white;
}
.navbar-custom .nav-link:hover {
color: #ddd;
}
.navbar-toggler {
border-color: white;
}
.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");
}
</style>
</head>
<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 -->
<nav class="navbar navbar-expand-lg navbar-custom">
<div class="container">
<a class="navbar-brand" style="margin-left: -7px;">
<img src="img/logo_w.png" width="220" height="70" loading="eager" fetchpriority="high" decoding="async" style="max-width: 220px; height: 70px; object-fit: contain;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<div class="navbar-nav">
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a href="faq.html" class="nav-link btn btn-outline-light me-2">FAQ</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a>
</div>
<!-- Move user info inside collapsible navbar and align it properly -->
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center">
<span id="user-name" class="navbar-text me-3"></span>
<button id="log-off" class="btn btn-danger">Déconnexion</button>
</div>
</div>
</div>
</nav>
<div class="container mt-5 pb-4" style="max-width: 900px;">
<div class="mb-3">
<h2 class="mb-1">FAQ</h2>
<div class="text-muted">Rapide, simple, sans drama.</div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<div class="mb-3">
<div class="fw-semibold">En 10 secondes</div>
<div class="text-muted">
Pool Oscars te sert à <strong>suivre les films</strong> de l’année, les <strong>regarder dans le lecteur</strong>,
cocher ce que tu as vu, et voir les <strong>stats</strong> (toi + les autres).
</div>
</div>
<div class="accordion" id="faqAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="faqHeading1">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#faqCollapse1" aria-expanded="true" aria-controls="faqCollapse1">
C’est quoi “Pool Oscars” ?
</button>
</h2>
<div id="faqCollapse1" class="accordion-collapse collapse show" aria-labelledby="faqHeading1" data-bs-parent="#faqAccordion">
<div class="accordion-body">
C’est une app “pool” pour suivre les films nommés aux Oscars (par année). Tu vois la liste, tu regardes,
tu coches, et tu compares ta progression avec les autres.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqHeading2">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqCollapse2" aria-expanded="false" aria-controls="faqCollapse2">
Je fais quoi sur la page “Films” ?
</button>
</h2>
<div id="faqCollapse2" class="accordion-collapse collapse" aria-labelledby="faqHeading2" data-bs-parent="#faqAccordion">
<div class="accordion-body">
Tu browses les films. Clique sur l’affiche ou le titre pour ouvrir le lecteur. L’icône IMDb ouvre la fiche
du film dans un nouvel onglet.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqHeading3">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqCollapse3" aria-expanded="false" aria-controls="faqCollapse3">
À quoi sert la “Checklist” ?
</button>
</h2>
<div id="faqCollapse3" class="accordion-collapse collapse" aria-labelledby="faqHeading3" data-bs-parent="#faqAccordion">
<div class="accordion-body">
C’est la version “tableau simple” pour cocher vite ce que tu as vu. Ça calcule ton ratio, une barre de
progression et une estimation “à voir par jour” avant la date des Oscars.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqHeading4">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqCollapse4" aria-expanded="false" aria-controls="faqCollapse4">
Les “Statistiques”, c’est quoi exactement ?
</button>
</h2>
<div id="faqCollapse4" class="accordion-collapse collapse" aria-labelledby="faqHeading4" data-bs-parent="#faqAccordion">
<div class="accordion-body">
Un classement (films vus / points) + des onglets “Gagnants” et “100%”. Tu peux aussi cliquer sur un nom pour
voir les films que la personne a regardés.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqHeading5">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqCollapse5" aria-expanded="false" aria-controls="faqCollapse5">
Pourquoi je me fais rediriger vers “Changer le mot de passe” ?
</button>
</h2>
<div id="faqCollapse5" class="accordion-collapse collapse" aria-labelledby="faqHeading5" data-bs-parent="#faqAccordion">
<div class="accordion-body">
Ça arrive quand ton compte a un mot de passe temporaire. Tu dois en définir un nouveau une fois, puis c’est
réglé.
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="faqHeading6">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faqCollapse6" aria-expanded="false" aria-controls="faqCollapse6">
C’est quoi le bouton “Admin” ?
</button>
</h2>
<div id="faqCollapse6" class="accordion-collapse collapse" aria-labelledby="faqHeading6" data-bs-parent="#faqAccordion">
<div class="accordion-body">
Seulement pour les admins: réglages (année active, date des Oscars, lecteur), gestion des films et des
utilisateurs.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="./js/app_faq.js"></script>
</body>
</html>
+184
View File
@@ -0,0 +1,184 @@
function base64UrlToJson(b64url) {
const s = String(b64url || '')
.replace(/-/g, '+')
.replace(/_/g, '/');
const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
return JSON.parse(atob(padded));
}
function decodeJwtPayload(token) {
const parts = String(token || '').split('.');
if (parts.length < 2) return null;
try { return base64UrlToJson(parts[1]); } catch (_) { return null; }
}
function isExpired(decoded) {
const exp = Number(decoded?.exp);
if (!Number.isFinite(exp) || exp <= 0) return false;
const now = Math.floor(Date.now() / 1000);
return exp < now;
}
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 };
}
async function fetchActiveYear() {
try {
const res = await fetch('/api/settings/year', { method: 'GET', cache: 'no-cache' });
if (!res.ok) throw new Error('Failed to fetch active year');
const data = await res.json();
const year = Number(data?.year);
return Number.isInteger(year) ? year : null;
} catch (_) {
return null;
}
}
window.addEventListener('DOMContentLoaded', async function () {
const pageLoader = createPageLoader({ title: 'Chargement de la FAQ' });
pageLoader.setProgress(14);
const token = localStorage.getItem('auth_token');
if (!token) {
window.location.href = '/';
return;
}
const decoded = decodeJwtPayload(token);
if (!decoded || isExpired(decoded)) {
try { localStorage.removeItem('auth_token'); } catch (_) {}
window.location.href = '/';
return;
}
if (decoded.mustChangePassword) {
window.location.href = '/change-password.html';
return;
}
// Header is usually set by app_header_boot, but keep it consistent if it ran before DOM was ready.
const nameEl = document.getElementById('user-name');
if (nameEl && !nameEl.textContent) nameEl.textContent = decoded.name || '';
if (decoded.admin) {
const adminLink = document.getElementById('admin-control-link');
if (adminLink) adminLink.classList.remove('d-none');
}
pageLoader.setProgress(40);
const activeYear = await fetchActiveYear();
if (activeYear) document.title = `Pool Oscars (${activeYear}) - FAQ`;
pageLoader.setProgress(90);
const logoffBtn = document.getElementById('log-off');
if (logoffBtn) {
logoffBtn.addEventListener('click', function () {
localStorage.removeItem('auth_token');
window.location.href = '/';
});
}
pageLoader.done();
});
+1
View File
@@ -59,6 +59,7 @@
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a> <a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a> <a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a> <a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a href="faq.html" class="nav-link btn btn-outline-light me-2">FAQ</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a> <a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a>
</div> </div>
<!-- Move user info inside collapsible navbar and align it properly --> <!-- Move user info inside collapsible navbar and align it properly -->
+1
View File
@@ -127,6 +127,7 @@
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a> <a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a> <a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a> <a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a href="faq.html" class="nav-link btn btn-outline-light me-2">FAQ</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a> <a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a>
</div> </div>
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2"> <div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2">
+1
View File
@@ -65,6 +65,7 @@
<a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a> <a href="movies.html" class="nav-link btn btn-outline-light me-2">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a> <a href="checklist.html" class="nav-link btn btn-outline-light me-2">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a> <a href="user-stat.html" class="nav-link btn btn-outline-light me-2">Statistiques</a>
<a href="faq.html" class="nav-link btn btn-outline-light me-2">FAQ</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a> <a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none">Admin</a>
</div> </div>
<!-- Move user info inside collapsible navbar and align it properly --> <!-- Move user info inside collapsible navbar and align it properly -->