Initial commit

This commit is contained in:
2026-05-12 23:33:46 +02:00
commit ccf32dcece
104 changed files with 17439 additions and 0 deletions

883
js/adminDashboardMods.js Normal file
View File

@@ -0,0 +1,883 @@
(function($) {
'use strict';
function isPostEditPage() {
return window.pagenow === 'post'
|| window.pagenow === 'post-new'
// On CPTs, pagenow is the post_type slug — also catch them via the
// body classes WP sets for any post.php / post-new.php screen.
|| document.body.classList.contains('post-php')
|| document.body.classList.contains('post-new-php');
}
function isProfileEditPage() {
return window.pagenow === 'profile' || window.pagenow === 'user-edit' || window.pagenow === 'user-new';
}
function getProfileForm() {
return document.querySelector('#your-profile, #createuser');
}
function isPodsModal() {
return new URLSearchParams(window.location.search).has('pods_modal');
}
function renameArticlesToAnnonces() {
const replacements = [
[/Tous les articles/g, 'Toutes les annonces'],
[/Ajouter un article/g, 'Ajouter une annonce'],
[/Modifier l.article/g, "Modifier l'annonce"],
[/Pr\u00e9visualiser l.article/g, "Pr\u00e9visualiser l'annonce"],
[/Afficher l.article/g, "Afficher l'annonce"],
[/Voir l.article/g, "Voir l'annonce"],
[/Article publi\u00e9/g, 'Annonce publi\u00e9e'],
[/Article mis \u00e0 jour/g, 'Annonce mise \u00e0 jour'],
[/Article planifi\u00e9/g, 'Annonce planifi\u00e9e'],
[/Articles par page/g, 'Annonces par page'],
[/Articles/g, 'Annonces'],
[/Article/g, 'Annonce'],
[/Rechercher des articles/g, 'Rechercher des annonces'],
];
function applyReplacements(text) {
return replacements.reduce((t, [s, r]) => t.replace(s, r), text);
}
function replaceInTextNodes(el) {
if (!el) return;
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(function(node) {
const replaced = applyReplacements(node.textContent);
if (replaced !== node.textContent) node.textContent = replaced;
});
}
// Menu latéral
replaceInTextNodes(document.querySelector('#menu-posts'));
// Titre de page (h1) et bouton d'ajout
document.querySelectorAll('.wp-heading-inline, .page-title-action').forEach(replaceInTextNodes);
// Notifications après sauvegarde (Article publié, mis à jour…)
document.querySelectorAll('#message, .notice').forEach(replaceInTextNodes);
// Boîte de publication — lien "Voir l'article"
replaceInTextNodes(document.querySelector('.submitbox'));
// Options d'écran — "Articles par page"
replaceInTextNodes(document.querySelector('#screen-options-wrap'));
// Bouton de recherche (attribut value + aria-label)
var searchSubmit = document.querySelector('#search-submit');
if (searchSubmit) {
if (searchSubmit.value) {
searchSubmit.value = applyReplacements(searchSubmit.value);
}
var ariaLabel = searchSubmit.getAttribute('aria-label');
if (ariaLabel) {
searchSubmit.setAttribute('aria-label', applyReplacements(ariaLabel));
}
}
// Titre de l'onglet du navigateur
document.title = applyReplacements(document.title);
}
function updatePostboxVisibility() {
document.querySelectorAll('.postbox').forEach((postBox) => {
if (postBox.id.startsWith('pods')) {
// body-en is controlled by language tabs — never auto-hide it
if (postBox.id === 'pods-meta-body-en') return;
const fields = postBox.querySelectorAll('tr');
const hasVisibleFields = Array.from(fields).some(field => field.style.display !== 'none');
postBox.style.display = hasVisibleFields ? 'block' : 'none';
}
});
}
// Force Visual (TinyMCE) mode on page load.
// WP stores the last-used editor mode in localStorage and restores it at document.ready.
// When Code mode is restored, TinyMCE is never initialised — tinymce.get() returns null.
// Instead, check the wrapper's CSS class:
// tmce-active = Visual mode (fine)
// html-active = Code mode (switch to Visual)
function ensureVisualMode(editorId, attempt) {
attempt = attempt || 0;
if (attempt > 15) return;
var wrap = document.getElementById('wp-' + editorId + '-wrap');
if (!wrap) {
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
return;
}
if (wrap.classList.contains('html-active')) {
var ed = window.tinymce && tinymce.get(editorId);
if (!ed || !ed.initialized) {
// TinyMCE not ready yet — retry rather than calling switchEditors.go() prematurely
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
return;
}
if (typeof switchEditors !== 'undefined') {
switchEditors.go(editorId, 'tmce');
}
return;
}
if (!wrap.classList.contains('tmce-active')) {
// Mode not yet determined — retry
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
}
}
// Phase 1: insert the tab bar and relocate #pods-meta-body-en.
// The DOM move breaks TinyMCE's iframe (browsers reset iframe content on detach),
// so we leave the container visible here and let Pods/TinyMCE initialise normally.
// The broken iframe is repaired by reinitEditor() on first EN tab open.
function setupBodyTabsDom() {
var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
var bodyEnBox = document.getElementById('pods-meta-body-en');
if (!nativeEditor || !bodyEnBox) return;
var tabBar = document.createElement('div');
tabBar.className = 'body-lang-tabs';
tabBar.innerHTML =
'<button type="button" class="body-lang-tab is-active" data-panel="fr">Fran\u00e7ais</button>' +
'<button type="button" class="body-lang-tab" data-panel="en">English</button>';
nativeEditor.parentNode.insertBefore(tabBar, nativeEditor);
// Move EN metabox to sit right after the native editor for correct visual layout.
// Do NOT hide it yet — Pods must init TinyMCE with the container visible so the
// iframe can measure its dimensions. Page is still opacity:0 so no flash.
nativeEditor.parentNode.insertBefore(bodyEnBox, nativeEditor.nextSibling);
}
// Rebuild a TinyMCE editor whose iframe is broken (empty/non-interactive).
// This happens when TinyMCE is initialised on a hidden (display:none) element:
// the iframe can't measure dimensions and its document body stays empty.
//
// We reinit from tinyMCEPreInit.mceInit — first trying the editor's own config
// (registered by Pods server-side), falling back to 'content' (the native WP editor).
//
// Inline toolbar positioning fix:
// TinyMCE's 'wordpress' plugin captures document.getElementById(id+'_ifr') during
// 'preinit' — before the iframe is created — so mceIframe is always null.
// Fix: intercept getElementById during preinit so the 'wordpress' plugin captures
// a proxyIframe instead of null. After init, proxy delegates to the real iframe.
function reinitEditor(editorId) {
var ed = window.tinymce && tinymce.get(editorId);
// Preserve existing content before destroying the instance
var savedContent = '';
if (ed) {
try { savedContent = ed.getContent(); } catch (e) {}
ed.remove();
}
if (!savedContent) {
var ta = document.getElementById(editorId);
if (ta) savedContent = ta.value || '';
}
if (!window.tinyMCEPreInit || !window.tinymce) return;
// Use the editor's own server-side config if available, else clone from 'content'
var baseInit = (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[editorId])
|| (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit['content']);
if (!baseInit) return;
// Proxy iframe: getBoundingClientRect() falls back to the editor wrap
var wrapId = 'wp-' + editorId + '-wrap';
var proxyIframe = {
getBoundingClientRect: function() {
var el = document.getElementById(wrapId);
return el ? el.getBoundingClientRect()
: { top: 0, left: 0, right: window.innerWidth,
bottom: window.innerHeight, width: window.innerWidth,
height: window.innerHeight };
}
};
var savedGetById = document.getElementById;
var origSetup = baseInit.setup;
var content = savedContent;
tinymce.init($.extend({}, baseInit, {
selector: '#' + editorId,
setup: function(editor) {
if (typeof origSetup === 'function') origSetup(editor);
editor.on('focus', function() {
window.wpActiveEditor = editorId;
});
editor.on('preinit', function() {
document.getElementById = function(id) {
if (id === editorId + '_ifr') return proxyIframe;
return savedGetById.call(document, id);
};
setTimeout(function() {
document.getElementById = savedGetById;
}, 0);
});
editor.on('init', function() {
// Point proxy to real iframe
var realIframe = savedGetById.call(document, editorId + '_ifr');
if (realIframe) {
proxyIframe.getBoundingClientRect = function() {
return realIframe.getBoundingClientRect();
};
}
// Restore content that was in the textarea
if (content) {
editor.setContent(content);
}
});
}
}));
}
// Phase 2: wire tab click handlers — runs at t=100ms after metabox reordering.
function initBodyLanguageTabs() {
var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
var bodyEnBox = document.getElementById('pods-meta-body-en');
var tabBar = document.querySelector('.body-lang-tabs');
if (!nativeEditor || !bodyEnBox || !tabBar) {
// body_en not available (e.g. contributor role) — still force visual mode on main editor
if (nativeEditor) ensureVisualMode('content');
return;
}
var enEditorId = 'pods-form-ui-pods-meta-body-en';
var enTmceReady = false;
// Hide EN panel — page is still opacity:0, user won't see the switch
bodyEnBox.style.display = 'none';
tabBar.querySelectorAll('.body-lang-tab').forEach(function(btn) {
btn.addEventListener('click', function() {
tabBar.querySelectorAll('.body-lang-tab').forEach(function(b) {
b.classList.remove('is-active');
});
btn.classList.add('is-active');
var revealedPanel;
if (btn.dataset.panel === 'fr') {
bodyEnBox.style.display = 'none';
nativeEditor.style.opacity = '0';
nativeEditor.style.display = '';
revealedPanel = nativeEditor;
} else {
nativeEditor.style.display = 'none';
bodyEnBox.style.opacity = '0';
bodyEnBox.style.display = 'block';
revealedPanel = bodyEnBox;
if (!enTmceReady) {
enTmceReady = true;
// Reinit while container is visible so TinyMCE can measure dimensions
reinitEditor(enEditorId);
}
}
// Notify TinyMCE to reflow, then fade in once layout is correct
setTimeout(function() {
window.dispatchEvent(new Event('resize'));
requestAnimationFrame(function() {
requestAnimationFrame(function() {
revealedPanel.style.opacity = '';
});
});
}, 50);
});
});
// Ensure both editors start in Visual (not Code) mode
ensureVisualMode('content');
ensureVisualMode(enEditorId);
}
function groupAxesCheckboxes() {
if (!window.thalimAxesGroups || !thalimAxesGroups.length) return;
var row = document.querySelector('.pods-form-ui-row-name-axes-thematiques');
if (!row) return;
var list = row.querySelector('ul');
if (!list) return;
// Already grouped — nothing to do
if (list.querySelector('.axes-group-label')) return;
// Map existing <li> by checkbox value; preserve "add new" button
var liMap = {};
var addNewItem = null;
list.querySelectorAll('li').forEach(function(li) {
if (li.classList.contains('pods-pick-add-new')) { addNewItem = li; return; }
var cb = li.querySelector('input[type="checkbox"]');
if (cb) liMap[cb.value] = li;
});
// Rebuild list in group order
list.innerHTML = '';
thalimAxesGroups.forEach(function(group) {
var labelLi = document.createElement('li');
labelLi.className = 'axes-group-label';
labelLi.textContent = group.label;
list.appendChild(labelLi);
group.terms.forEach(function(term) {
var li = liMap[String(term.id)];
if (li) list.appendChild(li);
});
});
if (addNewItem) list.appendChild(addNewItem);
}
var REF_BIB_EDITOR_ID = 'pods-form-ui-pods-meta-reference-bibliographique';
var refBibReinited = false;
// Reinit the référence bibliographique TinyMCE editor.
// Called at page load (if the field is already visible) and by the
// MutationObserver (when the field becomes visible after a category change).
function initRefBibEditor() {
if (refBibReinited) return;
var row = document.querySelector('.pods-form-ui-row-name-reference-bibliographique');
if (!row || row.style.display === 'none') return;
refBibReinited = true;
reinitEditor(REF_BIB_EDITOR_ID);
ensureVisualMode(REF_BIB_EDITOR_ID);
}
function initAxesGroupObserver() {
// Pods shows/hides conditional rows by removing inline style="display:none"
// Watch the entire Pods meta form for style changes on the axes row
var podsForm = document.querySelector('.pods-pick-values, #pods-meta-champs-contextuels, form#post');
if (!podsForm) podsForm = document.body;
var observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var target = mutations[i].target;
if (target.classList && target.classList.contains('pods-form-ui-row-name-axes-thematiques')) {
if (target.style.display !== 'none') {
setTimeout(groupAxesCheckboxes, 50);
}
}
// Reinit TinyMCE on the référence bibliographique field when its
// row becomes visible — Pods hides it with display:none which breaks
// the TinyMCE iframe. Only reinit once per page load.
if (!refBibReinited && target.classList &&
target.classList.contains('pods-form-ui-row-name-reference-bibliographique')) {
if (target.style.display !== 'none') {
setTimeout(initRefBibEditor, 100);
}
}
}
});
observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true });
}
function initPostEditPage() {
// Disable category options (CSS handles the color)
const categorieSelect = document.querySelector('#pods-form-ui-pods-meta-categorie');
if (categorieSelect) {
const categoriesToDisable = ['1', '12', '5', '20'];
categorieSelect.querySelectorAll('option').forEach(option => {
if (categoriesToDisable.includes(option.value)) {
option.disabled = true;
}
});
}
// Reorder meta boxes
const sideSortables = document.querySelector('#side-sortables');
if (sideSortables) {
const typeDannonce = document.querySelector('#pods-meta-type-dannonce');
const affichageAccueil = document.querySelector('#pods-meta-affichage-sur-laccueil');
const thematique = document.querySelector('#pods-meta-thematique');
if (typeDannonce) sideSortables.prepend(typeDannonce);
if (affichageAccueil) sideSortables.appendChild(affichageAccueil);
if (thematique) sideSortables.appendChild(thematique);
}
const submitDiv = document.querySelector('#submitdiv');
if (submitDiv && submitDiv.parentNode) {
submitDiv.parentNode.appendChild(submitDiv);
}
const champsContextuels = document.querySelector('#pods-meta-champs-contextuels');
if (champsContextuels && champsContextuels.parentNode) {
champsContextuels.parentNode.prepend(champsContextuels);
}
initBodyLanguageTabs();
initRefBibEditor();
groupAxesCheckboxes();
initAxesGroupObserver();
updatePostboxVisibility();
initDatePickerPopoverFix();
initInfoPopovers();
// Place #pods-meta-documents-joints in #normal-sortables, right after
// #pods-meta-champs-contextuels. This keeps it out of #post-body-content
// (the body editor section) regardless of whether champsContextuels is
// currently visible. When champsContextuels is hidden it takes no space,
// so documentsJoints simply appears first in #normal-sortables.
const documentsJoints = document.querySelector('#pods-meta-documents-joints');
if (documentsJoints) {
if (champsContextuels && champsContextuels.parentNode) {
champsContextuels.parentNode.insertBefore(documentsJoints, champsContextuels.nextSibling);
} else {
const normalSortables = document.querySelector('#normal-sortables');
if (normalSortables) normalSortables.prepend(documentsJoints);
}
}
// Inject separator row for the Membres grid layout
var membresTbody = document.querySelector('#pods-meta-membres .form-table tbody');
if (membresTbody && !membresTbody.querySelector('.membres-grid-separator')) {
var sep = document.createElement('tr');
sep.className = 'membres-grid-separator';
membresTbody.appendChild(sep);
}
updateMembresGridSeparator();
}
var INFO_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 11v6"/><path d="M12 8v.01" stroke-width="2"/></svg>';
var TRANSLATE_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
var TRANSLATE_LINES = [
'Traduction en anglais apr\u00e8s //',
'ex\u00a0: Texte en fran\u00e7ais // English text'
];
// Tips without a `page` key default to 'post'.
// type: 'translate' uses the globe icon + green button style.
var INFO_TIPS = [
// --- post edit page: info ---
{
selector: '.wp-heading-inline',
lines: [
'Saisir le titre anglais apr\u00e8s //',
'ex\u00a0: Titre de l\u2019annonce // Title of the announcement'
]
},
{
selector: '#pods-meta-documents-joints .postbox-header h2',
lines: [
'Ajouter les images dans les documents.',
'Ajouter les l\u00e9gendes comme titre du document.'
]
},
{
selector: '#pods-meta-membres .postbox-header h2',
lines: [
'Le champ fonction change le libell\u00e9 de la liste de personnes cit\u00e9es.',
'Le champ membre permet de lister les membres de Thalim li\u00e9s \u00e0 l\u2019annonce.',
'Le champ autre personnes permet de lister des personnes ext\u00e9rieures \u00e0 Thalim.'
]
},
{
selector: '#pods-meta-dates .postbox-header h2',
lines: [
'Pour entrer une date sans l\u2019heure, r\u00e9gler l\u2019heure sur 00\u202f:00.'
]
},
{
selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2',
lines: [
'\u00c9pingler l\u2019annonce dans le diaporama la fait s\u2019afficher avant les autres.'
]
},
{
selector: '#pods-meta-medias .postbox-header h2',
lines: [
'Pour ajouter un m\u00e9dia Canal\u00a0U, copier le lien depuis \u00ab\u00a0Citer cette ressource\u00a0\u00bb.',
'ex\u00a0: https://www.canal-u.tv/166564'
]
},
// --- post edit page: translate ---
{ type: 'translate', selector: '#pods-meta-documents-joints .postbox-header h2', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-sous-titre th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-lieu th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-1 th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-2 th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-3 th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-organisation th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-intervention th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-candidat th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-realisation th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-dirige th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-redaction th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-auteur th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-responsable th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-autre th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-concerne th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-directeur th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-direction-d-ouvrage th',lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-intervenant th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-participants th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-type-autre th', lines: TRANSLATE_LINES },
// --- contenu_general edit page: translate ---
{ type: 'translate', selector: '.pods-form-ui-row-name-umr th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-thalim th', lines: TRANSLATE_LINES },
{ type: 'translate', selector: '.pods-form-ui-row-name-siecles th', lines: TRANSLATE_LINES },
// --- user/profile edit page: translate ---
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-1 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-2 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-3 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-4 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-1 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-2 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-3 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-1 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-2 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-3 th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affiliation-autre th', lines: TRANSLATE_LINES },
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-de-these th', lines: TRANSLATE_LINES },
// --- taxonomy edit pages: translate ---
{ type: 'translate', page: 'taxonomy', selector: 'label[for="name"]', lines: TRANSLATE_LINES },
// --- user/profile edit page: info ---
{
page: 'user',
selector: '.pods-form-ui-label-pods-meta-identifiant-hal',
lines: [
'Renseigner votre idHAL (en lettres), pas votre PersonId (en chiffres).'
]
},
{
page: 'user',
selector: '.pods-form-ui-label-pods-meta-affichage-du-statut-1',
lines: [
'Texte de statut affiché sur le profil publique.'
]
}
];
var _popoverCloseHandlerRegistered = false;
function initInfoPopovers(currentPage) {
currentPage = currentPage || 'post';
INFO_TIPS.forEach(function(tip) {
if ((tip.page || 'post') !== currentPage) return;
var el = document.querySelector(tip.selector);
if (!el) return;
var isTranslate = tip.type === 'translate';
var btn = document.createElement('button');
btn.type = 'button';
btn.className = isTranslate ? 'thalim-translate-btn' : 'thalim-info-btn';
btn.setAttribute('aria-label', isTranslate ? 'Traduction bilingue' : 'Informations');
btn.innerHTML = isTranslate ? TRANSLATE_ICON : INFO_ICON;
var popover = document.createElement('div');
popover.className = 'thalim-info-popover' + (isTranslate ? ' thalim-translate-popover' : '');
popover.innerHTML = tip.lines.map(function(line) {
return '<p>' + line + '</p>';
}).join('');
var wrapper = document.createElement('span');
wrapper.className = 'thalim-info-wrapper';
wrapper.appendChild(btn);
wrapper.appendChild(popover);
el.appendChild(wrapper);
btn.addEventListener('click', function(e) {
e.stopPropagation();
var isOpen = popover.classList.contains('is-open');
document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
p.classList.remove('is-open');
});
if (!isOpen) {
var rect = btn.getBoundingClientRect();
popover.style.top = (rect.bottom + 6) + 'px';
popover.style.left = (rect.left + rect.width / 2) + 'px';
popover.classList.add('is-open');
}
});
popover.addEventListener('click', function(e) {
e.stopPropagation();
});
});
if (!_popoverCloseHandlerRegistered) {
_popoverCloseHandlerRegistered = true;
document.addEventListener('click', function() {
document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
p.classList.remove('is-open');
});
});
}
}
// Only native WP field sections — never touch Pods tables (they may contain TinyMCE editors)
var PROFILE_SECTION_KEYS = [
'user-language-wrap',
'user-first-name-wrap',
'user-email-wrap',
'user-pass1-wrap',
'upload-avatar-row',
];
// Desired order. Groups of 2 are wrapped in a flex row and displayed side by side.
var PROFILE_ORDER = [
['user-first-name-wrap', 'upload-avatar-row'],
['user-email-wrap'],
['user-language-wrap', 'user-pass1-wrap'],
];
function reorderProfileSections() {
var form = getProfileForm();
if (!form) return;
var pairMap = {};
form.querySelectorAll('table.form-table').forEach(function(table) {
PROFILE_SECTION_KEYS.forEach(function(key) {
if (pairMap[key] || !table.querySelector('.' + key)) return;
// Find the associated heading: first try preceding sibling in same parent,
// then look for an h2/h3 inside the same wrapper element.
var h2 = null;
var el = table.previousElementSibling;
while (el) {
if (el.tagName === 'H2' || el.tagName === 'H3') { h2 = el; break; }
if (el.tagName === 'TABLE') break;
el = el.previousElementSibling;
}
if (!h2 && table.parentElement !== form) {
h2 = table.parentElement.querySelector('h2, h3');
}
// The unit to move: if h2 and table share a non-form wrapper, move the wrapper.
var wrapper = null;
if (h2 && h2.parentElement !== form && h2.parentElement === table.parentElement) {
wrapper = h2.parentElement;
}
pairMap[key] = { h2: h2, table: table, wrapper: wrapper };
});
});
// Remove all matched units from DOM (dedup by actual element)
var removed = new Set();
function removeEl(el) {
if (el && !removed.has(el)) { removed.add(el); el.remove(); }
}
Object.values(pairMap).forEach(function(unit) {
if (unit.wrapper) { removeEl(unit.wrapper); }
else { removeEl(unit.h2); removeEl(unit.table); }
});
// Re-insert in declared order before the submit button
var submitAnchor = form.querySelector('p.submit');
function append(el) {
if (submitAnchor && submitAnchor.parentNode) form.insertBefore(el, submitAnchor);
else form.appendChild(el);
}
function appendUnit(unit) {
if (unit.wrapper) { append(unit.wrapper); }
else { if (unit.h2) append(unit.h2); append(unit.table); }
}
PROFILE_ORDER.forEach(function(group) {
var available = group.filter(function(key) { return !!pairMap[key]; });
if (!available.length) return;
// Dedup: two keys may resolve to the same table/wrapper
var seen = new Set();
var units = [];
available.forEach(function(key) {
var unit = pairMap[key];
var id = unit.wrapper || unit.table;
if (!seen.has(id)) { seen.add(id); units.push(unit); }
});
if (units.length === 1) {
appendUnit(units[0]);
} else {
var row = document.createElement('div');
row.className = 'profile-section-row';
units.forEach(function(unit) {
var col = document.createElement('div');
col.className = 'profile-section-col';
if (unit.wrapper) { col.appendChild(unit.wrapper); }
else { if (unit.h2) col.appendChild(unit.h2); col.appendChild(unit.table); }
row.appendChild(col);
});
append(row);
}
});
}
function initProfileEditors() {
reorderProfileSections();
initInfoPopovers('user');
// Hide the "À propos du compte" section heading
document.querySelectorAll('#your-profile h2, #adduser h2, #createuser h2').forEach(function(h2) {
if (h2.textContent.trim() === '\u00c0 propos du compte') {
h2.style.display = 'none';
}
});
// Rename "Rôle" label to "Rôle sur le site"
var roleLabel = document.querySelector('label[for="role"]');
if (roleLabel && roleLabel.textContent.trim() === 'R\u00f4le') {
roleLabel.textContent = 'R\u00f4le sur le site';
}
}
// Gutenberg's Popover component closes on outside click via focusout detection.
// But if focus never enters the popover, focusout never fires and clicking outside
// does nothing. Fix: focus the popover container as soon as it appears in the DOM.
function initDatePickerPopoverFix() {
var observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var added = mutations[i].addedNodes;
for (var j = 0; j < added.length; j++) {
var node = added[j];
if (node.nodeType !== 1) continue;
var content = node.classList.contains('components-popover__content')
? node
: node.querySelector && node.querySelector('.components-popover__content');
if (content) {
var c = content;
requestAnimationFrame(function() {
if (!c.hasAttribute('tabindex')) c.setAttribute('tabindex', '-1');
c.focus();
});
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
function updateMembresGridSeparator() {
var sep = document.querySelector('#pods-meta-membres .membres-grid-separator');
if (!sep) return;
var autreRows = document.querySelectorAll('#pods-meta-membres [class*="pods-form-ui-row-name-autre-"]');
var anyVisible = Array.from(autreRows).some(function(row) {
return row.style.display !== 'none';
});
sep.style.display = anyVisible ? '' : 'none';
}
// Inject a "Type de programme" filter select into the taxonomy search form.
// The form already has hidden taxonomy/post_type fields so the select value
// is submitted with them and picked up by pre_get_terms server-side.
function initProgrammeFilter() {
var form = document.querySelector('form.search-form');
if (!form) return;
var types = [
'Programme subventionné',
'Autre programme',
'Ancien programme'
];
// Read current filter value from the URL.
var params = new URLSearchParams(window.location.search);
var current = params.get('type_de_programme') || '';
var select = document.createElement('select');
select.name = 'type_de_programme';
select.id = 'filter-type-de-programme';
select.style.cssText = 'margin-right:6px;';
var blank = document.createElement('option');
blank.value = '';
blank.textContent = 'Tous les types';
select.appendChild(blank);
types.forEach(function(type) {
var opt = document.createElement('option');
opt.value = type;
opt.textContent = type;
if (type === current) opt.selected = true;
select.appendChild(opt);
});
// Insert before the first <p> (search-box) inside the form.
var searchBox = form.querySelector('p.search-box');
form.insertBefore(select, searchBox || null);
}
$(document).ready(function() {
renameArticlesToAnnonces();
if (isPostEditPage()) {
setupBodyTabsDom();
}
setTimeout(() => {
if (isPostEditPage()) {
initPostEditPage();
}
if (isProfileEditPage()) {
initProfileEditors();
}
var TRANSLATE_TAXONOMIES = ['axe_thematique', 'programme_de_recherche', 'post_tag'];
var isTaxonomyListPage = TRANSLATE_TAXONOMIES.some(function(tax) {
return window.location.search.indexOf('taxonomy=' + tax) !== -1;
});
if (isTaxonomyListPage) {
initInfoPopovers('taxonomy');
}
if (window.location.search.indexOf('taxonomy=programme_de_recherche') !== -1) {
initProgrammeFilter();
}
document.body.classList.add('admin-mods-ready');
}, 100);
// Fallback: force reveal after 2s in case the 100ms path failed (e.g. JS error mid-init)
setTimeout(() => {
document.body.classList.add('admin-mods-ready');
}, 2000);
$('#pods-form-ui-pods-meta-categorie').change(function() {
setTimeout(function() {
updatePostboxVisibility();
updateMembresGridSeparator();
}, 10);
});
if (isProfileEditPage() || window.pagenow === 'edit-tags' || window.pagenow === 'term') {
$(window).on('load', function() {
var scope = getProfileForm() || document;
scope.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) {
if (!ta.id) return;
ensureVisualMode(ta.id);
});
});
}
if (isPodsModal()) {
$(window).on('load', function() {
var itemId = $('#post_ID').val();
if (window.PodsDFV && itemId) {
window.PodsDFV.setFieldValue('post', itemId, 'categorie', '12', 0);
}
// Lock category select to 12 in iframe — delay to run after Pods React re-render
setTimeout(function() {
var $select = $('#pods-form-ui-pods-meta-categorie');
if ($select.length) {
$select.find('option').each(function() {
this.disabled = this.value !== '12';
});
$select.val('12');
}
updatePostboxVisibility();
}, 200);
});
}
});
})(jQuery);

