395 lines
16 KiB
JavaScript
395 lines
16 KiB
JavaScript
/* === ANIMATION TITRE-NAV (toutes les pages) === */
|
|
function animerTitreNav() {
|
|
document.querySelectorAll('.fenetre-nom').forEach(el => {
|
|
el.innerHTML = el.textContent.split('').map((c, i) => {
|
|
if (c === ' ') return ' ';
|
|
const delayColor = (i * 0.3).toFixed(1);
|
|
const delayLevite = (Math.random() * 4).toFixed(2);
|
|
const duree = (3 + Math.random() * 3).toFixed(2);
|
|
return `<span style="display:inline-block;animation:colorshift 12s ease-in-out infinite,levite ${duree}s ease-in-out infinite;animation-delay:-${delayColor}s,-${delayLevite}s">${c}</span>`;
|
|
}).join('');
|
|
});
|
|
}
|
|
|
|
/* === /NOW (index.html) === */
|
|
function afficherNow() {
|
|
const phrasesNow = [
|
|
"/now : faut que je fasses les courses.",
|
|
"/now : les arbres vibrent !",
|
|
"/now : les mains, les jambes, avancent..",
|
|
"/now : lave toi le visage.",
|
|
"/now : il pleut, et c'est très bien comme ça.",
|
|
"/now : pas mal la page d'accueil !",
|
|
"/now : i love my computer",
|
|
"/now : demain je serais en cours, j'espère",
|
|
"/now : je resterais auprès de toi pour toujours",
|
|
"/now : je serais toi j'irais prendre l'air",
|
|
"/now : je dois vraiment aller dormir la"
|
|
];
|
|
|
|
const spanNow = document.querySelector('.now');
|
|
if (spanNow) {
|
|
const indexAleatoire = Math.floor(Math.random() * phrasesNow.length);
|
|
spanNow.textContent = phrasesNow[indexAleatoire];
|
|
}
|
|
}
|
|
|
|
/* === GRILLE ALBUMS (reviews/index.html) === */
|
|
function toSlug(titre) {
|
|
return titre
|
|
.toLowerCase()
|
|
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^a-z0-9-]/g, '');
|
|
}
|
|
|
|
function chargerAlbums() {
|
|
fetch('data/albums.json')
|
|
.then(r => r.json())
|
|
.then(albums => {
|
|
const grille = document.getElementById('grille');
|
|
|
|
albums.forEach(album => {
|
|
const carte = document.createElement('div');
|
|
carte.className = 'carte';
|
|
|
|
const media = album.cover
|
|
? `<img src="${album.cover}" alt="${album.titre}" onerror="this.outerHTML='<div class=\\'placeholder\\'>${album.titre}</div>'">`
|
|
: `<div class="placeholder">${album.titre}</div>`;
|
|
|
|
const fav = album.favoris ? '<span class="badge-fav">♥</span>' : '';
|
|
|
|
carte.innerHTML = `
|
|
${fav}
|
|
${media}
|
|
<div class="nom">
|
|
${album.titre}<br>
|
|
<span class="artiste">${album.artiste}</span>
|
|
</div>
|
|
`;
|
|
|
|
carte.addEventListener('mouseenter', () => {
|
|
grille.classList.add('hovering');
|
|
carte.classList.add('actif');
|
|
});
|
|
carte.addEventListener('mouseleave', () => {
|
|
grille.classList.remove('hovering');
|
|
carte.classList.remove('actif');
|
|
});
|
|
|
|
carte.addEventListener('click', () => {
|
|
window.location.href = `albums/${toSlug(album.titre)}.html`;
|
|
});
|
|
|
|
grille.appendChild(carte);
|
|
});
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('grille').textContent = 'impossible de charger les albums.';
|
|
});
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════
|
|
DEPECHES — VERSION ENRICHIE
|
|
═══════════════════════════════════════════════════ */
|
|
|
|
/* ── helpers texte ───────────────────────────────── */
|
|
function escapeHtml(str) {
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
function parseMarkdown(text) {
|
|
// gras
|
|
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
// italique
|
|
text = text.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
// barré
|
|
text = text.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
|
// code inline
|
|
text = text.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
|
|
return text;
|
|
}
|
|
|
|
function parseHashtags(text) {
|
|
return text.replace(/(^|\s)(#[\wÀ-ÿ]+)/g, (m, before, tag) => {
|
|
const q = encodeURIComponent(tag);
|
|
return `${before}<a href="https://www.google.com/search?q=${q}" target="_blank" class="hashtag">${tag}</a>`;
|
|
});
|
|
}
|
|
|
|
function parseMentions(text) {
|
|
return text.replace(/(^|\s)(@[\w.]+)/g, (m, before, mention) => {
|
|
return `${before}<span class="mention">${mention}</span>`;
|
|
});
|
|
}
|
|
|
|
function timeAgo(dateStr) {
|
|
const m = dateStr.match(/(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})/);
|
|
if (!m) return dateStr;
|
|
const [, d, mo, y, H, Mi] = m;
|
|
const date = new Date(`${y}-${mo}-${d}T${H}:${Mi}:00`);
|
|
const sec = Math.floor((Date.now() - date) / 1000);
|
|
if (sec < 60) return "à l'instant";
|
|
if (sec < 3600) return `il y a ${Math.floor(sec / 60)} min`;
|
|
if (sec < 86400)return `il y a ${Math.floor(sec / 3600)} h`;
|
|
if (sec < 604800)return `il y a ${Math.floor(sec / 86400)} j`;
|
|
if (sec < 2592000)return `il y a ${Math.floor(sec / 604800)} sem`;
|
|
return dateStr;
|
|
}
|
|
|
|
/* ── split texte / urls ──────────────────────────── */
|
|
function splitTextAndUrls(text) {
|
|
const re = /(https?:\/\/[^\s]+)/g;
|
|
const parts = [];
|
|
let last = 0, m;
|
|
while ((m = re.exec(text)) !== null) {
|
|
if (m.index > last) parts.push({ type: 'text', value: text.slice(last, m.index) });
|
|
parts.push({ type: 'url', value: m[1] });
|
|
last = m.index + m[0].length;
|
|
}
|
|
if (last < text.length) parts.push({ type: 'text', value: text.slice(last) });
|
|
return parts;
|
|
}
|
|
|
|
/* ── embeds ──────────────────────────────────────── */
|
|
function styledLink(url) {
|
|
try {
|
|
const domain = new URL(url).hostname.replace(/^www\./, '');
|
|
return `<a href="${escapeHtml(url)}" target="_blank" class="depeche-link"><span class="link-domain">${escapeHtml(domain)}</span><span class="link-url">${escapeHtml(url)}</span></a>`;
|
|
} catch {
|
|
return `<a href="${escapeHtml(url)}" target="_blank" class="depeche-link">${escapeHtml(url)}</a>`;
|
|
}
|
|
}
|
|
|
|
async function resolveEmbed(url) {
|
|
/* vidéo 16:9 */
|
|
const yt = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
|
|
if (yt) return `<div class="embed-container video"><iframe src="https://www.youtube.com/embed/${yt[1]}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
|
|
|
|
const vm = url.match(/vimeo\.com\/(\d+)/);
|
|
if (vm) return `<div class="embed-container video"><iframe src="https://player.vimeo.com/video/${vm[1]}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
|
|
|
|
const dm = url.match(/(?:dailymotion\.com\/video\/|dai\.ly\/)([a-zA-Z0-9]+)/);
|
|
if (dm) return `<div class="embed-container video"><iframe src="https://www.dailymotion.com/embed/video/${dm[1]}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
|
|
|
|
const twClip = url.match(/(?:clips\.twitch\.tv\/|twitch\.tv\/[^/]+\/clip\/)([a-zA-Z0-9_-]+)/);
|
|
if (twClip) return `<div class="embed-container video"><iframe src="https://clips.twitch.tv/embed?clip=${twClip[1]}&parent=${location.hostname}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
|
|
|
|
const twVid = url.match(/twitch\.tv\/videos\/(\d+)/);
|
|
if (twVid) return `<div class="embed-container video"><iframe src="https://player.twitch.tv/?video=${twVid[1]}&parent=${location.hostname}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
|
|
|
|
const coub = url.match(/coub\.com\/view\/([a-zA-Z0-9]+)/);
|
|
if (coub) return `<div class="embed-container video"><iframe src="https://coub.com/embed/${coub[1]}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
|
|
|
|
/* musique */
|
|
const sp = url.match(/open\.spotify\.com\/(track|album|playlist|episode|show)\/([a-zA-Z0-9]+)/);
|
|
if (sp) return `<div class="embed-container music"><iframe src="https://open.spotify.com/embed/${sp[1]}/${sp[2]}" frameborder="0" allowtransparency="true" allow="encrypted-media" loading="lazy"></iframe></div>`;
|
|
|
|
const dz = url.match(/deezer\.com\/[a-z]{2}\/(track|album|playlist)\/(\d+)/);
|
|
if (dz) return `<div class="embed-container music"><iframe src="https://widget.deezer.com/widget/dark/${dz[1]}/${dz[2]}" frameborder="0" allowtransparency="true" allow="encrypted-media; clipboard-write" loading="lazy"></iframe></div>`;
|
|
|
|
const am = url.match(/music\.apple\.com\/[a-z]{2}\/(album|song|playlist)\/[^/]+\/(\d+)/);
|
|
if (am) return `<div class="embed-container music"><iframe src="https://embed.music.apple.com/us/${am[1]}/${am[2]}" frameborder="0" allow="encrypted-media" loading="lazy"></iframe></div>`;
|
|
|
|
if (url.includes('mixcloud.com')) {
|
|
return `<div class="embed-container music"><iframe src="https://www.mixcloud.com/widget/iframe/?feed=${encodeURIComponent(url)}&hide_cover=1&light=1" frameborder="0" loading="lazy"></iframe></div>`;
|
|
}
|
|
|
|
const aud = url.match(/audiomack\.com\/([^/]+)\/(song|album|playlist)\/([^/]+)/);
|
|
if (aud) return `<div class="embed-container music"><iframe src="https://audiomack.com/embed/${aud[1]}/${aud[2]}/${aud[3]}" scrolling="no" frameborder="0" loading="lazy"></iframe></div>`;
|
|
|
|
/* oembed */
|
|
if (url.includes('soundcloud.com')) {
|
|
try {
|
|
const r = await fetch(`https://soundcloud.com/oembed?format=json&url=${encodeURIComponent(url)}&maxwidth=500`);
|
|
const d = await r.json();
|
|
return `<div class="embed-container">${d.html}</div>`;
|
|
} catch { /* fallthrough */ }
|
|
}
|
|
|
|
if (url.includes('bandcamp.com')) {
|
|
try {
|
|
const r = await fetch(`https://bandcamp.com/api/embed/1/oembed?url=${encodeURIComponent(url)}&format=json`);
|
|
if (!r.ok) throw new Error('ko');
|
|
const d = await r.json();
|
|
return `<div class="embed-container">${d.html}</div>`;
|
|
} catch { /* fallthrough */ }
|
|
}
|
|
|
|
if (url.includes('tiktok.com')) {
|
|
try {
|
|
const r = await fetch(`https://www.tiktok.com/oembed?url=${encodeURIComponent(url)}`);
|
|
const d = await r.json();
|
|
return `<div class="embed-container">${d.html}</div>`;
|
|
} catch { /* fallthrough */ }
|
|
}
|
|
|
|
if (url.includes('reddit.com')) {
|
|
try {
|
|
const r = await fetch(`https://www.reddit.com/oembed?url=${encodeURIComponent(url)}`);
|
|
const d = await r.json();
|
|
return `<div class="embed-container">${d.html}</div>`;
|
|
} catch { /* fallthrough */ }
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/* ── parser principal ────────────────────────────── */
|
|
async function parseTexte(texte) {
|
|
const parts = splitTextAndUrls(texte);
|
|
const resolved = await Promise.all(parts.map(async part => {
|
|
if (part.type === 'text') {
|
|
let html = escapeHtml(part.value);
|
|
html = parseMarkdown(html);
|
|
html = parseHashtags(html);
|
|
html = parseMentions(html);
|
|
html = html.replace(/\n/g, '<br>');
|
|
return html;
|
|
}
|
|
const embed = await resolveEmbed(part.value);
|
|
return embed || styledLink(part.value);
|
|
}));
|
|
return resolved.join('');
|
|
}
|
|
|
|
/* ── rendu du feed ───────────────────────────────── */
|
|
async function chargerDepeches() {
|
|
const feed = document.getElementById('feed');
|
|
|
|
try {
|
|
const r = await fetch('/depeches');
|
|
const depeches = await r.json();
|
|
feed.innerHTML = '';
|
|
|
|
if (depeches.length === 0) {
|
|
feed.innerHTML = '<p class="chargement">aucune dépêche pour l\'instant.</p>';
|
|
return;
|
|
}
|
|
|
|
for (const d of depeches) {
|
|
const el = document.createElement('div');
|
|
el.className = 'depeche';
|
|
el.id = d.id;
|
|
|
|
const texteHtml = await parseTexte(d.texte);
|
|
const img = d.image
|
|
? `<img src="${d.image}" class="depeche-image" alt="" loading="lazy">`
|
|
: '';
|
|
|
|
const permalink = `${location.origin}${location.pathname}#${d.id}`;
|
|
|
|
el.innerHTML = `
|
|
<div class="depeche-header">
|
|
<span class="depeche-date" title="${d.date}">ewen — ${timeAgo(d.date)}</span>
|
|
<div class="depeche-actions">
|
|
<button class="depeche-action" data-action="copy" title="copier le lien">#</button>
|
|
<button class="depeche-action" data-action="share" title="partager">↗</button>
|
|
</div>
|
|
</div>
|
|
<div class="depeche-texte">${texteHtml}</div>
|
|
${img}
|
|
`;
|
|
|
|
/* actions */
|
|
el.querySelector('[data-action="copy"]').addEventListener('click', function () {
|
|
navigator.clipboard.writeText(permalink).then(() => {
|
|
this.textContent = '✓';
|
|
setTimeout(() => this.textContent = '#', 1200);
|
|
});
|
|
});
|
|
|
|
el.querySelector('[data-action="share"]').addEventListener('click', function () {
|
|
if (navigator.share) {
|
|
navigator.share({ title: 'dépêche', url: permalink });
|
|
} else {
|
|
navigator.clipboard.writeText(permalink).then(() => {
|
|
this.textContent = '✓';
|
|
setTimeout(() => this.textContent = '↗', 1200);
|
|
});
|
|
}
|
|
});
|
|
|
|
feed.appendChild(el);
|
|
}
|
|
|
|
/* scroll vers l'ancre si présente */
|
|
if (location.hash) {
|
|
const target = document.querySelector(location.hash);
|
|
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
} catch {
|
|
feed.innerHTML = '<p class="chargement">impossible de charger les dépêches.</p>';
|
|
}
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════
|
|
LIGHTBOX
|
|
═══════════════════════════════════════════════════ */
|
|
function initLightbox() {
|
|
const contenu = document.querySelector('.fenetre-contenu');
|
|
if (!contenu) return;
|
|
|
|
contenu.addEventListener('click', e => {
|
|
const img = e.target.closest('img:not(.thanks)');
|
|
if (!img || img.closest('.depeche-link') || img.closest('.embed-container')) return;
|
|
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'lightbox';
|
|
|
|
const fond = document.createElement('div');
|
|
fond.className = 'lightbox-fond';
|
|
overlay.appendChild(fond);
|
|
|
|
const clone = document.createElement('img');
|
|
clone.className = 'lightbox-image';
|
|
clone.src = img.src;
|
|
clone.alt = img.alt;
|
|
overlay.appendChild(clone);
|
|
|
|
const fermer = document.createElement('button');
|
|
fermer.className = 'lightbox-fermer';
|
|
fermer.innerHTML = '×';
|
|
overlay.appendChild(fermer);
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
overlay.addEventListener('click', e => {
|
|
if (e.target === fond || e.target === fermer || e.target === overlay) {
|
|
overlay.remove();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('keydown', function onKey(e) {
|
|
if (e.key === 'Escape') {
|
|
overlay.remove();
|
|
document.removeEventListener('keydown', onKey);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════
|
|
INIT
|
|
═══════════════════════════════════════════════════ */
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
animerTitreNav();
|
|
|
|
if (document.querySelector('.now')) {
|
|
afficherNow();
|
|
}
|
|
|
|
if (document.getElementById('grille')) {
|
|
chargerAlbums();
|
|
}
|
|
|
|
if (document.getElementById('feed')) {
|
|
chargerDepeches();
|
|
}
|
|
|
|
initLightbox();
|
|
});
|