Source

consequences.js

// NAVIGATION ET ANIMATIONS INTERACTIVES
let sunHasDisappeared = false;

/**
 * Déplace la tête et les yeux en fonction de la position de la souris
 * @param {MouseEvent} e - L'événement de mouvement de souris
 * @constant head {HTMLElement} - L'élément de la tête
 * @constant eyeLeft {HTMLElement} - L'élément de l'œil gauche
 * @constant eyeRight {HTMLElement} - L'élément de l'œil droit
 * @constant centerX {number} - La position X du centre de la fenêtre7  
 * @constant centerY {number} - La position Y du centre de la fenêtre
 * @constant dx {number} - La différence entre la position de la souris et le centre de la fenêtre sur l'axe X
 * @constant dy {number} - La différence entre la position de la souris et le centre de la fenêtre sur l'axe Y
 * @description Déplace la tête et les yeux en fonction de la position de la souris
 * @returns {void}
 */
document.addEventListener('mousemove', (e) => {
    const head = document.getElementById('head');
    const eyeLeft = document.getElementById('eye-left');
    const eyeRight = document.getElementById('eye-right');
    const centerX = window.innerWidth / 2;
    const centerY = window.innerHeight / 2;
    const dx = (e.clientX - centerX) / 50;
    const dy = (e.clientY - centerY) / 50;
    head.style.transform = `translate(${dx}px, ${dy}px)`;
    eyeLeft.style.transform = `translate(${dx}px, ${dy}px)`;
    eyeRight.style.transform = `translate(${dx}px, ${dy}px)`;
});

/**
 * Anime le clignement des yeux toutes les 4 secondes
 * @constant leftEye {HTMLElement} - L'élément de l'œil gauche
 * @constant rightEye {HTMLElement} - L'élément de l'œil droit
 * @description Anime le clignement des yeux en ajoutant et en supprimant la classe 'blink'
 * @returns {void}
 */
setInterval(() => {
    const leftEye = document.getElementById('eye-left');
    const rightEye = document.getElementById('eye-right');
    leftEye.classList.add('blink');
    rightEye.classList.add('blink');
    setTimeout(() => {
        leftEye.classList.remove('blink');
        rightEye.classList.remove('blink');
    }, 200);
}, 4000);

/**
 * Gère le clic sur le soleil : fait disparaître le soleil, change la couleur du ciel,
 * masque la goutte, affiche un sourire et lance la neige et les messages climatiques
 * @constant sun {HTMLElement} - L'élément du soleil
 * @constant bubble {HTMLElement} - L'élément de la bulle de parole
 * @description Gère le clic sur le soleil
 * @returns {void}
 */
document.getElementById('sun').addEventListener('click', () => {
    const sun = document.getElementById('sun');
    sun.style.opacity = '0';
    setTimeout(() => sun.style.display = 'none', 800);
    document.body.style.setProperty('--sky-color', '#a2ddee');
    document.querySelector('.droplet-css').style.display = 'none';
    document.getElementById('mouth').classList.add('smile');
    const bubble = document.getElementById('speech-bubble');
    bubble.innerHTML = `
        <p>Ahhh… C'est bien mieux maintenant ❄️</p>
        <p>Est-ce que tu sais que...</p>
        <div id="messages" style="margin-top: 12px;"></div>
    `;
    startSnowfall();
    startClimateMessages();
    sunHasDisappeared = true;
});

/**
 * Lance l'effet de chute de flocons de neige sur la page
 * @constant containerClass {string} - La classe du conteneur de flocons de neige
 * @constant snowflakeChars {string[]} - Les caractères de flocons de neige
 * @constant colors {string[]} - Les couleurs des flocons de neige
 * @constant MAX_FLAKES {number} - Le nombre maximum de flocons de neige
 * @constant currentFlakes {number} - Le nombre actuel de flocons de neige
 * @description Crée un conteneur pour les flocons de neige et les anime
 * @returns {void}
 */