119
js/adminFormRestore.js Normal file
View File

@@ -0,0 +1,119 @@
(function($) {
'use strict';
var STORAGE_PREFIX = 'thalim_form_restore_';
var MAX_AGE_MS = 10 * 60 * 1000; // 10 min
function getPostId() {
return $('#post_ID').val() || 'new';
}
function getStorageKey() {
return STORAGE_PREFIX + getPostId();
}
function saveFormData() {
var data = { timestamp: Date.now() };
var $title = $('#title');
if ($title.length) data.title = $title.val();
var $content = $('#content');
if ($content.length) {
if (typeof tinyMCE !== 'undefined' && tinyMCE.get('content')) {
data.content = tinyMCE.get('content').getContent();
} else {
data.content = $content.val();
}
}
$('[name^="pods_meta_"]').each(function() {
data[this.name] = $(this).val();
});
try {
sessionStorage.setItem(getStorageKey(), JSON.stringify(data));
} catch (e) {}
}
function restoreFormData() {
var navEntries = performance.getEntriesByType('navigation');
if (!navEntries.length || navEntries[0].type !== 'back_forward') return;
var key = getStorageKey();
var stored, data;
try {
stored = sessionStorage.getItem(key);
if (!stored) return;
data = JSON.parse(stored);
} catch (e) { return; }
if (!data.timestamp || Date.now() - data.timestamp > MAX_AGE_MS) {
try { sessionStorage.removeItem(key); } catch (e) {}
return;
}
// Restaurer titre
if (data.title !== undefined) $('#title').val(data.title);
// Restaurer contenu (TinyMCE ou textarea brut)
if (data.content !== undefined) {
$('#content').val(data.content);
if (typeof tinyMCE !== 'undefined') {
var ed = tinyMCE.get('content');
if (ed) {
ed.setContent(data.content);
} else {
tinyMCE.on('AddEditor', function(e) {
if (e.editor.id === 'content') {
e.editor.on('init', function() {
e.editor.setContent(data.content);
});
}
});
}
}
}
// Restaurer champs Pods après rendu React
var restorePods = function() {
setTimeout(function() {
var postId = getPostId();
Object.keys(data).forEach(function(name) {
if (!name.startsWith('pods_meta_')) return;
var fieldName = name.replace(/^pods_meta_/, '');
var value = data[name];
var $el = $('[name="' + name + '"]');
if ($el.length) $el.val(value).trigger('change');
if (window.PodsDFV && postId) {
try { window.PodsDFV.setFieldValue('post', postId, fieldName, value, 0); } catch (e) {}
}
});
}, 300);
};
if (document.readyState === 'complete') {
restorePods();
} else {
$(window).on('load', restorePods);
}
// Notice informative
var $notice = $('<div class="notice notice-info is-dismissible"><p>Votre contenu a \u00e9t\u00e9 restaur\u00e9 suite \u00e0 une erreur de validation. V\u00e9rifiez les champs obligatoires avant de publier.</p></div>');
$('#wpbody-content').find('.wrap').first().find('h1').after($notice);
}
$(document).ready(function() {
if (window.pagenow !== 'post' && window.pagenow !== 'post-new') return;
// Nettoyage si sauvegarde réussie
if ($('.notice-success, #message.updated').length) {
try { sessionStorage.removeItem(getStorageKey()); } catch (e) {}
return;
}
$('#post').on('submit', saveFormData);
restoreFormData();
});
})(jQuery);

