first commit
Some checks are pending
Deploy / Deploy to Production (push) Waiting to run

This commit is contained in:
isUnknown 2025-12-10 15:12:06 +01:00
commit a3620a1f5f
1042 changed files with 226722 additions and 0 deletions

154
assets/js/donation.js Normal file
View file

@ -0,0 +1,154 @@
(function () {
'use strict';
const DONORBOX_CAMPAIGN_URL = 'https://donorbox.org/soutenir-index-2025-don';
const TAX_REDUCTION_RATE = 0.66;
const AMOUNTS = {
oneOff: [200, 100, 50, 20],
monthly: [50, 20, 10, 5],
};
const TRANSLATIONS = {
fr: {
afterTax: 'Soit {amount} € après impôts',
perMonth: '€/mois',
withTaxReduction: 'Avec 66 % de déduction fiscale',
},
en: {
afterTax: 'That is {amount} € after tax',
perMonth: '€/month',
withTaxReduction: 'With 66 % tax deduction',
},
};
function getLanguage() {
return document.documentElement.lang || 'fr';
}
function translate(key, params = {}) {
const lang = getLanguage();
let text = TRANSLATIONS[lang][key] || TRANSLATIONS['fr'][key];
Object.keys(params).forEach((param) => {
text = text.replace(`{${param}}`, params[param]);
});
return text;
}
function calculateAfterTax(amount) {
const result = amount * (1 - TAX_REDUCTION_RATE);
const rounded = Math.round(result * 100) / 100;
return rounded % 1 === 0 ? Math.round(rounded) : rounded.toFixed(2);
}
function formatAmount(amount) {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '\u202F');
}
function generateDonorboxUrl(amount, isMonthly) {
const params = new URLSearchParams();
params.append('default_interval', isMonthly ? 'm' : 'o');
if (amount) {
params.append('amount', amount);
}
// Récupérer les paramètres UTM de l'URL actuelle
const currentUrlParams = new URLSearchParams(window.location.search);
const utmParams = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content',
];
utmParams.forEach((utmParam) => {
const value = currentUrlParams.get(utmParam);
if (value) {
params.append(utmParam, value);
}
});
return `${DONORBOX_CAMPAIGN_URL}?${params.toString()}`;
}
function initTabs() {
const tabButtons = document.querySelectorAll('.nav--tabs__btn');
const containers = document.querySelectorAll('.btn--donation__container');
tabButtons.forEach((button, index) => {
button.addEventListener('click', () => {
tabButtons.forEach((btn) => btn.classList.remove('is-selected'));
containers.forEach((container) =>
container.classList.remove('is-selected')
);
button.classList.add('is-selected');
containers[index].classList.add('is-selected');
});
});
}
function initDonationButtons() {
const oneOffContainer = document.querySelector(
'[data-donnation="one-off"]'
);
const oneOffButtons = oneOffContainer.querySelectorAll('.btn--donation');
oneOffButtons.forEach((button, index) => {
if (index < AMOUNTS.oneOff.length) {
const amount = AMOUNTS.oneOff[index];
const afterTax = calculateAfterTax(amount);
button.innerHTML = `
<p class="bold">${formatAmount(amount)}&#8239;</p>
<p class="small">${translate('afterTax', {
amount: formatAmount(afterTax),
})}</p>
`;
button.addEventListener('click', () => {
window.open(generateDonorboxUrl(amount, false), '_blank');
});
} else {
button.addEventListener('click', () => {
window.open(generateDonorboxUrl(null, false), '_blank');
});
}
});
const monthlyContainer = document.querySelector(
'[data-donnation="monthly"]'
);
const monthlyButtons = monthlyContainer.querySelectorAll('.btn--donation');
monthlyButtons.forEach((button, index) => {
if (index < AMOUNTS.monthly.length) {
const amount = AMOUNTS.monthly[index];
const afterTax = calculateAfterTax(amount);
button.innerHTML = `
<p class="bold">${formatAmount(amount)}&#8239;${translate(
'perMonth'
)}</p>
<p class="small">${translate('afterTax', {
amount: formatAmount(afterTax),
})}</p>
`;
button.addEventListener('click', () => {
window.open(generateDonorboxUrl(amount, true), '_blank');
});
} else {
button.addEventListener('click', () => {
window.open(generateDonorboxUrl(null, true), '_blank');
});
}
});
}
document.addEventListener('DOMContentLoaded', () => {
initTabs();
initDonationButtons();
});
})();