function startSnowfall() {
    const containerClass = "snowfall";
    let container = document.querySelector(`.${containerClass}`);

    if (!container) {
        container = document.createElement("div");
        container.className = containerClass;
        document.body.appendChild(container);
    }

    const snowflakeChars = ["❄", "❅", "❆", "✻", "✼"];
    const colors = ["white", "#cceeff", "#99ddff"];

    const MAX_FLAKES = 100;
    let currentFlakes = 0;

    /**
     * Retourne un nombre aléatoire entre min et max
     * @param {number} min
     * @param {number} max
     * @description Retourne un nombre aléatoire entre min et max
     * @returns {number}
     */
    function randomBetween(min, max) {
        return Math.random() * (max - min) + min;
    }

    /**
     * Crée un flocon de neige et l'ajoute au conteneur
     * @constant flake {HTMLElement} - L'élément du flocon de neige
     * @constant size {number} - La taille du flocon de neige
     * @description Crée un flocon de neige et l'ajoute au conteneur
     * @returns {void}
     */
    function createFlake() {
        if (currentFlakes >= MAX_FLAKES) return;

        const flake = document.createElement("span");
        flake.className = "snowflake";
        flake.textContent = snowflakeChars[Math.floor(Math.random() * snowflakeChars.length)];

        const size = randomBetween(12, 28);
        flake.style.left = `${randomBetween(0, 100)}vw`;
        flake.style.fontSize = `${size}px`;
        flake.style.opacity = randomBetween(0.5, 1).toFixed(2);
        flake.style.color = colors[Math.floor(Math.random() * colors.length)];

        flake.style.animation = `
            fall ${randomBetween(6, 12)}s linear infinite,
            sway ${randomBetween(2, 5)}s ease-in-out infinite alternate
        `;

        container.appendChild(flake);
        currentFlakes++;

        setTimeout(() => {
            flake.remove();
            currentFlakes--;
        }, 12000);
    }

    setInterval(createFlake, 200);
}

/**
 * Affiche des messages climatiques dans la bulle de parole, cycliquement
 * @constant messages {string[]} - Les messages climatiques
 * @const container {HTMLElement} - L'élément de la bulle de parole
 * @description Affiche des messages climatiques dans la bulle de parole
 * @returns {void}
 */
function startClimateMessages() {
    const messages = [
        "🌡️ L'Arctique se réchauffe 4x plus vite.",
        "🧊 75% de la glace de mer a disparu.",
        "🐻‍❄️ J'ai besoin de glace pour survivre.",
        "🚿 Une douche rapide = -60L d’eau !",
        "🌍 Chaque geste compte pour le climat."
    ];
    let current = 0;
    const container = document.getElementById('messages');
    setTimeout(() => {
        container.textContent = messages[current];
        setInterval(() => {
            current = (current + 1) % messages.length;
            container.textContent = messages[current];
        }, 8000);
    }, 5000);
}

/**
 * Affiche un cœur animé lorsque la tête est cliquée (si le soleil a disparu)
 * @constant wrap {HTMLElement} - L'élément de l'enveloppe
 * @constant head {HTMLElement} - L'élément de la tête
 * @constant heart {HTMLElement} - L'élément du cœur
 * @description Affiche un cœur animé lorsque la tête est cliquée
 * @returns {void}
 */
document.getElementById('head').addEventListener('click', () => {
    const wrap = document.querySelector('.wrap');
    const head = document.getElementById('head');

    if (sunHasDisappeared) {
        const heart = document.createElement('div');
        heart.className = 'heart-pop';
        heart.textContent = '❤️';
        wrap.appendChild(heart);

        head.classList.remove('happy-animate'); 
        void head.offsetWidth; 
        head.classList.add('happy-animate');

        setTimeout(() => heart.remove(), 1000);
    }
});

// === CAROUSEL 3D NAVIGATION ===
const carousel = document.querySelector('.carousel');
const items = document.querySelectorAll('.carousel-item');
let currentItem = 0;


/**
 * Met à jour la position du carrousel en fonction de l'élément courant
 * @constant offset {number} - L'offset de translation en pourcentage
 * @description Met à jour la position du carrousel
 * @returns {void}
 */
function updateCarousel() {
    const offset = -currentItem * 100;
    carousel.style.transform = `translateX(${offset}%)`;
}

/**
 * Gère le clic sur le bouton "suivant" du carrousel
 * @description Gère le clic sur le bouton "suivant"
 * @returns {void}
 */
document.querySelector('.carousel-btn.next').addEventListener('click', () => {
    currentItem = (currentItem + 1) % items.length;
    updateCarousel();
});

/**
 * Gère le clic sur le bouton "précédent" du carrousel
 * @description Gère le clic sur le bouton "précédent"
 * @returns {void}
 */