146
js/agendaView.js Normal file
View File

@@ -0,0 +1,146 @@
(function () {
'use strict';
document.addEventListener('DOMContentLoaded', function () {
var toggleBtn = document.querySelector('.agenda-toggle-btn');
var gridSections = document.getElementById('grid-sections');
var agendaEl = document.getElementById('agenda-view');
if (!toggleBtn || !agendaEl) return;
var swiper = null;
var agendaPage = 0;
var agendaDone = false;
var agendaLoading = false;
var swiperWrapper = document.getElementById('agenda-swiper-wrapper');
var agendaSpinner = document.getElementById('agenda-spinner');
var categoryId = agendaEl.dataset.category || '';
var includeChildren = agendaEl.dataset.includeChildren || '0';
var axe = agendaEl.dataset.axe || '';
var dateFrom = agendaEl.dataset.dateFrom || '';
var dateTo = agendaEl.dataset.dateTo || '';
function showAgenda() {
if (gridSections) gridSections.style.display = 'none';
agendaEl.classList.add('is-active');
toggleBtn.innerHTML =
'<i class="iconoir-view-grid"></i>' +
(agendaViewData.lang === 'en' ? 'Switch to grid view' : 'Passer à la vue grille');
if (!swiper) {
loadMoreAgenda(function (data) {
var todayOffset = Math.max(0, ((data && data.today_offset) || 0) - 1);
var anchorPage = Math.ceil((todayOffset + 1) / 12);
if (anchorPage > 1) {
chainLoadPages(2, anchorPage, function () {
initSwiper(todayOffset);
});
} else {
initSwiper(todayOffset);
}
});
}
}
function showGrid() {
agendaEl.classList.remove('is-active');
if (gridSections) gridSections.style.display = '';
toggleBtn.innerHTML =
'<i class="iconoir-calendar"></i>' +
(agendaViewData.lang === 'en' ? 'Switch to agenda view' : 'Passer à la vue agenda');
}
function initSwiper(initialSlide) {
swiper = new Swiper(agendaEl.querySelector('.agenda-swiper'), {
slidesPerView: 1.2,
spaceBetween: 20,
initialSlide: initialSlide || 0,
navigation: {
nextEl: agendaEl.querySelector('.agenda-swiper-prev'),
prevEl: agendaEl.querySelector('.agenda-swiper-next'),
},
breakpoints: {
640: { slidesPerView: 2, spaceBetween: 24 },
1024: { slidesPerView: 3, spaceBetween: 32 },
},
on: {
reachEnd: function () {
if (!agendaDone) loadMoreAgenda();
},
},
});
swiper.changeLanguageDirection('rtl');
}
function chainLoadPages(fromPage, toPage, callback) {
if (fromPage > toPage) { callback(); return; }
loadMoreAgenda(function () {
chainLoadPages(fromPage + 1, toPage, callback);
});
}
function loadMoreAgenda(callback) {
if (agendaLoading || agendaDone) return;
agendaLoading = true;
agendaPage++;
agendaSpinner.style.display = 'flex';
var data = new FormData();
data.append('action', 'load_more_agenda');
data.append('page', agendaPage);
data.append('nonce', agendaViewData.nonce);
data.append('lang', agendaViewData.lang);
if (categoryId) data.append('category', categoryId);
if (includeChildren==='1') data.append('include_children', '1');
if (axe) data.append('axe', axe);
if (dateFrom) data.append('date_from', dateFrom);
if (dateTo) data.append('date_to', dateTo);
fetch(agendaViewData.ajaxUrl, { method: 'POST', body: data })
.then(function (r) { return r.json(); })
.then(function (result) {
agendaSpinner.style.display = 'none';
agendaLoading = false;
if (result.success && result.data.html) {
var tmp = document.createElement('div');
tmp.innerHTML = result.data.html;
Array.from(tmp.children).forEach(function (slide) {
if (swiper) {
swiper.appendSlide(slide.outerHTML);
} else {
swiperWrapper.appendChild(slide);
}
});
if (swiper) swiper.update();
if (callback) callback(result.data);
} else {
agendaDone = true;
if (callback) callback(result.data);
}
})
.catch(function () {
agendaSpinner.style.display = 'none';
agendaLoading = false;
});
}
toggleBtn.addEventListener('click', function (e) {
e.preventDefault();
if (agendaEl.classList.contains('is-active')) {
var url = new URL(window.location.href);
url.searchParams.delete('view');
history.pushState({}, '', url.toString());
showGrid();
} else {
var url = new URL(window.location.href);
url.searchParams.set('view', 'agenda');
history.pushState({}, '', url.toString());
showAgenda();
}
});
// Initial state driven by server-side class
if (agendaEl.classList.contains('is-active')) {
showAgenda();
}
});
}());