105
assets/js/donorbox-gauge.js Normal file
View file

@ -0,0 +1,105 @@
const DONORBOX_CONFIG = {
proxyUrl: '/api/donorbox-proxy.php',
};
function formatCurrency(amount) {
const rounded = Math.round(amount * 100) / 100;
const hasDecimals = rounded % 1 !== 0;
const formatted = rounded.toLocaleString('fr-FR', {
minimumFractionDigits: 0,
maximumFractionDigits: hasDecimals ? 2 : 0,
});
return formatted.replace(/[\s\u00A0\u202F]/g, '\u202F') + '\u202F€';
}
async function fetchDonorboxData() {
try {
const response = await fetch(DONORBOX_CONFIG.proxyUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
return data;
} catch (error) {
console.error('Error fetching Donorbox data:', error);
throw error;
}
}
function updateGaugeDisplay(campaignData) {
const collected = campaignData.total_raised || 0;
const goal = campaignData.goal_amt || 50000;
const percentage = Math.min((collected / goal) * 100, 100);
const gaugeElement = document.getElementById('gauge');
if (gaugeElement) {
gaugeElement.style.setProperty(
'--pourcent',
`${percentage > 2.5 ? percentage : 2.5}%`
);
}
const collectedElement = document.querySelector(
'#gauge--infos__donateurs .value'
);
if (collectedElement) {
collectedElement.textContent = formatCurrency(collected);
}
const goalElement = document.querySelector('#gauge--infos__objectif .value');
if (goalElement) {
goalElement.textContent = formatCurrency(goal);
}
const donorsCount = campaignData.donations_count || 0;
const donorsElement = document.querySelector('#gauge--infos__donors .value');
if (donorsElement) {
donorsElement.textContent = donorsCount;
}
console.log('Gauge updated:', {
collected: formatCurrency(collected),
goal: formatCurrency(goal),
percentage: `${percentage.toFixed(1)}%`,
});
}
async function initDonorboxGauge() {
try {
console.log('Fetching Donorbox data...');
const campaignData = await fetchDonorboxData();
updateGaugeDisplay(campaignData);
} catch (error) {
console.error('Failed to update gauge:', error);
}
}
function setupAutoRefresh(intervalMinutes = 5) {
const intervalMs = intervalMinutes * 60 * 1000;
setInterval(initDonorboxGauge, intervalMs);
console.log(`Auto-refresh configured: every ${intervalMinutes} minutes`);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
initDonorboxGauge();
setupAutoRefresh(5);
});
} else {
initDonorboxGauge();
setupAutoRefresh(5);
}

View file

@ -0,0 +1,89 @@
(function () {
'use strict';
const PROXY_URL = '/api/brevo-newsletter-proxy.php';
async function subscribeToNewsletter(email, attributes = {}) {
const response = await fetch(PROXY_URL, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, attributes }),
});
const data = await response.json();
if (!response.ok) {
const error = new Error(
data.user_message || data.message || 'Subscription error'
);
error.code = data.error;
error.data = data;
throw error;
}
return data;
}
function showMessage(form, text, isError = false) {
const oldMessages = form.parentNode.querySelectorAll('.newsletter-message');
oldMessages.forEach((msg) => msg.remove());
const message = document.createElement('p');
message.className = 'newsletter-message';
message.textContent = text;
message.style.marginTop = '0.5rem';
message.style.fontSize = '0.9rem';
message.style.color = isError
? 'var(--color-error, #ef4444)'
: 'var(--color-success, #22c55e)';
form.parentNode.insertBefore(message, form.nextSibling);
if (!isError) {
setTimeout(() => message.remove(), 5000);
}
}
async function handleFormSubmit(event) {
event.preventDefault();
const form = event.target;
const emailInput = form.querySelector('input[type="email"]');
const submitButton = form.querySelector('button[type="submit"]');
if (!emailInput || !emailInput.value) {
const message = location.pathname.includes('/en/')
? 'Please enter a valid email address.'
: 'Veuillez entrer une adresse email valide.';
showMessage(form, messsage, true);
return;
}
const email = emailInput.value.trim();
submitButton.disabled = true;
try {
await subscribeToNewsletter(email);
const message = location.pathname.includes('/en/')
? 'Thank you! Your subscription has been confirmed.'
: 'Merci, votre inscription est confirmée !';
showMessage(form, message, false);
form.reset();
} catch (error) {
const isAlreadySubscribed = error.code === 'email_already_exists';
showMessage(form, error.message, !isAlreadySubscribed);
} finally {
submitButton.disabled = false;
}
}
function initNewsletterForms() {
const forms = document.querySelectorAll('.form__newsletter');
forms.forEach((form) => form.addEventListener('submit', handleFormSubmit));
}
document.addEventListener('DOMContentLoaded', initNewsletterForms);
})();