document.querySelector('.carousel-btn.prev').addEventListener('click', () => {
    currentItem = (currentItem - 1 + items.length) % items.length;
    updateCarousel();
});

// Redirige vers des liens externes lors du clic sur les cartes de catégories d'impacts

/**
 * Tableau des liens externes pour chaque catégorie d'impact
 * @constant impactLinks {string[]} - Liens vers des articles externes pour chaque catégorie d'impact
 * @description Liens vers des articles externes pour chaque catégorie d'impact
 * @type {string[]}
 */
const impactLinks = [
    // Fonte des glaces
    "https://www.lemonde.fr/planete/article/2025/02/19/les-glaciers-declinent-partout-autour-du-globe-perdant-l-equivalent-de-trois-piscines-olympiques-par-seconde_6554635_3244.html",

    // Canicules & Incendies
    "https://www.futura-sciences.com/planete/actualites/climatologie-2024-ete-annee-tous-extremes-118314/",

    // Catastrophes naturelles
    "https://www.futura-sciences.com/planete/actualites/rechauffement-climatique-570-000-morts-voici-pires-catastrophes-meteo-depuis-20-ans-europe-surrepresentee-117268/",

    // Santé humaine
    "https://www.who.int/fr/news-room/fact-sheets/detail/climate-change-and-health",

    // Agriculture
    "https://www.actu-environnement.com/ae/news/adaptation-climat-agriculture-filieres-exploitations-cultures-eau-45679.php4",

    // Biodiversité
    "https://www.wwf.fr/vous-informer/actualites/rapport-planete-vivante-2024-les-populations-de-vertebres-sauvages-ont-decline-de-73-depuis-1970",
];

/**
 * Ajoute un événement de clic sur chaque carte d'impact pour ouvrir le lien correspondant
 * @description Ajoute un événement de clic sur chaque carte d'impact
 * @returns {void}
 */
document.querySelectorAll('.rubriques .card').forEach((card, idx) => {
    card.style.cursor = "pointer";
    card.addEventListener('click', () => {
        window.open(impactLinks[idx], '_blank');
    });
});

/**
 * Tableau des liens externes pour les articles d'actualité
 * @constant newsLinks {string[]} - Liens vers des articles d'actualité externes
 * @description Liens vers des articles d'actualité externes
 * @type {string[]}
 */
const newsLinks = [
    // Jakarta menacée par la montée des eaux
    "https://www.lemonde.fr/planete/article/2025/03/06/a-djakarta-les-inondations-rappellent-la-menace-de-submersion-et-les-risques-de-la-sururbanisation_6576817_3244.html",

    // Été 2024 : le plus chaud jamais enregistré
    "https://www.lemonde.fr/planete/article/2024/09/06/l-ete-2024-est-le-plus-chaud-jamais-enregistre-dans-le-monde_6305244_3244.html"
];

/**
 * Ajoute un événement de clic sur chaque article d'actualité pour ouvrir le lien correspondant
 * @description Ajoute un événement de clic sur chaque article d'actualité
 * @returns {void}
 */
document.querySelectorAll('.news.block article').forEach((article, idx) => {
    article.style.cursor = "pointer";
    article.addEventListener('click', () => {
        window.open(newsLinks[idx], '_blank');
    });
});

/**
 * Tableau des liens externes pour les boutons du slider
 * @constant sliderLinks {string[]} - Liens vers des articles externes pour chaque catégorie d'impact
 * @description Liens vers des articles externes pour chaque catégorie d'impact
 * @type {string[]}
 */
const sliderLinks = [
    // Sécheresse extrême
    "https://rhone-mediterranee.eaufrance.fr/aout-2024-la-secheresse-se-declenche-sur-le-bassin",

    // Tempêtes violentes
    "https://meteofrance.com/actualites-et-dossiers/comprendre-la-meteo/les-tempetes-remarquables-en-france",

    // Écosystèmes détruits
    "https://www.greenpeace.fr/deforestation/"
];

/**
 * Ajoute un événement de clic sur chaque bouton du slider pour ouvrir le lien correspondant
 * @description Ajoute un événement de clic sur chaque bouton du slider
 * @returns {void}
 */
document.querySelectorAll('.carousel .carousel-item button').forEach((btn, idx) => {
    btn.addEventListener('click', () => {
        window.open(sliderLinks[idx], '_blank');
    });
});