577
js/animatedLogo.js Normal file
View File

@@ -0,0 +1,577 @@
const CURSOR_INFLUENCE_INNER = 150;
const CURSOR_INFLUENCE_OUTER = 300;
const EASING_FACTOR = 0.03;
const COLORS = [
'#e0775d',
'#7cc0c6',
'#e05680',
'#46ae51',
'#bb8dd9',
'#f7ff29',
];
class FloatingShape {
constructor(element, originalX, originalY, width, height) {
this.element = element;
this.width = width;
this.height = height;
this.originalX = originalX;
this.originalY = originalY;
this.posX = originalX;
this.posY = originalY;
this.targetX = originalX;
this.targetY = originalY;
}
update(mouseX, mouseY) {
const shapeCenterX = this.originalX + this.width / 2;
const shapeCenterY = this.originalY + this.height / 2;
const dx = mouseX - shapeCenterX;
const dy = mouseY - shapeCenterY;
const distance = Math.sqrt(dx * dx + dy * dy);
const innerRadius = CURSOR_INFLUENCE_INNER / 2;
const outerRadius = CURSOR_INFLUENCE_OUTER / 2;
if (distance < outerRadius && distance > 0) {
const dirX = dx / distance;
const dirY = dy / distance;
let strength, maxDisplacement;
if (distance < innerRadius) {
strength = (innerRadius - distance) / innerRadius;
maxDisplacement = innerRadius;
} else {
const outerZoneProgress = (outerRadius - distance) / (outerRadius - innerRadius);
strength = outerZoneProgress * 0.5;
maxDisplacement = innerRadius * 0.6;
}
this.targetX = this.originalX - dirX * strength * maxDisplacement;
this.targetY = this.originalY - dirY * strength * maxDisplacement;
} else {
this.targetX = this.originalX;
this.targetY = this.originalY;
}
this.posX += (this.targetX - this.posX) * EASING_FACTOR;
this.posY += (this.targetY - this.posY) * EASING_FACTOR;
}
render() {
this.element.style.transform = `translate3d(${this.posX}px, ${this.posY}px, 0)`;
}
}
class FloatingShapesManager {
constructor(containerId) {
this.container = document.getElementById(containerId);
if (!this.container) {
console.error(`Container #${containerId} not found`);
return;
}
this.shapes = [];
this.strokePaths = []; // Store all stroke paths for fill control
this.fillShape = null; // Reference to the filled shape SVG for fade control
this.fillShapeReady = false; // Track if fillshape animation has completed
this.thalimText = null; // Reference to the THALIM text element
this.textReady = false; // Track if text animation has completed
this.mouseX = 0;
this.mouseY = 0;
this.animationId = null;
this.isTouching = false; // Track if currently in a touch interaction
this.init();
}
init() {
const containerRect = this.container.getBoundingClientRect();
const containerWidth = containerRect.width;
const containerHeight = containerRect.height;
const shapeConfigs = [
{
id: 'shape-1',
svgPath: `${themeDirURI}/assets/logo-shapes/shape1.svg`,
baseWidth: 53.564522,
baseHeight: 112.37409,
scale: 1.5,
posX: this.isMobile() ? 20: 35,
posY: -20,
strokeWidth: 2,
animationDuration: 1.9,
gradientStart: COLORS[0], // orange
gradientEnd: '#e0b7ad'
},
{
id: 'shape-2',
svgPath: `${themeDirURI}/assets/logo-shapes/shape4.svg`,
baseWidth: 74.08564,
baseHeight: 121.90051,
scale: 1.5,
posX: 0,
posY: this.isMobile() ? -8 : -5,
strokeWidth: 2,
animationDuration: 2.5,
gradientStart: '#aec4c6',
gradientEnd: COLORS[1] // blue
},
{
id: 'shape-3',
svgPath: `${themeDirURI}/assets/logo-shapes/shape3.svg`,
baseWidth: 159.16571,
baseHeight: 87.756729,
scale: 1.5,
posX: 0,
posY: -10,
strokeWidth: 2,
animationDuration: 3.1,
gradientStart: COLORS[2], // pink
gradientEnd: '#e0b0be'
},
{
id: 'shape-4',
svgPath: `${themeDirURI}/assets/logo-shapes/shape2.svg`,
baseWidth: 143.26076,
baseHeight: 20.26207,
scale: 1.5,
posX: 0,
posY: this.isMobile() ? 20 : 45,
strokeWidth: 2,
animationDuration: 3.7,
gradientStart: '#8bc491',
gradientEnd: COLORS[3] // green
},
{
id: 'shape-5',
svgPath: `${themeDirURI}/assets/logo-shapes/shape5.svg`,
baseWidth: 155.66518,
baseHeight: 87.785599,
scale: 1.5,
posX: this.isMobile() ? 13 : 19.5,
posY: this.isMobile() ? -18 : -23.5,
strokeWidth: 2,
animationDuration: 4.3,
gradientStart: COLORS[4], // purple
gradientEnd: '#c9b0d9'
},
{
id: 'fillshape',
svgPath: `${themeDirURI}/assets/logo-shapes/fillshape.svg`,
baseWidth: 142.78297,
baseHeight: 72.903015,
scale: this.isMobile() ? 0.78 : 1.47,
posX: this.isMobile() ? 7.5 : 11,
posY: this.isMobile() ? -13 : -14.5,
isFilled: true,
targetOpacity: 1,
animationDuration: 0.9,
animationDelay: 3.7 // Start after longest stroke animation
}
];
// Create shapes with center-based positioning
const centerX = containerWidth / 2;
const centerY = containerHeight / 2;
// Apply mobile scale on smaller viewports
const mobileScale = 0.8;
shapeConfigs.forEach((config) => {
const scale = config.id === 'fillshape' ? config.scale : this.isMobile() ? mobileScale : config.scale;
const scaledWidth = config.baseWidth * scale;
const scaledHeight = config.baseHeight * scale;
// Calculate position from center with offset, minus half dimensions to center the shape
const x = centerX + (config.posX || 0) - scaledWidth / 2;
const y = centerY + (config.posY || 0) - scaledHeight / 2;
// Create animated stroke element
this.createAnimatedStrokeElement(config, scaledWidth, scaledHeight, x, y);
});
// Create THALIM text overlay
this.createThalimText(centerX, centerY);
this.container.addEventListener('mousemove', this.handleMouseMove.bind(this));
this.container.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
this.container.addEventListener('touchstart', this.handleTouchStart.bind(this));
this.container.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
this.container.addEventListener('touchend', this.handleTouchEnd.bind(this));
window.addEventListener('resize', this.handleResize.bind(this));
// Set sketch margin on mobile based on hero-logos height
this.setSketchMarginOnMobile();
this.animate();
}
isMobile() {
return window.innerWidth < 768; // tablet breakpoint from scss/_variables.scss
}
setSketchMarginOnMobile() {
if (this.isMobile()) {
const heroLogos = document.querySelector('.hero-logos');
if (heroLogos) {
const logoHeight = heroLogos.offsetHeight;
this.container.style.marginTop = `${logoHeight}px`;
}
} else {
// Reset margin-top on desktop (handled by CSS)
this.container.style.marginTop = '';
}
}
async createAnimatedStrokeElement(config, width, height, x, y) {
try {
const response = await fetch(config.svgPath);
const svgText = await response.text();
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml');
const svgElement = svgDoc.querySelector('svg');
if (!svgElement) {
console.error(`Failed to parse SVG for ${config.id}`);
return;
}
// Create shape div for physics
const shapeDiv = document.createElement('div');
shapeDiv.className = 'floating-shape';
shapeDiv.id = config.id;
shapeDiv.style.zIndex = config.isFilled ? '10' : '1'; // Fillshape above strokes
// Set SVG dimensions
svgElement.setAttribute('width', width);
svgElement.setAttribute('height', height);
if (config.isFilled) {
// Handle filled shapes (fade-in animation)
svgElement.style.opacity = '0';
svgElement.style.transition = 'opacity 0.3s ease-out'; // Smooth fade on mouse interaction
const duration = config.animationDuration || 1.5;
const delay = config.animationDelay || 0;
const targetOpacity = config.targetOpacity || 0.5;
svgElement.style.setProperty('--target-opacity', targetOpacity);
svgElement.style.animation = `fadeIn ${duration}s ease-in-out ${delay}s forwards`;
// Remove animation after it completes so we can control opacity manually
setTimeout(() => {
svgElement.style.animation = 'none';
svgElement.style.opacity = targetOpacity;
this.fillShapeReady = true; // Mark fillshape as ready for interaction
}, (delay + duration) * 1000);
// Store reference for mouse interaction
this.fillShape = svgElement;
this.fillShapeOpacity = targetOpacity;
// Position the fillshape div (since it's not in the physics system)
shapeDiv.style.transform = `translate3d(${x}px, ${y}px, 0)`;
} else {
// Handle stroked shapes (gradient + stroke drawing animation)
// Create gradient for stroke
const gradientId = `gradient-${config.id}`;
// Create defs element if it doesn't exist
let defs = svgElement.querySelector('defs');
if (!defs) {
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
svgElement.insertBefore(defs, svgElement.firstChild);
}
// Create linear gradient
const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
gradient.setAttribute('id', gradientId);
gradient.setAttribute('x1', '0%');
gradient.setAttribute('y1', '0%');
gradient.setAttribute('x2', '100%');
gradient.setAttribute('y2', '100%');
// Create gradient stops
const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
stop1.setAttribute('offset', '0%');
stop1.setAttribute('stop-color', config.gradientStart);
const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
stop2.setAttribute('offset', '100%');
stop2.setAttribute('stop-color', config.gradientEnd);
gradient.appendChild(stop1);
gradient.appendChild(stop2);
defs.appendChild(gradient);
// Apply stroke styling and animation to all paths in the SVG
const paths = svgElement.querySelectorAll('path, polyline, polygon, line, circle, ellipse, rect');
paths.forEach(path => {
// Set stroke properties
path.style.fill = 'white';
path.style.fillOpacity = '0'; // Start invisible
path.style.transition = 'fill-opacity 0.5s ease-in-out'; // Smooth fill changes
path.style.stroke = `url(#${gradientId})`;
// Don't override stroke-width - preserve SVG's compensated values
path.style.strokeLinecap = 'round';
path.style.strokeLinejoin = 'round';
// Calculate path length for animation
const pathLength = path.getTotalLength ? path.getTotalLength() : 1000;
// Set CSS variable for path length
path.style.setProperty('--path-length', pathLength);
// Set up stroke dash animation
path.style.strokeDasharray = pathLength;
path.style.strokeDashoffset = pathLength;
// Create CSS animation (plays once and stays completed)
const duration = config.animationDuration || 3;
path.style.animation = `drawStroke ${duration}s ease-in-out forwards`;
// Store path reference for fill control
this.strokePaths.push(path);
});
// Fade in white fill when text starts appearing (at 4.3s)
setTimeout(() => {
paths.forEach(path => {
path.style.fillOpacity = '0.7';
});
}, 4300);
}
// Append SVG to shape div
shapeDiv.appendChild(svgElement);
this.container.appendChild(shapeDiv);
// Create FloatingShape instance (only for shapes with physics, not filled shapes)
if (!config.isFilled) {
const floatingShape = new FloatingShape(shapeDiv, x, y, width, height);
this.shapes.push(floatingShape);
}
} catch (error) {
console.error(`Error loading SVG for ${config.id}:`, error);
}
}
createThalimText(centerX, centerY) {
// Create container for the text
const textContainer = document.createElement('div');
textContainer.className = 'thalim-text';
textContainer.style.top = '-20px';
textContainer.style.left = '10px';
textContainer.style.transform = `translate3d(${centerX}px, ${centerY}px, 0) translate(-50%, -50%)`; // Center the text
textContainer.style.color = '#000000cc';
// Create individual letter spans
const letters = 'thalim'.split('');
letters.forEach((letter, index) => {
const span = document.createElement('span');
span.textContent = letter;
span.style.opacity = '0';
span.style.animation = `letterAppear 0.8s ease-in-out ${4.2 + index * 0.1}s forwards`;
textContainer.appendChild(span);
});
// Mark text as ready after last letter finishes animating
// Last letter: 4.2s + 0.5s (6th letter) + 0.8s (duration) = 5.5s
setTimeout(() => {
this.textReady = true;
}, 5500);
this.container.appendChild(textContainer);
this.thalimText = textContainer;
}
handleMouseMove(e) {
// Ignore mouse events during touch interactions (prevents interference from synthetic mouse events)
if (this.isTouching) {
return;
}
const rect = this.container.getBoundingClientRect();
this.mouseX = e.clientX - rect.left;
this.mouseY = e.clientY - rect.top;
// Fade out fillshape and text on mouse move (only if they've finished appearing)
if (this.fillShape && this.fillShapeReady) {
this.fillShape.style.opacity = '0';
}
if (this.thalimText && this.textReady) {
this.thalimText.style.opacity = '0';
}
// Fade out white fills on stroke shapes
this.strokePaths.forEach(path => {
path.style.fillOpacity = '0';
});
}
handleMouseLeave() {
// Move mouse far away to trigger return animation
this.mouseX = -10000;
this.mouseY = -10000;
// Fade fillshape and text back in after delay (let strokes settle first)
// Only if they have completed their initial animations
if (this.fillShape && this.fillShapeReady) {
setTimeout(() => {
this.fillShape.style.opacity = this.fillShapeOpacity;
}, 800); // Delay to allow strokes to return to position
}
if (this.thalimText && this.textReady) {
setTimeout(() => {
this.thalimText.style.opacity = '1';
}, 800);
}
// Fade white fills back in on stroke shapes
setTimeout(() => {
this.strokePaths.forEach(path => {
path.style.fillOpacity = '0.7';
});
}, 800);
}
handleTouchStart() {
// Mark that we're in a touch interaction to prevent mouse event interference
this.isTouching = true;
}
handleTouchMove(e) {
e.preventDefault(); // Prevent scrolling while interacting with sketch
const rect = this.container.getBoundingClientRect();
const touch = e.touches[0];
this.mouseX = touch.clientX - rect.left;
this.mouseY = touch.clientY - rect.top;
// Fade out fillshape and text on touch move (only if they've finished appearing)
if (this.fillShape && this.fillShapeReady) {
this.fillShape.style.opacity = '0';
}
if (this.thalimText && this.textReady) {
this.thalimText.style.opacity = '0';
}
// Fade out white fills on stroke shapes
this.strokePaths.forEach(path => {
path.style.fillOpacity = '0';
});
}
handleTouchEnd() {
// Reset shapes to original position
this.handleMouseLeave();
// Clear touch flag after a delay to ignore synthetic mouse events
setTimeout(() => {
this.isTouching = false;
}, 500);
}
handleResize() {
// Don't refresh on mobile (prevents refresh during scroll when browser bar appears/disappears)
if (this.isMobile()) {
// Still update margin-top on mobile if needed
this.setSketchMarginOnMobile();
return;
}
// Debounced resize: only recreate after user stops resizing for 250ms
// This prevents page crashes from rapid resize events
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
this.resizeTimeout = setTimeout(() => {
// Stop animation loop before destroying
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
// Clear shapes and container
this.shapes = [];
this.container.innerHTML = '';
// Reinitialize
this.init();
}, 250);
}
animate() {
// Update all shapes
this.shapes.forEach(shape => {
shape.update(this.mouseX, this.mouseY);
shape.render();
});
// Continue loop
this.animationId = requestAnimationFrame(this.animate.bind(this));
}
destroy() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
this.container.removeEventListener('mousemove', this.handleMouseMove);
this.container.removeEventListener('mouseleave', this.handleMouseLeave);
this.container.removeEventListener('touchstart', this.handleTouchStart);
this.container.removeEventListener('touchmove', this.handleTouchMove);
this.container.removeEventListener('touchend', this.handleTouchEnd);
window.removeEventListener('resize', this.handleResize);
}
}
// Inject CSS for stroke and fade animations
const style = document.createElement('style');
style.textContent = `
@keyframes drawStroke {
0% {
stroke-dashoffset: var(--path-length);
opacity: 0;
}
10% {
opacity: 1;
}
100% {
stroke-dashoffset: 0;
opacity: 1;
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: var(--target-opacity);
}
}
@keyframes letterAppear {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
`;
document.head.appendChild(style);
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.floatingShapes = new FloatingShapesManager('sketch');
});
} else {
window.floatingShapes = new FloatingShapesManager('sketch');
}