90
assets/js/onload.js Normal file
View file

@ -0,0 +1,90 @@
window.onload = function () {
headerShrink();
initCart();
toggleDonationButton();
videos();
};
window.onscroll = function () {
headerShrink();
toggleDonationButton();
};
function initCart() {
const items = document.querySelectorAll('.store__product .link-block');
const header = document.querySelector('#site-header');
}
function headerShrink() {
const header = document.getElementById('site-header');
const scrollPosition = window.scrollY || document.documentElement.scrollTop;
if (scrollPosition > 100) {
header.classList.add('is-shrinked');
} else {
header.classList.remove('is-shrinked');
}
}
function toggleDonationButton() {
const btn = document.getElementById('btn--don__mobile');
const section = document.getElementById('section__donation');
const footer = document.getElementById('site-footer');
if (!btn || !section || !footer) return; // sécurité
const scrollTop = window.scrollY || window.pageYOffset;
const triggerPoint = section.offsetTop + section.offsetHeight / 2;
// 1⃣ Gestion de la visibilité du bouton
if (scrollTop >= triggerPoint) {
btn.classList.add('is-visible');
} else {
btn.classList.remove('is-visible');
}
// 2⃣ Gestion du stickiness quand le footer apparaît
const footerRect = footer.getBoundingClientRect();
const windowHeight = window.innerHeight;
if (footerRect.top < windowHeight) {
// Le footer est visible dans la fenêtre
btn.classList.add('is-sticky');
} else {
btn.classList.remove('is-sticky');
}
}
function videos(){
console.log("video");
let section = document.getElementById("section__video");
console.log(section);
let videoslinks = document.querySelectorAll(".videos__li");
videoslinks.forEach(function (video, index) {
video.addEventListener("click", (event) => {
let data = video.getAttribute('data-video');
var div = document.createElement('section');
div.id = "video__fullscreen";
div.innerHTML = '<button id="video__close"></button>\
<iframe\
src="' + data + '"\
style="aspect-ratio: 9/16; width: 100%; border: 0"\
allow="autoplay; fullscreen; picture-in-picture"\
allowfullscreen ></iframe>';
document.body.appendChild(div);
document.body.classList.add("is-fullscreen");
let close = document.querySelector("#video__close");
close.addEventListener("click", (event) => {
div.remove();
document.body.classList.remove("is-fullscreen");
});
});
});
}

60
assets/js/product-size.js Normal file
View file

@ -0,0 +1,60 @@
/**
* Gestion de la sélection des options produit
* Met à jour les attributs Snipcart et gère les classes CSS
*/
(function() {
'use strict';
/**
* Initialise la gestion des options
*/
function initOptionSelector() {
const optionsContainer = document.querySelector('.product-options');
const addToCartButton = document.querySelector('.snipcart-add-item');
if (!addToCartButton) {
return;
}
// Si pas d'options, le bouton est déjà actif
if (!optionsContainer) {
return;
}
const radios = optionsContainer.querySelectorAll('input[type="radio"]');
// Réinitialiser toutes les options (important pour le cache navigateur)
radios.forEach(radio => {
radio.checked = false;
});
// Retirer la classe is-selected de tous les li
const allLi = optionsContainer.querySelectorAll('li');
allLi.forEach(li => li.classList.remove('is-selected'));
// S'assurer que le bouton est désactivé au départ
addToCartButton.setAttribute('disabled', 'disabled');
// Écouter les changements de sélection
radios.forEach(radio => {
radio.addEventListener('change', function() {
// Mettre à jour l'attribut Snipcart
addToCartButton.setAttribute('data-item-custom1-value', this.value);
// Activer le bouton
addToCartButton.removeAttribute('disabled');
// Gérer la classe is-selected sur les li parents
allLi.forEach(li => li.classList.remove('is-selected'));
this.closest('li').classList.add('is-selected');
});
});
}
/**
* Initialisation au chargement de la page
*/
document.addEventListener('DOMContentLoaded', initOptionSelector);
})();

