- Mise en page sortie du Python : Scripts/templates/ (Jinja2 + CSS/JS communs copiés dans assets/ de chaque voyage) - Voyages rangés dans un sous-dossier (Balades/…) : rubriques sur l'accueil, 2 plus récents par rubrique, pages voyages.html et balades.html - prepare_web.py : photos allégées (photos_web/) et images d'aperçu des vidéos (apercus/) - Voyage d'une seule journée : la page du voyage est la carte du jour - Carte globale de l'accueil (OpenStreetMap) : voyages du site + cartes Google My Maps récupérées (mymaps.txt, _mymaps/), moto par voyage (moto.txt) - KML par voyage pour My Maps - compress.sh : réencode une sortie illisible (encodage interrompu) ; repli automatique sur l'autre qualité dans la page - Corrections : CSV vide (voyage sans photo), racine de genere_site.sh, fichiers ._ de macOS - README réécrit, commentaires allégés Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
184 lines
7.6 KiB
JavaScript
184 lines
7.6 KiB
JavaScript
/* =============================================================================
|
|
Pages d'un voyage : vidéos, diaporamas photo et carnet de route.
|
|
Copié dans <voyage>/assets/ à chaque génération.
|
|
|
|
Les gestionnaires sont appelés par des onclick="" dans les popups : Leaflet
|
|
bloque la propagation des clics hors des popups, une délégation au niveau
|
|
du document ne les verrait pas.
|
|
============================================================================= */
|
|
|
|
// --- Vidéos ------------------------------------------------------------------
|
|
// Le 720p (H.264) est chargé par défaut et se lit partout. Le 1080p est en
|
|
// HEVC : si le navigateur ne le décode pas (Firefox surtout), on grise le bouton.
|
|
var HEVC_OK = (function () {
|
|
try {
|
|
var t = document.createElement('video').canPlayType('video/mp4; codecs="hvc1.1.6.L120.B0"');
|
|
return t === 'probably' || t === 'maybe';
|
|
} catch (e) { return false; }
|
|
})();
|
|
|
|
function applyCodecSupport(popup) {
|
|
if (HEVC_OK) return;
|
|
var btns = popup.querySelectorAll('.quality-btn[data-hevc="1"]');
|
|
for (var i = 0; i < btns.length; i++) {
|
|
btns[i].classList.add('qbtn-disabled');
|
|
btns[i].title = "Le 1080p est encodé en HEVC, que ce navigateur ne sait pas décoder. Essaie Safari, Chrome ou Edge.";
|
|
}
|
|
var note = popup.querySelector('.hevc-note');
|
|
if (note) note.style.display = 'block';
|
|
}
|
|
|
|
function changeQuality(btn) {
|
|
if (btn.classList.contains('qbtn-disabled')) return;
|
|
var box = btn.closest('.popup-video');
|
|
var video = box.querySelector('video');
|
|
var newSrc = btn.getAttribute('data-src');
|
|
|
|
// video.src est une URL absolue, data-src est relatif : on compare les noms.
|
|
var nomActuel = decodeURIComponent(video.src).split('/').pop();
|
|
var nomVoulu = decodeURIComponent(newSrc).split('/').pop();
|
|
if (nomActuel === nomVoulu) return;
|
|
|
|
var savedTime = video.currentTime;
|
|
var wasPlaying = !video.paused;
|
|
video.src = newSrc;
|
|
video.onloadedmetadata = function () {
|
|
this.currentTime = savedTime;
|
|
if (wasPlaying) {
|
|
var p = this.play();
|
|
if (p !== undefined) p.catch(function () { console.log("Lecture bloquée"); });
|
|
}
|
|
this.onloadedmetadata = null;
|
|
};
|
|
video.load();
|
|
|
|
var buttons = box.querySelectorAll('.quality-btn');
|
|
for (var i = 0; i < buttons.length; i++) buttons[i].classList.remove('active-720', 'active-1080');
|
|
btn.classList.add(btn.getAttribute('data-hevc') === '1' ? 'active-1080' : 'active-720');
|
|
}
|
|
|
|
// À l'ouverture d'une popup vidéo : codec, image d'aperçu, puis lecture.
|
|
// L'aperçu n'est posé qu'à ce moment-là : en attribut poster="" direct, le
|
|
// navigateur chargerait d'un coup les aperçus de toutes les vidéos du jour.
|
|
function nomFichier(u) { return decodeURIComponent(u || '').split('/').pop(); }
|
|
|
|
// Filet de sécurité : si une version ne se lit pas (1080p HEVC malgré la
|
|
// détection, fichier abîmé ou absent), on essaie l'autre.
|
|
function surErreurVideo() {
|
|
var video = this, box = video.closest('.popup-video');
|
|
video._essais = video._essais || {};
|
|
video._essais[nomFichier(video.src)] = true;
|
|
var candidats = [video.getAttribute('data-fallback')];
|
|
var hd = box.querySelector('.quality-btn[data-hevc="1"]');
|
|
if (hd && HEVC_OK) candidats.push(hd.getAttribute('data-src'));
|
|
for (var i = 0; i < candidats.length; i++) {
|
|
var c = candidats[i];
|
|
if (c && !video._essais[nomFichier(c)]) {
|
|
var btn = box.querySelector('.quality-btn[data-src="' + c + '"]');
|
|
if (btn) changeQuality(btn); else { video.src = c; video.load(); }
|
|
var p = video.play();
|
|
if (p !== undefined) p.catch(function () {});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function preparePopup(node) {
|
|
applyCodecSupport(node);
|
|
var video = node.querySelector('video');
|
|
if (!video) return;
|
|
// Leaflet réinsère la même popup à chaque ouverture : une seule initialisation.
|
|
if (!video._pret) {
|
|
video._pret = true;
|
|
var apercu = video.getAttribute('data-poster');
|
|
if (apercu) video.setAttribute('poster', apercu);
|
|
video.addEventListener('error', surErreurVideo);
|
|
}
|
|
var p = video.play();
|
|
if (p !== undefined) p.catch(function (e) { console.log("Autoplay bloqué par le navigateur", e); });
|
|
}
|
|
|
|
var observer = new MutationObserver(function (mutations) {
|
|
mutations.forEach(function (m) {
|
|
m.addedNodes.forEach(function (node) {
|
|
if (node.nodeType === 1 && node.classList && node.classList.contains('leaflet-popup')) {
|
|
preparePopup(node);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
});
|
|
|
|
// --- Diaporama photos ----------------------------------------------------------
|
|
function moveSlide(btn, step) {
|
|
var slides = btn.parentElement.querySelectorAll('.slides .slide');
|
|
var actif = Array.prototype.findIndex.call(slides, function (s) { return s.style.display !== 'none'; });
|
|
if (actif === -1) actif = 0;
|
|
slides[actif].style.display = 'none';
|
|
var suivant = (actif + step + slides.length) % slides.length;
|
|
slides[suivant].style.display = 'block';
|
|
btn.parentElement.querySelector('.slide-counter').innerText = (suivant + 1) + '/' + slides.length;
|
|
}
|
|
|
|
// --- Carnet de route -----------------------------------------------------------
|
|
// localStorage lève une exception sur file:// dans certains navigateurs
|
|
// (Safari, navigation privée) : on l'enveloppe.
|
|
function memLire(k) { try { return localStorage.getItem(k); } catch (e) { return null; } }
|
|
function memEcrire(k, v) { try { localStorage.setItem(k, v); } catch (e) {} }
|
|
|
|
// Journée à afficher : celle de l'URL (#jour-AAAA-MM-JJ) ou celle de la page.
|
|
function journalCible() {
|
|
var m = /#jour-(\d{4}-\d{2}-\d{2})/.exec(window.location.hash || '');
|
|
return m ? m[1] : (window.JOUR_COURANT || null);
|
|
}
|
|
|
|
function journalDefile(jour) {
|
|
var panel = document.getElementById('journal-panel');
|
|
if (!panel || !jour) return false;
|
|
var el = document.getElementById('ancre-' + jour);
|
|
if (!el) return false;
|
|
var r = el.getBoundingClientRect(), pr = panel.getBoundingClientRect();
|
|
panel.scrollTop += (r.top - pr.top) - 70; // laisse voir le titre du jour
|
|
var actif = panel.querySelector('.jour-actif');
|
|
if (actif) actif.classList.remove('jour-actif');
|
|
el.classList.add('jour-actif');
|
|
return true;
|
|
}
|
|
|
|
// Rejoué plusieurs fois : les CSS distantes (Leaflet, Font Awesome) arrivent
|
|
// après DOMContentLoaded et décalent les hauteurs.
|
|
function journalRecale() {
|
|
var j = journalCible();
|
|
journalDefile(j);
|
|
[150, 500, 1200].forEach(function (d) { setTimeout(function () { journalDefile(j); }, d); });
|
|
}
|
|
|
|
function toggleJournal(open) {
|
|
var panel = document.getElementById('journal-panel');
|
|
if (!panel) return;
|
|
panel.classList.toggle('open', open);
|
|
memEcrire('journalOpen', open ? 'true' : 'false');
|
|
if (open) journalRecale();
|
|
}
|
|
|
|
function journalInit() {
|
|
var panel = document.getElementById('journal-panel');
|
|
if (!panel) return;
|
|
var vientDuCarnet = /#jour-\d{4}-\d{2}-\d{2}/.test(window.location.hash || '');
|
|
if (memLire('journalOpen') === 'true' || vientDuCarnet) {
|
|
panel.classList.add('open');
|
|
if (vientDuCarnet) memEcrire('journalOpen', 'true');
|
|
}
|
|
journalRecale();
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
window.addEventListener('DOMContentLoaded', journalInit);
|
|
} else {
|
|
journalInit();
|
|
}
|
|
window.addEventListener('load', journalRecale);
|
|
window.addEventListener('hashchange', function () { toggleJournal(true); });
|