28
js/annoncesSwiper.js Normal file
View File

@@ -0,0 +1,28 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-swiper]').forEach(function (section) {
new Swiper(section.querySelector('.swiper'), {
navigation: {
addIcons: false,
nextEl: section.querySelector('.swiper-button-next'),
prevEl: section.querySelector('.swiper-button-prev'),
},
autoplay: {
delay: 5000,
disableOnInteraction: false,
pauseOnMouseEnter: true,
},
slidesPerView: 1,
spaceBetween: 30,
breakpoints: {
768: {
slidesPerView: 2,
spaceBetween: 40,
},
1024: {
slidesPerView: 3,
spaceBetween: 50,
},
},
});
});
});

138
js/categoryFilters.js Normal file
View File

@@ -0,0 +1,138 @@
document.addEventListener('DOMContentLoaded', function () {
// ── Filters toggle ────────────────────────────────────────
var toggleBtn = document.getElementById('category-filters-toggle');
var filtersEl = document.getElementById('category-filters');
if (toggleBtn && filtersEl) {
toggleBtn.addEventListener('click', function () {
var isOpen = filtersEl.classList.toggle('is-open');
toggleBtn.classList.toggle('is-open', isOpen);
toggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
});
}
var dateBtn = document.getElementById('filter-date-btn');
var datePopover = document.getElementById('filter-date-popover');
var dateFrom = document.getElementById('filter-date-from');
var dateTo = document.getElementById('filter-date-to');
var dateApply = document.getElementById('filter-date-apply');
var axeBtn = document.getElementById('filter-axe-btn');
var axePopover = document.getElementById('filter-axe-popover');
if (!dateBtn) return;
// Build URL with updated query params
function buildUrl(params) {
var url = new URL(window.location.href);
Object.keys(params).forEach(function (key) {
if (params[key]) {
url.searchParams.set(key, params[key]);
} else {
url.searchParams.delete(key);
}
});
return url.toString();
}
function formatDate(d) {
var y = d.getFullYear();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
return y + '-' + m + '-' + day;
}
// Dropdown toggle
function openDropdown(popover) {
popover.style.display = '';
popover.closest('.filter-dd').classList.add('is-open');
}
function closeDropdown(popover) {
popover.style.display = 'none';
popover.closest('.filter-dd').classList.remove('is-open');
}
function toggleDropdown(popover) {
if (popover.style.display === 'none') {
openDropdown(popover);
} else {
closeDropdown(popover);
}
}
// Close dropdowns on outside click
document.addEventListener('click', function (e) {
if (!e.target.closest('#filter-date-dd')) {
closeDropdown(datePopover);
}
if (axePopover && !e.target.closest('#filter-axe-dd')) {
closeDropdown(axePopover);
}
});
// --- Date dropdown ---
dateBtn.addEventListener('click', function (e) {
e.stopPropagation();
if (axePopover) closeDropdown(axePopover);
toggleDropdown(datePopover);
});
// Date presets
datePopover.addEventListener('click', function (e) {
var preset = e.target.dataset.preset;
if (!preset) return;
var now = new Date();
var from, to;
if (preset === 'week') {
var day = now.getDay();
var diff = day === 0 ? 6 : day - 1;
from = new Date(now);
from.setDate(now.getDate() - diff);
to = new Date(from);
to.setDate(from.getDate() + 6);
} else if (preset === 'month') {
from = new Date(now.getFullYear(), now.getMonth(), 1);
to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
} else if (preset === 'lastmonth') {
from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
to = new Date(now.getFullYear(), now.getMonth(), 0);
} else if (preset === 'upcoming') {
from = now;
to = null;
}
window.location.href = buildUrl({
date_from: formatDate(from),
date_to: to ? formatDate(to) : ''
});
});
// Date apply → navigate
dateApply.addEventListener('click', function () {
window.location.href = buildUrl({
date_from: dateFrom.value,
date_to: dateTo.value
});
});
// --- Axe dropdown ---
if (axeBtn && axePopover) {
axeBtn.addEventListener('click', function (e) {
e.stopPropagation();
closeDropdown(datePopover);
toggleDropdown(axePopover);
});
axePopover.addEventListener('click', function (e) {
var li = e.target.closest('[data-axe-id]');
if (!li) return;
if (li.dataset.axeHref) {
window.location.href = li.dataset.axeHref;
} else {
window.location.href = buildUrl({ axe: li.dataset.axeId });
}
});
}
});

22
js/coloredWordsHero.js Normal file
View File

@@ -0,0 +1,22 @@
document.addEventListener('DOMContentLoaded', function() {
if (document.querySelector('body').classList.contains('home')) {
const colors = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9'];
const timeouts = new Map();
// Color changing on hover
document.querySelectorAll('.color-changer').forEach(element => {
element.addEventListener('mouseenter', (e) => {
if (timeouts.has(e.target)) {
clearTimeout(timeouts.get(e.target));
}
e.target.style.color = colors[Math.floor(Math.random() * colors.length)];
const timeoutId = setTimeout(() => {
e.target.style.color = 'black';
timeouts.delete(e.target);
}, 2000);
timeouts.set(e.target, timeoutId);
});
});
}
});

43
js/fitPostCardTitle.js Normal file
View File