82
assets/js/snipcart.js Normal file
View file

@ -0,0 +1,82 @@
window.SnipcartSettings = {
publicApiKey:
'NGU4ODQ3MjAtY2MzMC00MWEyLWI2YTMtNjBmNGYzMTBlOTZkNjM4OTY1NDY4OTE5MTQyMTI3',
loadStrategy: 'on-user-interaction',
};
(() => {
var c, d;
(d = (c = window.SnipcartSettings).version) != null || (c.version = '3.0');
var s, S;
(S = (s = window.SnipcartSettings).timeoutDuration) != null ||
(s.timeoutDuration = 2750);
var l, p;
(p = (l = window.SnipcartSettings).domain) != null ||
(l.domain = 'cdn.snipcart.com');
var w, u;
(u = (w = window.SnipcartSettings).protocol) != null ||
(w.protocol = 'https');
var f =
window.SnipcartSettings.version.includes('v3.0.0-ci') ||
(window.SnipcartSettings.version != '3.0' &&
window.SnipcartSettings.version.localeCompare('3.4.0', void 0, {
numeric: !0,
sensitivity: 'base',
}) === -1),
m = ['focus', 'mouseover', 'touchmove', 'scroll', 'keydown'];
window.LoadSnipcart = o;
document.readyState === 'loading'
? document.addEventListener('DOMContentLoaded', r)
: r();
function r() {
window.SnipcartSettings.loadStrategy
? window.SnipcartSettings.loadStrategy === 'on-user-interaction' &&
(m.forEach((t) => document.addEventListener(t, o)),
setTimeout(o, window.SnipcartSettings.timeoutDuration))
: o();
}
var a = !1;
function o() {
if (a) return;
a = !0;
let t = document.getElementsByTagName('head')[0],
e = document.querySelector('#snipcart'),
i = document.querySelector(
`src[src^="${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}"][src$="snipcart.js"]`
),
n = document.querySelector(
`link[href^="${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}"][href$="snipcart.css"]`
);
e ||
((e = document.createElement('div')),
(e.id = 'snipcart'),
e.setAttribute('hidden', 'true'),
document.body.appendChild(e)),
v(e),
i ||
((i = document.createElement('script')),
(i.src = `${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}/themes/v${window.SnipcartSettings.version}/default/snipcart.js`),
(i.async = !0),
t.appendChild(i)),
n ||
((n = document.createElement('link')),
(n.rel = 'stylesheet'),
(n.type = 'text/css'),
(n.href = `${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}/themes/v${window.SnipcartSettings.version}/default/snipcart.css`),
t.prepend(n)),
m.forEach((g) => document.removeEventListener(g, o));
}
function v(t) {
!f ||
((t.dataset.apiKey = window.SnipcartSettings.publicApiKey),
window.SnipcartSettings.addProductBehavior &&
(t.dataset.configAddProductBehavior =
window.SnipcartSettings.addProductBehavior),
window.SnipcartSettings.modalStyle &&
(t.dataset.configModalStyle = window.SnipcartSettings.modalStyle),
window.SnipcartSettings.currency &&
(t.dataset.currency = window.SnipcartSettings.currency),
window.SnipcartSettings.templatesUrl &&
(t.dataset.templatesUrl = window.SnipcartSettings.templatesUrl));
}
})();

View file

@ -0,0 +1,33 @@
// How to use
// <header id="header" w3-include-html="components/header.html"></header>
function includeHTML() {
var z, i, elmnt, file, xhttp;
/* Loop through a collection of all HTML elements: */
z = document.getElementsByTagName("*");
for (i = 0; i < z.length; i++) {
elmnt = z[i];
/*search for elements with a certain atrribute:*/
file = elmnt.getAttribute("w3-include-html");
if (file) {
/* Make an HTTP request using the attribute value as the file name: */
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {elmnt.innerHTML = this.responseText;}
if (this.status == 404) {elmnt.innerHTML = "Page not found.";}
/* Remove the attribute, and call this function once more: */
elmnt.removeAttribute("w3-include-html");
includeHTML();
}
}
xhttp.open("GET", file, true);
xhttp.send();
/* Exit the function: */
return;
}
}
}