@@ -0,0 +1,43 @@
document.addEventListener('DOMContentLoaded', function () {
function fitTitles() {
var cards = document.querySelectorAll('.post-card');
cards.forEach(function (card) {
var h2 = card.querySelector('.gradient-container h2');
if (!h2) return;
var container = h2.closest('.gradient-container');
var maxHeight = container.clientHeight;
var fontSize = parseFloat(window.getComputedStyle(h2).fontSize);
var minSize = 12;
h2.style.fontSize = '';
fontSize = parseFloat(window.getComputedStyle(h2).fontSize);
while (h2.scrollHeight > maxHeight && fontSize > minSize) {
fontSize -= 1;
h2.style.fontSize = fontSize + 'px';
}
});
}
window.fitPostCardTitles = fitTitles;
fitTitles();
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(fitTitles, 200);
});
// Re-fit after infinite scroll loads new cards
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
if (m.addedNodes.length) fitTitles();
});
});
var grid = document.getElementById('post-grid');
if (grid) {
observer.observe(grid, { childList: true });
}
});

37
js/frenchTypography.js Normal file
View File

@@ -0,0 +1,37 @@
/**
* French typography: replaces normal spaces with non-breaking spaces
* where French typographic rules require them.
*/
function applyFrenchTypography(elements) {
elements.forEach(function (el) {
var html = el.innerHTML;
// Collapse multiple spaces into one (outside HTML tags)
html = html.replace(/([^<>]) {2,}(?=[^<>])/g, '$1 ');
// Space before ? ! : ; »
html = html.replace(/ ([?!:;»])/g, '\u00A0$1');
// Space after «
html = html.replace(/(«) /g, '$1\u00A0');
// "p. 42" → non-breaking space after "p."
html = html.replace(/\bp\. /g, 'p.\u00A0');
// "n° 3" → non-breaking space after "n°"
html = html.replace(/\bn° /g, 'n°\u00A0');
el.innerHTML = html;
});
}
document.addEventListener('DOMContentLoaded', function () {
applyFrenchTypography(document.querySelectorAll('.post-card h2, .post-card .post-card__title'));
// Re-apply on any post-card dynamically added anywhere on the page
new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
m.addedNodes.forEach(function (node) {
if (node.nodeType !== 1) return;
var targets = node.classList && node.classList.contains('post-card')
? node.querySelectorAll('h2, .post-card__title')
: node.querySelectorAll('.post-card h2, .post-card .post-card__title');
applyFrenchTypography(targets);
});
});
}).observe(document.body, { childList: true, subtree: true });
});

16
js/imageSwiper.js Normal file
View File

@@ -0,0 +1,16 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-image-swiper]').forEach(function (container) {
new Swiper(container.querySelector('.swiper'), {
slidesPerView: 1,
loop: true,
navigation: {
nextEl: container.querySelector('.swiper-button-next'),
prevEl: container.querySelector('.swiper-button-prev'),
},
pagination: {
el: container.querySelector('.swiper-pagination'),
clickable: true,
},
});
});
});

71
js/infiniteScroll.js Normal file
View File

@@ -0,0 +1,71 @@
document.addEventListener('DOMContentLoaded', function () {
var sentinel = document.getElementById('scroll-sentinel');
var grid = document.getElementById('post-grid');
var spinner = document.getElementById('scroll-spinner');
if (!sentinel || !grid) return;
var page = 1;
var loading = false;
var done = false;
// Read filter params from sentinel data attributes (set server-side)
var categoryId = sentinel.dataset.category || '';
var axe = sentinel.dataset.axe || '';
var dateFrom = sentinel.dataset.dateFrom || '';
var dateTo = sentinel.dataset.dateTo || '';
var taxonomy = sentinel.dataset.taxonomy || '';
var termId = sentinel.dataset.term || '';
var catFilter = sentinel.dataset.filterCat || '';
var filterAutres = sentinel.dataset.filterAutres || '';
var excludeCats = sentinel.dataset.excludeCats || '';
var searchQuery = sentinel.dataset.search || '';
function fetchPosts() {
if (loading || done) return;
loading = true;
page++;
spinner.style.display = 'flex';
var data = new FormData();
data.append('action', 'load_more_posts');
data.append('page', page);
data.append('nonce', infiniteScrollData.nonce);
data.append('lang', infiniteScrollData.lang || 'fr');
if (categoryId) data.append('category', categoryId);
if (axe) data.append('axe', axe);
if (dateFrom) data.append('date_from', dateFrom);
if (dateTo) data.append('date_to', dateTo);
if (taxonomy) data.append('taxonomy', taxonomy);
if (termId) data.append('term', termId);
if (catFilter) data.append('filter_cat', catFilter);
if (filterAutres) data.append('filter_autres', filterAutres);
if (excludeCats) data.append('exclude_cats', excludeCats);
if (searchQuery) data.append('search', searchQuery);
fetch(infiniteScrollData.ajaxUrl, { method: 'POST', body: data })
.then(function (response) { return response.json(); })
.then(function (result) {
spinner.style.display = 'none';
if (result.success && result.data.html) {
grid.insertAdjacentHTML('beforeend', result.data.html);
loading = false;
} else {
done = true;
observer.disconnect();
}
})
.catch(function () {
spinner.style.display = 'none';
loading = false;
});
}
var observer = new IntersectionObserver(function (entries) {
if (entries[0].isIntersecting) {
fetchPosts();
}
}, { rootMargin: '200px' });
observer.observe(sentinel);
});

249
js/keywordCloud.js Normal file
View File

@@ -0,0 +1,249 @@
(function () {
'use strict';
document.addEventListener('DOMContentLoaded', function () {
var container = document.getElementById('keyword-container');
var tags = window.thalimTags;
if (!container || !tags || !tags.length) return;
// — init —
var GAP = 14;
var COLORS = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9'];
var colorTimeouts = new Map();
// Mélange aléatoire pour varier la disposition à chaque chargement
var shuffled = tags.slice().sort(function () { return Math.random() - 0.5; });
// Crée les éléments une seule fois (réutilisés à chaque layout)
var items = shuffled.map(function (tag) {
var el = document.createElement('a');
el.className = 'keyword';
el.href = tag.url;
el.textContent = tag.name;
el.addEventListener('mouseenter', function () {
if (colorTimeouts.has(el)) clearTimeout(colorTimeouts.get(el));
el.style.color = COLORS[Math.floor(Math.random() * COLORS.length)];
colorTimeouts.set(el, setTimeout(function () {
el.style.color = '';
colorTimeouts.delete(el);
}, 2000));
});
container.appendChild(el);
return { el: el, w: 0, h: 0 };
});
var gradOverlay = null;
var lastLayoutWidth = 0;
function layoutCloud() {
var cw = container.offsetWidth;
if (cw === lastLayoutWidth) return;
lastLayoutWidth = cw;
var cx = cw / 2;
var isMobile = cw < 768;
var a = cx - GAP; // demi-grand axe horizontal
var b = Math.round(cw * (isMobile ? 0.45 : 0.20)); // demi-petit axe vertical (plus haut sur mobile)
var R_V = Math.round(b * (isMobile ? 0.45 : 0.70)); // demi-axe vertical de la zone d'exclusion (plus petit sur mobile)
var R_H = Math.round(b * (isMobile ? 0.70 : 1.15)); // demi-axe horizontal (plus petit sur mobile)
// Re-mesurer les éléments (la taille peut changer avec le viewport)
// +1 compense les arrondis sub-pixel sur écrans haute densité
items.forEach(function (item) {
var rect = item.el.getBoundingClientRect();
item.w = Math.ceil(rect.width) + 1;
item.h = Math.ceil(rect.height) + 1;
});
var placed = [];
function hasOverlap(x, y, w, h) {
for (var i = 0; i < placed.length; i++) {
var p = placed[i];
if (x < p.x + p.w + GAP &&
x + w + GAP > p.x &&
y < p.y + p.h + GAP &&
y + h + GAP > p.y) return true;
}
return false;
}
// Placement contraint à l'anneau elliptique :
// { intérieur ellipse (a, b) } ∩ { extérieur ellipse d'exclusion (R_H, R_V) }
// Les candidats sont triés par distance à l'ellipse d'exclusion (croissante) :
// les mots s'accumulent au plus près du centre avant de s'étendre.
function findPos(w, h) {
var candidates = [];
for (var x = 0; x <= cw - w; x += 8) {
var dx = (x + w / 2) - cx;
var ratio = dx / a;
if (Math.abs(ratio) >= 1) continue;
// Limite verticale du centre du mot imposée par l'ellipse externe (avec marge h/2)
var maxPcy = b * Math.sqrt(1 - ratio * ratio);
if (maxPcy < h / 2) continue;
var maxAbsY = maxPcy - h / 2;
// Limite verticale minimale imposée par l'ellipse d'exclusion (R_H, R_V)
var minAbsY = (Math.abs(dx) >= R_H) ? 0
: R_V * Math.sqrt(Math.max(0, 1 - (dx * dx) / (R_H * R_H)));
if (minAbsY > maxAbsY) continue;
for (var absY = minAbsY; absY <= maxAbsY; absY += 4) {
// Distance normalisée à l'ellipse d'exclusion (0 = sur le bord)
var nx = dx / R_H, ny = absY / R_V;
var dist = Math.sqrt(nx * nx + ny * ny) - 1;
var yB = Math.round(absY - h / 2);
if (absY > 0) {
candidates.push({ x: x, y: Math.round(-absY - h / 2), dist: dist });
}
candidates.push({ x: x, y: yB, dist: dist });
}
}
// Trier par distance à l'ellipse d'exclusion croissante → attraction vers le centre
candidates.sort(function (ca, cb) { return ca.dist - cb.dist; });
for (var i = 0; i < candidates.length; i++) {
var c = candidates[i];
if (!hasOverlap(c.x, c.y, w, h)) return { x: c.x, y: c.y };
}
return null; // aucune position dans l'anneau
}
// Place les mots dans l'anneau elliptique, collecte les débordements
var overflow = [];
items.forEach(function (item) {
var pos = findPos(item.w, item.h);
if (pos) {
item.pos = { x: pos.x, y: pos.y, w: item.w, h: item.h };
placed.push(item.pos);
item.el.style.left = pos.x + 'px';
item.el.style.top = pos.y + 'px';
} else {
overflow.push(item);
}
});
// Placement des débordements en lignes centrées (style flex-wrap center)
if (overflow.length) {
var startY = placed.reduce(function (m, p) { return Math.max(m, p.y + p.h); }, 0) + GAP;
var rows = [];
var currentRow = [];
var currentRowW = 0;
overflow.forEach(function (item) {
var needed = currentRowW > 0 ? item.w + GAP : item.w;
if (currentRowW + needed > cw && currentRow.length > 0) {
rows.push(currentRow);
currentRow = [];
currentRowW = 0;
}
currentRow.push(item);
currentRowW += (currentRowW > 0 ? GAP : 0) + item.w;
});
if (currentRow.length) rows.push(currentRow);
var curY = startY;
rows.forEach(function (row) {
var rowW = row.reduce(function (s, item) { return s + item.w; }, 0) + (row.length - 1) * GAP;
var rowH = row.reduce(function (m, item) { return Math.max(m, item.h); }, 0);
var offsetX = Math.round((cw - rowW) / 2);
row.forEach(function (item) {
item.pos = { x: offsetX, y: curY, w: item.w, h: item.h };
placed.push(item.pos);
item.el.style.left = offsetX + 'px';
item.el.style.top = curY + 'px';
offsetX += item.w + GAP;
});
curY += rowH + GAP;
});
}
// Normalisation Y : cy=0 → shift px depuis le haut du conteneur
var minY = items.reduce(function (m, item) { return Math.min(m, item.pos.y); }, Infinity);
var shift = Math.max(0, GAP - minY);
items.forEach(function (item) {
item.pos.y += shift;
item.el.style.top = item.pos.y + 'px';
});
// Hauteur basée sur le contenu réel (évite le débordement sur mobile)
var maxPlacedY = items.reduce(function (m, item) { return Math.max(m, item.pos.y + item.pos.h); }, 0);
container.style.height = (maxPlacedY + GAP) + 'px';
// Dégradé sur un overlay séparé pour permettre l'animation scale au survol
if (gradOverlay) {
container.removeChild(gradOverlay);
}
gradOverlay = document.createElement('div');
gradOverlay.style.cssText =
'position:absolute;inset:0;pointer-events:none;' +
'background:radial-gradient(circle ' + Math.round(R_V * 2) + 'px at ' + cx + 'px ' + shift + 'px,' +
'rgba(247,255,41,1) 0%,rgba(247,255,41,0.6) 16%,rgba(247,255,41,0.15) 55%,transparent 70%);' +
'transform-origin:' + cx + 'px ' + shift + 'px;' +
'transition:transform 0.4s ease;';
container.insertBefore(gradOverlay, container.firstChild);
}
// Premier layout
layoutCloud();
// Événements du dégradé (référencent gradOverlay via closure)
container.addEventListener('mouseenter', function () {
if (!gradOverlay) return;
gradOverlay.style.transition = 'transform 0.4s ease';
gradOverlay.style.transform = 'scale(0.82)';
});
container.addEventListener('mousemove', function (e) {
if (!gradOverlay) return;
var cw = container.offsetWidth;
var cx = cw / 2;
var rect = container.getBoundingClientRect();
var dx = (e.clientX - rect.left) - cx;
var dy = (e.clientY - rect.top) - parseFloat(gradOverlay.style.transformOrigin.split(' ')[1]);
var tx = (-dx * 0.09).toFixed(2);
var ty = (-dy * 0.09).toFixed(2);
gradOverlay.style.transition = 'transform 0.15s ease-out';
gradOverlay.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(0.82)';
});
container.addEventListener('mouseleave', function () {
if (!gradOverlay) return;
gradOverlay.style.transition = 'transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
gradOverlay.style.transform = 'scale(1)';
});
// Resize avec debounce
var resizeTimer;
window.addEventListener('resize', function () {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
lastLayoutWidth = 0; // forcer le recalcul
layoutCloud();
}, 250);
});
// Animation d'apparition au scroll
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
items.forEach(function (item, i) {
item.el.style.animationDelay = (i * 0.03) + 's';
item.el.classList.add('keyword--visible');
});
observer.unobserve(container);
}
});
}, { threshold: 0.05 });
observer.observe(container);
});
})();

229
js/membresFilters.js Normal file
View File

@@ -0,0 +1,229 @@
document.addEventListener('DOMContentLoaded', function () {
// ── Filters toggle ────────────────────────────────────────
var membresToggleBtn = document.getElementById('membres-filters-toggle');
var membresFiltersEl = document.getElementById('membres-filters');
if (membresToggleBtn && membresFiltersEl) {
membresToggleBtn.addEventListener('click', function () {
var isOpen = membresFiltersEl.classList.toggle('is-open');
membresToggleBtn.classList.toggle('is-open', isOpen);
membresToggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
if (isOpen) {
var searchInput = document.getElementById('membres-search');
if (searchInput) searchInput.focus();
}
});
}
var activeRole = '';
var searchQuery = '';
// ── Dropdown (role filter) ────────────────────────────────
var roleBtn = document.getElementById('filter-role-btn');
var rolePopover = document.getElementById('filter-role-popover');
var roleLabel = document.getElementById('filter-role-label');
var roleReset = document.getElementById('role-reset');
var roleDd = document.getElementById('filter-role-dd');
function openRole() { rolePopover.style.display = ''; roleDd.classList.add('is-open'); }
function closeRole() { rolePopover.style.display = 'none'; roleDd.classList.remove('is-open'); }
roleBtn.addEventListener('click', function () {
rolePopover.style.display === 'none' ? openRole() : closeRole();
});
rolePopover.querySelectorAll('[data-role]').forEach(function (item) {
item.addEventListener('click', function () {
activeRole = item.dataset.role;
roleLabel.textContent = item.textContent.trim();
roleReset.style.display = activeRole ? '' : 'none';
roleDd.classList.toggle('is-active', !!activeRole);
closeRole();
updateChips();
applyFilters();
});
});
roleReset.addEventListener('click', function (e) {
e.preventDefault();
activeRole = '';
roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim();
roleReset.style.display = 'none';
roleDd.classList.remove('is-active');
updateChips();
applyFilters();
});
document.addEventListener('click', function (e) {
if (!roleDd.contains(e.target)) closeRole();
});
// ── Search input ──────────────────────────────────────────
var searchInput = document.getElementById('membres-search');
var searchReset = document.getElementById('search-reset');
searchInput.addEventListener('input', function () {
searchQuery = searchInput.value.trim().toLowerCase();
searchReset.style.display = searchQuery ? '' : 'none';
searchInput.classList.toggle('is-active', !!searchQuery);
updateChips();
applyFilters();
});
searchReset.addEventListener('click', function (e) {
e.preventDefault();
searchQuery = '';
searchInput.value = '';
searchReset.style.display = 'none';
searchInput.classList.remove('is-active');
updateChips();
applyFilters();
});
// ── Active chips ──────────────────────────────────────────
var chipsContainer = document.getElementById('membres-active-chips');
function makeChip(label, onRemove) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'filter-chip';
btn.innerHTML = label + '<i class="iconoir-xmark"></i>';
btn.addEventListener('click', onRemove);
return btn;
}
function updateChips() {
if (!chipsContainer) return;
chipsContainer.innerHTML = '';
if (activeRole) {
chipsContainer.appendChild(makeChip(activeRole, function () {
activeRole = '';
roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim();
roleReset.style.display = 'none';
roleDd.classList.remove('is-active');
updateChips();
applyFilters();
}));
}
if (searchQuery) {
chipsContainer.appendChild(makeChip(searchInput.value, function () {
searchQuery = '';
searchInput.value = '';
searchReset.style.display = 'none';
searchInput.classList.remove('is-active');
updateChips();
applyFilters();
}));
}
}
// ── Column sort ───────────────────────────────────────────
var sortKey = 'nom';
var sortDir = 'asc';
function getSortValue(row, key) {
if (key === 'nom') {
return (row.dataset.sortName || '').trim().toLowerCase();
}
if (key === 'statut') return (row.dataset.status || '').toLowerCase();
if (key === 'affiliation') return (row.dataset.affiliation || '').toLowerCase();
return '';
}
function applySortToTable(tbody) {
var rows = Array.from(tbody.querySelectorAll('tr'));
rows.sort(function (a, b) {
var va = getSortValue(a, sortKey);
var vb = getSortValue(b, sortKey);
var cmp = va.localeCompare(vb, 'fr', { sensitivity: 'base' });
return sortDir === 'asc' ? cmp : -cmp;
});
rows.forEach(function (row) { tbody.appendChild(row); });
}
function applySort() {
document.querySelectorAll('.membres-table tbody').forEach(function (tbody) {
if (!tbody.closest('table').hasAttribute('data-fixed-order')) {
applySortToTable(tbody);
}
restripe(tbody);
});
document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) {
th.classList.remove('sort-asc', 'sort-desc');
if (th.dataset.sort === sortKey) {
th.classList.add('sort-' + sortDir);
}
});
}
document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) {
th.addEventListener('click', function () {
var key = th.dataset.sort;
if (sortKey === key) {
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
} else {
sortKey = key;
sortDir = 'asc';
}
applySort();
});
});
applySort();
// ── Row restriping (fixes alternating colors after filter) ──
function restripe(tbody) {
var n = 0;
tbody.querySelectorAll('tr').forEach(function (row) {
if (row.style.display === 'none') return;
row.classList.toggle('is-even-row', n % 2 === 1);
n++;
});
}
// ── Apply both filters ────────────────────────────────────
function applyFilters() {
var isFiltering = activeRole || searchQuery;
document.querySelectorAll('.membres-item').forEach(function (item) {
// Filter rows by role and/or name
var rows = item.querySelectorAll('tbody tr');
var visible = 0;
rows.forEach(function (row) {
var name = (row.dataset.name || '').toLowerCase();
var status = (row.dataset.status || '').toLowerCase();
var affiliation = (row.dataset.affiliation || '').toLowerCase();
var roles = (row.dataset.roles || '').split('|').map(function (r) { return r.trim().toLowerCase(); });
var matchesRole = !activeRole || roles.includes(activeRole.toLowerCase());
var matchesName = !searchQuery || name.includes(searchQuery)
|| status.includes(searchQuery)
|| affiliation.includes(searchQuery);
var show = matchesRole && matchesName;
row.style.display = show ? '' : 'none';
if (show) visible++;
});
item.querySelectorAll('tbody').forEach(restripe);
// Hide group if no rows are visible
if (isFiltering && visible === 0) {
item.style.display = 'none';
return;
}
item.style.display = '';
var content = item.querySelector('.membres-content');
if (isFiltering) {
// Auto-expand when a filter is active
content.style.display = '';
item.classList.add('is-open');
} else {
// Collapse back when all filters are cleared
content.style.display = 'none';
item.classList.remove('is-open');
}
});
}
});

81
js/membresPopover.js Normal file
View File

@@ -0,0 +1,81 @@
document.addEventListener('DOMContentLoaded', function () {
var section = document.querySelector('.membres-section');
if (!section) return;
// Build the popover element once and append to body
var popover = document.createElement('div');
popover.id = 'membre-popover';
popover.innerHTML =
'<div class="membre-popover-inner">' +
'<img class="membre-popover-pic" src="" alt="">' +
'<div class="membre-popover-info">' +
'<p class="membre-popover-name"></p>' +
'<p class="membre-popover-status"></p>' +
'<p class="membre-popover-domaines"></p>' +
'<p class="membre-popover-autres"></p>' +
'</div>' +
'</div>';
document.body.appendChild(popover);
var pic = popover.querySelector('.membre-popover-pic');
var elName = popover.querySelector('.membre-popover-name');
var elStat = popover.querySelector('.membre-popover-status');
var elDom = popover.querySelector('.membre-popover-domaines');
var elAut = popover.querySelector('.membre-popover-autres');
var visible = false;
var currentRow = null;
// ── Show/hide via event delegation on the section ────────
section.addEventListener('mouseover', function (e) {
var row = e.target.closest('tbody tr');
if (!row || row === currentRow) return;
currentRow = row;
var avatar = row.dataset.avatar || '';
if (avatar) {
pic.src = avatar;
pic.style.display = '';
} else {
pic.src = '';
pic.style.display = 'none';
}
elName.textContent = row.dataset.name || '';
elStat.textContent = row.dataset.status || '';
var domVal = row.dataset.domaines || '';
elDom.textContent = domVal;
elDom.style.display = domVal ? '' : 'none';
var autVal = row.dataset.autresDomaines || '';
elAut.innerHTML = autVal.replace(/\n/g, '<br>');
elAut.style.display = autVal ? '' : 'none';
popover.classList.add('is-visible');
visible = true;
});
section.addEventListener('mouseout', function (e) {
var row = e.target.closest('tbody tr');
if (!row) return;
// only hide when leaving the row entirely (not moving to a child)
if (e.relatedTarget && row.contains(e.relatedTarget)) return;
currentRow = null;
popover.classList.remove('is-visible');
visible = false;
});
// ── Follow the cursor ─────────────────────────────────────
document.addEventListener('mousemove', function (e) {
if (!visible) return;
var x = e.clientX + 18;
var y = e.clientY + 18;
var pw = popover.offsetWidth;
var ph = popover.offsetHeight;
if (x + pw > window.innerWidth) x = e.clientX - pw - 8;
if (y + ph > window.innerHeight) y = e.clientY - ph - 8;
popover.style.left = x + 'px';
popover.style.top = y + 'px';
});
});

57
js/messageLabo.js Normal file
View File

@@ -0,0 +1,57 @@
document.addEventListener('DOMContentLoaded', function () {
var messageList = document.querySelector('.messages-list');
var agendaContent = document.querySelector('.agenda-content');
var sectionTitle = document.querySelector('.message-du-labo .section-title');
var buttonMessages = document.querySelector('.button-messages');
if (!messageList || !agendaContent) return;
var items = Array.from(messageList.querySelectorAll('.message-item'));
function sync() {
items.forEach(function (item) {
item.style.display = '';
var content = item.querySelector('.message-content');
if (content) { content.style.maxHeight = ''; content.classList.remove('is-overflowing'); }
});
if (window.innerWidth < 768) {
// Mobile : afficher uniquement le premier message
items.forEach(function (item, i) { item.style.display = i === 0 ? '' : 'none'; });
return;
}
var budget = agendaContent.offsetHeight
- (sectionTitle ? sectionTitle.offsetHeight : 0)
- (buttonMessages ? buttonMessages.offsetHeight : 0);
var used = 0;
for (var i = 0; i < items.length; i++) {
var item = items[i];
var itemHeight = item.offsetHeight;
if (used + itemHeight <= budget) {
used += itemHeight;
} else {
var remaining = budget - used;
var content = item.querySelector('.message-content');
if (content && remaining > 100) {
var contentBudget = remaining - (itemHeight - content.offsetHeight);
if (contentBudget > 60) {
content.style.maxHeight = contentBudget + 'px';
content.classList.add('is-overflowing');
} else {
item.style.display = 'none';
}
} else {
item.style.display = 'none';
}
for (var j = i + 1; j < items.length; j++) {
items[j].style.display = 'none';
}
break;
}
}
}
sync();
window.addEventListener('resize', sync);
});

32
js/navAxesToggle.js Normal file
View File

@@ -0,0 +1,32 @@
document.addEventListener('DOMContentLoaded', function () {
var item = document.querySelector('.nav-axes-item');
if (!item) return;
var trigger = item.querySelector('.nav-axes-trigger');
var mainMenu = document.querySelector('.main-menu');
trigger.addEventListener('click', function (e) {
e.stopPropagation();
var isOpen = item.classList.toggle('is-open');
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
});
// Close when clicking outside
document.addEventListener('click', function (e) {
if (!item.contains(e.target)) {
item.classList.remove('is-open');
trigger.setAttribute('aria-expanded', 'false');
}
});
// Reset when main menu closes
if (mainMenu) {
var observer = new MutationObserver(function () {
if (!mainMenu.classList.contains('active')) {
item.classList.remove('is-open');
trigger.setAttribute('aria-expanded', 'false');
}
});
observer.observe(mainMenu, { attributes: true, attributeFilter: ['class'] });
}
});

133
js/overlay.js Normal file
View File

@@ -0,0 +1,133 @@
document.addEventListener('DOMContentLoaded', function() {
const header = document.querySelector('header');
const body = document.querySelector('body');
const menuToggle = document.querySelector('.menu-toggle');
const mainMenu = document.querySelector('.main-menu');
const menuOverlay = document.querySelector('.overlay');
const menuIcon = document.querySelector('.menu-toggle i');
const wpAdminBar = document.querySelector('#wpadminbar');
const stickyHeaderMobile = document.querySelector('.header-right');
const searchButton = document.querySelector('.search-button');
const searchPanel = document.querySelector('.search-panel');
const searchIcon = document.querySelector('.search-button i');
const searchInput = document.querySelector('.search-panel__input');
const breakpointTablet = 768;
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
// Compute the pixel offset at which panels should appear (just below the header)
function getHeaderBottom() {
const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0;
if (window.innerWidth < breakpointTablet) {
if (window.scrollY > header.getBoundingClientRect().bottom) {
return stickyHeaderMobile.getBoundingClientRect().bottom + window.scrollY;
}
return header.getBoundingClientRect().bottom + window.scrollY;
}
return header.offsetHeight + adminBarHeight;
}
function updateOverlay() {
const anyOpen = mainMenu.classList.contains('active') || searchPanel.classList.contains('active');
menuOverlay.classList.toggle('active', anyOpen);
if (anyOpen) {
body.style.overflow = 'hidden';
} else {
body.style.removeProperty('overflow');
}
}
// --- Menu ---
function openMenu() {
mainMenu.scrollTo(0, 0);
if (window.innerWidth < breakpointTablet) {
const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0;
if (window.scrollY > header.getBoundingClientRect().bottom) {
mainMenu.style.height = `${window.innerHeight - adminBarHeight - stickyHeaderMobile.offsetHeight}px`;
} else {
mainMenu.style.height = `${window.innerHeight - header.getBoundingClientRect().bottom}px`;
}
} else {
mainMenu.style.removeProperty('height');
}
mainMenu.style.top = `${getHeaderBottom()}px`;
mainMenu.classList.add('active');
menuIcon.classList.remove('iconoir-menu');
menuIcon.classList.add('iconoir-xmark');
}
function closeMenu() {
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
mainMenu.classList.remove('active');
menuIcon.classList.remove('iconoir-xmark');
menuIcon.classList.add('iconoir-menu');
}
function toggleMenu() {
if (searchPanel.classList.contains('active')) closeSearch();
if (mainMenu.classList.contains('active')) {
closeMenu();
} else {
openMenu();
}
updateOverlay();
}
// --- Search ---
function openSearch() {
searchPanel.style.top = `${getHeaderBottom()}px`;
searchPanel.classList.add('active');
searchIcon.classList.remove('iconoir-search');
searchIcon.classList.add('iconoir-xmark');
setTimeout(function() { if (searchInput) searchInput.focus(); }, 400);
}
function closeSearch() {
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
searchPanel.classList.remove('active');
searchIcon.classList.remove('iconoir-xmark');
searchIcon.classList.add('iconoir-search');
}
function toggleSearch() {
if (mainMenu.classList.contains('active')) closeMenu();
if (searchPanel.classList.contains('active')) {
closeSearch();
} else {
openSearch();
}
updateOverlay();
}
// --- Event listeners ---
menuToggle.addEventListener('click', toggleMenu);
searchButton.addEventListener('click', toggleSearch);
menuOverlay.addEventListener('click', function() {
if (mainMenu.classList.contains('active')) closeMenu();
if (searchPanel.classList.contains('active')) closeSearch();
updateOverlay();
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
if (mainMenu.classList.contains('active')) closeMenu();
if (searchPanel.classList.contains('active')) closeSearch();
updateOverlay();
}
});
let resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
if (mainMenu.classList.contains('active')) closeMenu();
if (searchPanel.classList.contains('active')) closeSearch();
updateOverlay();
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
}, 150);
});
});

24
js/quickLinks.js Normal file
View File

@@ -0,0 +1,24 @@
document.addEventListener('DOMContentLoaded', function () {
var quickLinks = document.querySelector('.quick-links');
if (!quickLinks) return;
// Last section: keyword cloud if present, otherwise last swiper section
var lastSection = document.querySelector('.keyword-cloud');
if (!lastSection) {
var swiperSections = document.querySelectorAll('.swiper-section');
lastSection = swiperSections[swiperSections.length - 1] || null;
}
if (!lastSection) return;
var initialTop = quickLinks.getBoundingClientRect().top + window.scrollY;
var quickLinksHeight = quickLinks.offsetHeight;
window.addEventListener('scroll', function () {
var sectionBottom = lastSection.getBoundingClientRect().bottom;
if (initialTop - sectionBottom > 0) {
quickLinks.style.top = (initialTop - (initialTop - sectionBottom) - quickLinksHeight) + 'px';
} else {
quickLinks.style.top = initialTop + 'px';
}
});
});

39
js/seanceToggle.js Normal file
View File

@@ -0,0 +1,39 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-seance-toggle]').forEach(function (header) {
header.addEventListener('click', function (e) {
// Don't toggle when clicking a link
if (e.target.closest('a')) return;
var item = header.closest('.seance-item, .membres-item, .author-posts-item, .labo-dropdown-item');
var content = item.querySelector('.seance-content, .membres-content, .author-posts-content, .labo-dropdown-content');
var isOpen = item.classList.contains('is-open');
if (isOpen) {
content.style.display = 'none';
item.classList.remove('is-open');
} else {
content.style.display = '';
item.classList.add('is-open');
if (window.fitPostCardTitles) window.fitPostCardTitles();
}
});
});
// Auto-expand and scroll to a séance targeted by URL hash (#seance-{ID})
var hash = window.location.hash;
if (hash && hash.startsWith('#seance-')) {
var target = document.querySelector(hash + '.seance-item');
if (target) {
var content = target.querySelector('.seance-content');
if (content) {
content.style.display = '';
target.classList.add('is-open');
if (window.fitPostCardTitles) window.fitPostCardTitles();
setTimeout(function () {
var top = target.getBoundingClientRect().top + window.scrollY - 150;
window.scrollTo({ top: top, behavior: 'smooth' });
}, 150);
}
}
}
});

91
js/stickyHeader.js Normal file
View File

@@ -0,0 +1,91 @@
document.addEventListener('DOMContentLoaded', function() {
const header = document.querySelector('header');
const wpAdminBar = document.querySelector('#wpadminbar');
const stickyHeaderMobile = document.querySelector('.header-right');
const relativeHeaderMobile = document.querySelector('.header-left');
const mainLogo = document.querySelector('.main-logo');
const description = document.querySelector('.description');
const burgerContainer = document.querySelector('.menu-toggle > div');
const menuIconContainer = document.querySelector('.menu-toggle > div > div');
const menuText = document.querySelector('.menu-toggle > div > p');
const breakpointTablet = 768;
function checkMobile() {
if (window.innerWidth < breakpointTablet) {
stickyHeaderMobile.style.top = wpAdminBar ? `${wpAdminBar.offsetHeight}px` : '0px';
} else {
stickyHeaderMobile.style.top = 'unset';
}
}
function resetStyles() {
header.style.removeProperty("height");
mainLogo.style.removeProperty("padding");
description.style.removeProperty("opacity");
burgerContainer.style.removeProperty("padding");
burgerContainer.style.removeProperty("justify-content");
menuIconContainer.style.removeProperty("font-size");
menuText.style.removeProperty("display");
}
window.addEventListener('scroll', () => {
const isScrolledTop = window.scrollY === 0;
if (window.innerWidth < breakpointTablet) {
// mobile
if (window.scrollY > header.getBoundingClientRect().bottom) {
// déployer petit logo à gauche
if(!stickyHeaderMobile.classList.contains('scrolled')) {
stickyHeaderMobile.classList.add('scrolled')
}
} else {
// rétracter petit logo à gauche
if(stickyHeaderMobile.classList.contains('scrolled')) {
stickyHeaderMobile.classList.remove('scrolled');
}
}
} else {
// desktop
header.style.height = isScrolledTop ? '12vh' : '6vh';
header.style.minHeight = isScrolledTop ? '100px' : 'unset';
mainLogo.style.padding = isScrolledTop ? '1.5rem 2rem' : '0.2rem 0.4rem';
description.style.opacity = isScrolledTop ? '1' : '0';
burgerContainer.style.padding = isScrolledTop ? '2rem' : '0.6rem 1rem';
burgerContainer.style.justifyContent = isScrolledTop ? 'space-between' : 'center';
menuIconContainer.style.fontSize = isScrolledTop ? '2rem' : '1.5rem';
menuText.style.display = isScrolledTop ? '' : 'none';
if (window.scrollY === 0) {
// agrandir le header
} else {
// diminuer le header
}
}
});
let resizeTimeout;
let previousWidth = window.innerWidth;
window.addEventListener('resize', () => {
let currentWidth = window.innerWidth;
if (currentWidth !== previousWidth) {
window.scrollTo(0, 0);
previousWidth = currentWidth;
}
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
resetStyles();
checkMobile();
}, 150);
});
checkMobile();
});