<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Outils mémoire</title>
	<atom:link href="https://outilsmemoire.fr/feed/" rel="self" type="application/rss+xml" />
	<link>https://outilsmemoire.fr</link>
	<description>Techniques de mémorisation</description>
	<lastBuildDate>Tue, 27 Jan 2026 21:28:32 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://outilsmemoire.fr/wp-content/uploads/2026/01/cropped-Image-32x32.png</url>
	<title>Outils mémoire</title>
	<link>https://outilsmemoire.fr</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Diseur de cartes</title>
		<link>https://outilsmemoire.fr/2026/01/27/diseur-de-cartes/</link>
					<comments>https://outilsmemoire.fr/2026/01/27/diseur-de-cartes/#respond</comments>
		
		<dc:creator><![CDATA[pcamino]]></dc:creator>
		<pubDate>Tue, 27 Jan 2026 20:46:03 +0000</pubDate>
				<category><![CDATA[Outil]]></category>
		<guid isPermaLink="false">https://outilsmemoire.fr/?p=51</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[<div class="et_pb_section_0 et_pb_section et_section_regular et_flex_section">
<div class="et_pb_row_0 et_pb_row et_flex_row">
<div class="et_pb_column_0 et_pb_column et-last-child et_flex_column et_pb_css_mix_blend_mode_passthrough et_flex_column_24_24 et_flex_column_24_24_tablet et_flex_column_24_24_phone">
<div class="et_pb_code_0 et_pb_code et_pb_module"><div class="et_pb_code_inner"><!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tirage de Cartes avec Synthèse Vocale</title>
    <!-- Tailwind CSS CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- Inter Font -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&#038;display=swap" rel="stylesheet">
    <style>
        /* Style for Inter font */
        body {
            font-family: 'Inter', sans-serif;
        }
        /* Style for the playing card */
        .playing-card {
            width: 150px;
            height: 210px;
            border-radius: 12px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            padding: 1rem;
            position: relative;
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
            background-color: white;
            border: 1px solid #e5e7eb;
            transition: all 0.3s ease;
        }
        .playing-card.red {
            color: #ef4444; /* red-500 */
        }
        .playing-card.black {
            color: #111827; /* gray-900 */
        }
        .card-value {
            font-size: 2rem;
            font-weight: bold;
            line-height: 1;
        }
        .card-suit {
            font-size: 1.5rem;
        }
        .card-suit-center {
            font-size: 4rem;
            text-align: center;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
        /* Style for inputs */
        input[type="number"] {
            -moz-appearance: textfield;
        }
        input[type="number"]::-webkit-outer-spin-button,
        input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }
    </style>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen p-4">

    <!-- This new wrapper is the key to perfect centering -->
    <div class="w-full flex flex-col items-center gap-8">
        <div class="w-full max-w-lg mx-auto bg-white p-8 rounded-xl shadow-lg">
            <h1 class="text-3xl font-bold text-center text-gray-800 mb-6">Tirage de Cartes Automatisé</h1>

            <!-- Configuration area -->
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
                <div>
                    <label for="num-cards" class="block text-sm font-medium text-gray-700 mb-1">Nombre de cartes à piocher</label>
                    <input type="number" id="num-cards" value="5" min="1" max="52" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
                </div>
                <div>
                    <label for="interval" class="block text-sm font-medium text-gray-700 mb-1">Intervalle (secondes)</label>
                    <input type="number" id="interval" value="2" min="1" max="10" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
                </div>
            </div>

            <!-- Display area - only drawn card -->
            <div class="flex justify-center items-center mb-6 h-[220px]">
                <div id="drawn-card-container" class="playing-card bg-gray-200 flex items-center justify-center text-center">
                       <span class="text-gray-500">Prêt pour le tirage</span>
                </div>
            </div>
            
            <!-- Information -->
            <div id="status-display" class="text-center text-gray-600 mb-2 font-medium h-6" aria-live="polite"></div>
            <div id="card-count" class="text-center text-gray-600 mb-6 font-medium">
                Cartes restantes : 52
            </div>

            <!-- Action buttons -->
            <div class="flex justify-center space-x-4">
                <button id="start-button" class="px-6 py-3 bg-green-600 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-opacity-75 transition-transform transform hover:scale-105 disabled:bg-gray-400 disabled:cursor-not-allowed">
                    Démarrer
                </button>
                <button id="stop-button" class="px-6 py-3 bg-red-600 text-white font-semibold rounded-lg shadow-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-75 transition-transform transform hover:scale-105 disabled:bg-gray-400 disabled:cursor-not-allowed" disabled>
                    Arrêter
                </button>
                <button id="shuffle-button" class="px-6 py-3 bg-gray-600 text-white font-semibold rounded-lg shadow-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-opacity-75 transition-transform transform hover:scale-105">
                    Mélanger
                </button>
            </div>
        </div>

        <!-- Container for the results table (now inside the wrapper) -->
        <div id="results-container" class="w-full max-w-lg mx-auto"></div>
    </div>


    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // --- DOM Elements ---
            const startButton = document.getElementById('start-button');
            const stopButton = document.getElementById('stop-button');
            const shuffleButton = document.getElementById('shuffle-button');
            const cardCountElement = document.getElementById('card-count');
            const drawnCardContainer = document.getElementById('drawn-card-container');
            const numCardsInput = document.getElementById('num-cards');
            const intervalInput = document.getElementById('interval');
            const statusDisplay = document.getElementById('status-display');
            const resultsContainer = document.getElementById('results-container');

            // --- Game Data ---
            const suits = { '♥': 'Cœur', '♦': 'Carreau', '♠': 'Pique', '♣': 'Trèfle' };
            const values = { 'A': 'As', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '10': '10', 'J': 'Valet', 'Q': 'Dame', 'K': 'Roi' };
            let deck = [];
            let drawnCardsHistory = []; 
            let drawTimeout;
            let isDrawing = false;

            // --- Game Functions ---

            /** Creates a standard 52-card deck. */
            const createDeck = () => {
                deck = [];
                for (const suitSymbol in suits) {
                    for (const valueSymbol in values) {
                        const cardName = `${values[valueSymbol]} de ${suits[suitSymbol]}`;
                        deck.push({ suit: suitSymbol, value: valueSymbol, name: cardName });
                    }
                }
            };

            /** Shuffles the deck using the Fisher-Yates algorithm. */
            const shuffleDeck = () => {
                for (let i = deck.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [deck[i], deck[j]] = [deck[j], deck[i]];
                }
            };

            /** Speaks text using the browser's speech synthesis. */
            const speak = (text) => {
                if ('speechSynthesis' in window) {
                    const utterance = new SpeechSynthesisUtterance(text);
                    utterance.lang = 'fr-FR';
                    utterance.rate = 0.9;
                    window.speechSynthesis.speak(utterance);
                } else {
                    console.warn("La synthèse vocale n'est pas supportée par ce navigateur.");
                }
            };

            /** Displays the drawn card in the UI. */
            const updateDrawnCardUI = (card) => {
                drawnCardContainer.innerHTML = '';
                if (card) {
                    const isRed = card.suit === '♥' || card.suit === '♦';
                    const colorClass = isRed ? 'red' : 'black';
                    drawnCardContainer.className = `playing-card ${colorClass}`;
                    drawnCardContainer.innerHTML = `
                        <div class="self-start"><div class="card-value">${card.value}</div><div class="card-suit">${card.suit}</div></div>
                        <div class="card-suit-center">${card.suit}</div>
                        <div class="self-end rotate-180"><div class="card-value">${card.value}</div><div class="card-suit">${card.suit}</div></div>
                    `;
                } else {
                    drawnCardContainer.className = 'playing-card bg-gray-200 flex items-center justify-center text-center';
                    drawnCardContainer.innerHTML = '<span class="text-gray-500">Jeu vide !</span>';
                }
            };
            
            /** Updates UI elements based on the game state. */
            const updateUI = () => {
                cardCountElement.textContent = `Cartes restantes : ${deck.length}`;
                numCardsInput.max = deck.length > 0 ? deck.length : 1;
                startButton.disabled = isDrawing || deck.length === 0;
                stopButton.disabled = !isDrawing;
                shuffleButton.disabled = isDrawing;
                numCardsInput.disabled = isDrawing;
                intervalInput.disabled = isDrawing;
            };

            /** Exports the drawn cards history to a CSV file. */
            const exportResultsToCSV = () => {
                if (drawnCardsHistory.length === 0) return;

                let csvContent = "data:text/csv;charset=utf-8,";
                csvContent += "Ordre,Carte Tirée\r\n"; // Headers

                drawnCardsHistory.forEach((cardName, index) => {
                    csvContent += `${index + 1},"${cardName}"\r\n`;
                });

                const encodedUri = encodeURI(csvContent);
                const link = document.createElement("a");
                link.setAttribute("href", encodedUri);
                link.setAttribute("download", "tirage-cartes.csv");
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            };

            /** Displays the history of drawn cards in a table. */
            const displayResultsTable = () => {
                if (drawnCardsHistory.length === 0) return;

                let tableHTML = `
                    <div class="bg-white p-6 rounded-xl shadow-lg">
                        <div class="flex justify-between items-center mb-4">
                            <h2 class="text-2xl font-bold text-gray-800">Résultats du Tirage</h2>
                            <button id="export-button" class="px-4 py-2 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-75 transition-transform transform hover:scale-105">
                                Exporter en CSV
                            </button>
                        </div>
                        <div class="overflow-x-auto">
                            <table class="min-w-full bg-white border-collapse">
                                <thead class="bg-gray-200">
                                    <tr>
                                        <th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 border-b">Ordre</th>
                                        <th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 border-b">Carte Tirée</th>
                                    </tr>
                                </thead>
                                <tbody>
                `;
                drawnCardsHistory.forEach((cardName, index) => {
                    tableHTML += `
                        <tr class="hover:bg-gray-50">
                            <td class="px-4 py-2 border-b border-gray-200 text-center w-1/4">${index + 1}</td>
                            <td class="px-4 py-2 border-b border-gray-200">${cardName}</td>
                        </tr>
                    `;
                });
                tableHTML += `</tbody></table></div></div>`;
                resultsContainer.innerHTML = tableHTML;

                // Add event listener to the newly created button
                document.getElementById('export-button').addEventListener('click', exportResultsToCSV);
            };

            /** Stops the current drawing process. */
            const stopDrawing = () => {
                clearTimeout(drawTimeout);
                isDrawing = false;
                statusDisplay.textContent = "Tirage arrêté.";
                updateUI();
                displayResultsTable(); // Show results even if stopped early
            };

            /** Starts the card drawing process. */
            const startDrawing = () => {
                let cardsToDraw = parseInt(numCardsInput.value, 10);
                const interval = parseInt(intervalInput.value, 10) * 1000;

                if (isNaN(cardsToDraw) || cardsToDraw <= 0) {
                    statusDisplay.textContent = "Veuillez entrer un nombre de cartes valide.";
                    return;
                }
                if (cardsToDraw > deck.length) {
                    statusDisplay.textContent = "Pas assez de cartes dans le paquet !";
                    return;
                }

                isDrawing = true;
                drawnCardsHistory = [];
                resultsContainer.innerHTML = '';
                updateUI();
                statusDisplay.textContent = "Tirage en cours...";

                function drawLoop() {
                    if (cardsToDraw > 0 && deck.length > 0) {
                        const card = deck.pop();
                        drawnCardsHistory.push(card.name);
                        updateDrawnCardUI(card);
                        speak(card.name);
                        updateUI();
                        
                        cardsToDraw--;
                        if (cardsToDraw > 0) {
                            drawTimeout = setTimeout(drawLoop, interval);
                        } else {
                            isDrawing = false;
                            statusDisplay.textContent = "Tirage terminé !";
                            updateUI();
                            displayResultsTable();
                        }
                    } else {
                        stopDrawing();
                    }
                }
                drawLoop();
            };
            
            /** Resets the game to its initial state. */
            const resetAndShuffle = () => {
                if (isDrawing) stopDrawing();
                createDeck();
                shuffleDeck();
                statusDisplay.textContent = "";
                resultsContainer.innerHTML = ''; 
                drawnCardsHistory = [];
                drawnCardContainer.className = 'playing-card bg-gray-200 flex items-center justify-center text-center';
                drawnCardContainer.innerHTML = '<span class="text-gray-500">Prêt pour le tirage</span>';
                updateUI();
            };

            // --- Event Listeners ---
            startButton.addEventListener('click', startDrawing);
            stopButton.addEventListener('click', stopDrawing);
            shuffleButton.addEventListener('click', resetAndShuffle);

            // --- Initialization ---
            if ('speechSynthesis' in window) {
                window.speechSynthesis.getVoices();
            }
            resetAndShuffle();
        });
    </script>
</body>
</html></div></div>
</div>
</div>
</div>]]></content:encoded>
					
					<wfw:commentRss>https://outilsmemoire.fr/2026/01/27/diseur-de-cartes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Diseur de mots</title>
		<link>https://outilsmemoire.fr/2026/01/27/46/</link>
					<comments>https://outilsmemoire.fr/2026/01/27/46/#respond</comments>
		
		<dc:creator><![CDATA[pcamino]]></dc:creator>
		<pubDate>Tue, 27 Jan 2026 20:41:35 +0000</pubDate>
				<category><![CDATA[Outil]]></category>
		<guid isPermaLink="false">https://outilsmemoire.fr/?p=46</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[<div class="et_pb_section_2 et_pb_section et_section_regular et_flex_section">
<div class="et_pb_row_2 et_pb_row et_flex_row">
<div class="et_pb_column_2 et_pb_column et-last-child et_flex_column et_pb_css_mix_blend_mode_passthrough et_flex_column_24_24 et_flex_column_24_24_tablet et_flex_column_24_24_phone">
<div class="et_pb_code_5 et_pb_code et_pb_module"></div>

<div class="et_pb_code_6 et_pb_code et_pb_module"></div>

<div class="et_pb_code_7 et_pb_code et_pb_module"><div class="et_pb_code_inner">
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Générateur de Mots</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            padding: 20px;
            background-color: #f4f4f9;
            line-height: 1.6;
        }
        h1 {
            color: #333;
            text-align: center;
        }
        #controls {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            margin-bottom: 20px;
            max-width: 600px;
            margin-left: auto;
            margin-right: auto;
        }
        .control-group {
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        label {
            font-weight: 500;
        }
        input[type="number"] {
            width: 80px;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        .button-container {
            text-align: center;
            margin-top: 20px;
        }
        button {
            padding: 10px 20px;
            font-size: 1em;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin: 0 5px;
            transition: background-color 0.3s, transform 0.1s;
        }
        button:active {
            transform: scale(0.98);
        }
        #start {
            background-color: #007bff;
            color: white;
        }
        #start:hover { background-color: #0056b3; }
        #stop {
            background-color: #dc3545;
            color: white;
        }
        #stop:hover { background-color: #c82333; }
        #pauseResumeButton {
            background-color: #ffc107;
            color: #212529;
        }
        #pauseResumeButton:hover { background-color: #e0a800; }
        #downloadButton {
            background-color: #28a745;
            color: white;
        }
        #downloadButton:hover { background-color: #218838; }
        button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }
        #currentWordDisplay {
            font-size: 2.5em;
            text-align: center;
            margin-top: 20px;
            padding: 20px;
            background-color: #fff;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            min-height: 60px;
            color: #333;
            font-weight: bold;
        }
        #seriesContainer {
            margin-top: 30px;
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            justify-content: center;
        }
        .series-table {
            border-collapse: collapse;
            width: 320px;
            background-color: #fff;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            border-radius: 8px;
            overflow: hidden;
        }
        .series-table th, .series-table td {
            border: 1px solid #ddd;
            padding: 10px;
            text-align: left;
        }
        .series-table th {
            background-color: #e9ecef;
            color: #495057;
        }
        .series-table .index-col {
            width: 40px;
            text-align: center;
            font-weight: bold;
        }
        .hidden {
            display: none;
        }
    </style>
</head>
<body>
    <h1>🎤 Générateur de Mots Avancé</h1>

    <div id="controls">
        <div class="control-group">
            <label for="numSeries">Nombre de séries de mots :</label>
            <input type="number" id="numSeries" value="1" min="1">
        </div>
        <div class="control-group">
            <label for="numWords">Nombre de mots par série :</label>
            <input type="number" id="numWords" value="10" min="1">
        </div>
        <div class="control-group">
            <label for="duration">Durée entre les mots (secondes) :</label>
            <input type="number" id="duration" value="2" min="1">
        </div>
        <div class="button-container">
            <button id="start">▶️ Démarrer</button>
            <button id="pauseResumeButton" class="hidden">⏸️ Pause</button>
            <button id="stop" class="hidden">⛔ Arrêter</button>
            <button id="downloadButton" class="hidden">💾 Télécharger les résultats (CSV)</button>
        </div>
    </div>

    <div id="currentWordDisplay" class="hidden"></div>
    <div id="seriesContainer"></div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // Element references
            const numSeriesInput = document.getElementById('numSeries');
            const numWordsInput = document.getElementById('numWords');
            const durationInput = document.getElementById('duration');
            const startButton = document.getElementById('start');
            const stopButton = document.getElementById('stop');
            const pauseResumeButton = document.getElementById('pauseResumeButton');
            const downloadButton = document.getElementById('downloadButton');
            const currentWordDisplay = document.getElementById('currentWordDisplay');
            const seriesContainer = document.getElementById('seriesContainer');

            // Word list
            const availableWords = ["abaisser", "abandonner", "abasourdir", "abdiquer", "abhorrer", "abîmer", "abolir", "abonder", "abonner", "aboutir", "aboyer", "abréger", "abriter", "abroger", "absorber", "abstenir", "abuser", "accabler", "accélérer", "accepter", "acclamer", "accommoder", "accompagner", "accomplir", "accorder", "accoster", "accoucher", "accrocher", "accroître", "accueillir", "accuser", "acheminer", "achever", "acquérir", "acquitter", "activer", "adapter", "adhérer", "adjuger", "admettre", "admirer", "adopter", "adorer", "adoucir", "adresser", "affecter", "affermir", "afficher", "affiler", "affirmer", "affliger", "affluer", "affoler", "affronter", "agacer", "agencer", "aggraver", "agir", "agiter", "agrafer", "aider", "aiguiser", "aimer", "ajouter", "ajuster", "alarmer", "aligner", "alléger", "alléguer", "allier", "allonger", "allumer", "alourdir", "altérer", "amasser", "améliorer", "aménager", "amener", "amplifier", "amuser", "analyser", "anéantir", "animer", "annoncer", "annuler", "anticiper", "apaiser", "apercevoir", "aplanir", "applaudir", "appliquer", "apporter", "apprécier", "apprendre", "approcher", "approprier", "approuver", "arbitrer", "arborer", "arracher", "arranger", "arrêter", "arriver", "arroser", "aspirer", "assainir", "assaillir", "assembler", "asseoir", "assister", "associer", "assombrir", "assortir", "assumer", "assurer", "astiquer", "attacher", "attaquer", "atteindre", "atteler", "attendre", "atténuer", "atterrir", "attirer", "attraper", "attribuer", "augmenter", "authentifier", "automatiser", "autoriser", "avaler", "avancer", "aventurer", "avertir", "aviser", "avoir", "avouer", "baisser", "balancer", "balayer", "baliser", "bannir", "baptiser", "barrer", "bavarder", "béatifier", "bénir", "bercer", "blâmer", "blanchir", "blesser", "bloquer", "boire", "bondir", "border", "borner", "boucher", "bouger", "bouillir", "bourrer", "boutonner", "braconner", "braver", "bricoler", "briller", "brouiller", "brûler", "brusquer", "bûcher", "buller", "cacher", "calmer", "cambrioler", "camoufler", "camper", "canaliser", "capituler", "captiver", "capturer", "caresser", "casser", "catégoriser", "causer", "cautionner", "céder", "ceindre", "ceinturer", "celer", "censurer", "centraliser", "cerner", "certifier", "cesser", "chahuter", "chambouler", "chanceler", "changer", "chanter", "chasser", "châtier", "chauffer", "chauler", "chercher", "chevaucher", "choisir", "chuchoter", "circuler", "citer", "clarifier", "classer", "cligner", "clôturer", "cocher", "coïncider", "collaborer", "collecter", "coller", "combattre", "combiner", "commander", "commencer", "commenter", "commettre", "communiquer", "comparaître", "comparer", "compatir", "compléter", "complimenter", "compliquer", "composer", "comprendre", "compresser", "compromettre", "compter", "concevoir", "concentrer", "concerner", "concevoir", "condamner", "condenser", "conditionner", "confesser", "confier", "confirmer", "confisquer", "conforter", "congédier", "conjuguer", "connaître", "connecter", "conquérir", "consacrer", "considérer", "consoler", "consolider", "consommer", "constater", "constituer", "construire", "consulter", "contacter", "contempler", "contenir", "contenter", "contester", "continuer", "contracter", "contrarier", "contribuer", "contrôler", "convaincre", "convenir", "convertir", "convoquer", "copier", "corriger", "corroborer", "coucher", "coudre", "couler", "couper", "courir", "couvrir", "craindre", "créer", "creuser", "crever", "crier", "croire", "croquer", "croître", "cueillir", "cultiver", "cumuler", "débarrasser", "débattre", "débiter", "déborder", "déceler", "décider", "décoller", "décommander", "décomposer", "déconseiller", "décorer", "découvrir", "décrire", "dédier", "déduire", "défendre", "défiler", "définir", "dégager", "déguster", "déléguer", "délivrer", "demander", "démarrer", "déménager", "démentir", "démettre", "démolir", "démonter", "démoraliser", "dénoncer", "dénouer", "dépasser", "dépeindre", "dépendre", "dépérir", "déplacer", "déplorer", "déployer", "dépolluer", "déposer", "dépouiller", "dépoussiérer", "déranger", "dériver", "dérober", "désactiver", "désapprouver", "descendre", "déserter", "désespérer", "désigner", "désirer", "désobéir", "dessécher", "dessiner", "détacher", "détailler", "détériorer", "détester", "détonner", "détruire", "devancer", "développer", "devenir", "devoir", "diagnostiquer", "dialoguer", "dicter", "différer", "diffuser", "digérer", "diminuer", "dîner", "diriger", "discerner", "discipliner", "discuter", "disparaître", "dispatcher", "dispenser", "disposer", "disséquer", "dissiper", "dissoudre", "distinguer", "distraire", "diverger", "diviser", "divulguer", "dormir", "doter", "doubler", "douter", "draguer", "dramatiser", "dresser", "dupliquer", "ébranler", "écarter", "échanger", "échapper", "échouer", "éclaircir", "éclater", "éclipser", "écouter", "écraser", "écrire", "édifier", "éditer", "éduquer", "effacer", "effectuer", "effleurer", "effondrer", "égaliser", "égarer", "égayer", "éjecter", "élaborer", "élargir", "élever", "éliminer", "éloigner", "émaner", "emballer", "embarrasser", "embaucher", "embrasser", "emmener", "émouvoir", "employer", "emprunter", "enchanter", "enchérir", "encourager", "endommager", "endormir", "enduire", "énerver", "enfreindre", "engager", "englober", "enregistrer", "enrichir", "enseigner", "ensevelir", "entamer", "entendre", "enterrer", "enthousiasmer", "entourer", "entraîner", "entretenir", "entrevoir", "envahir", "envelopper", "envier", "envoyer", "épauler", "éperonner", "éplucher", "épouser", "épuiser", "équilibrer", "ériger", "errer", "escalader", "essayer", "estimer", "établir", "étendre", "éteindre", "étendre", "étonner", "étouffer", "étudier", "évaluer", "éviter", "évoquer", "exagérer", "examiner", "exceller", "excuser", "exécuter", "exempter", "exercer", "exiger", "exister", "expérimenter", "expliquer", "exploiter", "explorer", "exposer", "exprimer", "expulser", "extraire", "fabriquer", "faciliter", "faiblir", "faillir", "faire", "falloir", "familiariser", "fasciner", "fatiguer", "favoriser", "féliciter", "fendre", "fermer", "fêter", "feuilleter", "figurer", "filmer", "fixer", "flatter", "fleurir", "flotter", "focaliser", "forcer", "former", "formuler", "fortifier", "fournir", "fragiliser", "freiner", "frémir", "fréquenter", "frire", "frissonner", "frotter", "fructifier", "fuir", "fumer", "fonctionner","arbre", "maison", "voiture", "livre", "table", "chaise", "porte", "fenêtre", "mur", "toit", "sol", "plafond", "lit", "canapé", "télévision", "ordinateur", "téléphone", "radio", "musique", "image", "photo", "film", "vêtement", "chaussure", "chapeau", "sac", "valise", "clé", "serrure", "couteau", "fourchette", "cuillère", "assiette", "verre", "bouteille", "eau", "feu", "air", "terre", "soleil", "lune", "étoile", "ciel", "nuage", "vent", "pluie", "neige", "montagne", "rivière", "océan", "lac", "forêt", "désert", "chemin", "route", "ville", "village", "pays", "continent", "planète", "univers", "espace", "temps", "jour", "nuit", "heure", "minute", "seconde", "année", "mois", "semaine", "printemps", "été", "automne", "hiver", "matin", "soir", "midi", "minuit", "homme", "femme", "enfant", "garçon", "fille", "père", "mère", "frère", "sœur", "grand-père", "grand-mère", "ami", "amie", "voisin", "voisine", "professeur", "élève", "médecin", "patient", "client", "vendeur", "acheteur", "chien", "chat", "oiseau", "poisson", "cheval", "vache", "cochon", "poule", "mouton", "lapin", "souris", "rat", "insecte", "araignée", "serpent", "arbre", "fleur", "fruit", "légume", "pain", "fromage", "viande", "poisson", "riz", "pâtes", "sucre", "sel", "huile", "beurre", "lait", "œuf", "idée", "pensée", "concept", "théorie", "principe", "valeur", "croyance", "opinion", "sentiment", "émotion", "joie", "tristesse", "colère", "peur", "amour", "haine", "espoir", "désespoir", "courage", "lâcheté", "vérité", "mensonge", "justice", "injustice", "liberté", "esclavage", "paix", "guerre", "vie", "mort", "travail", "école", "église", "hôpital", "magasin", "restaurant", "hôtel", "bureau", "usine", "ferme", "plage", "piscine", "cinéma", "théâtre", "musée", "bibliothèque", "parc", "jardin", "rue", "place", "marché", "sport", "jeu", "art", "musique", "littérature", "science", "technologie", "histoire", "langue", "culture", "société", "famille", "groupe", "équipe", "pays", "monde", "nature", "santé", "maladie", "accident", "problème", "solution", "question", "réponse", "exemple", "raison", "cause", "conséquence", "but", "moyen", "fin", "début", "milieu", "fin", "ordre", "désordre", "chaos", "harmonie", "équilibre", "mouvement", "immobilité", "stabilité", "instabilité", "changement", "permanence", "évolution", "révolution", "progrès", "régression", "développement", "déclin", "croissance", "décroissance", "abondance", "rareté", "excès", "manque", "suffisance", "besoin", "envie", "désir", "volonté", "pouvoir", "contrôle", "influence", "dépendance", "indépendance", "liberté", "autorité", "responsabilité", "devoir", "droit", "obligation", "interdiction", "permission", "tolérance", "intolérance", "respect", "mépris", "dignité", "honte", "culpabilité", "innocence", "vérité", "mensonge", "connaissance", "ignorance", "sagesse", "folie", "raison", "déraison", "conscience", "inconscience", "réalité", "illusion", "apparence", "essence", "existence", "néant", "devenir", "continuité", "rupture", "mémoire", "oubli", "imagination", "créativité", "logique", "absurdité", "sens", "non-sens", "bien", "mal", "beau", "laideur", "vrai", "faux", "juste", "injuste", "simple", "complexe", "facile", "difficile", "rapide", "lent", "calme", "agitation", "tranquillité", "bruit", "silence", "lumière", "obscurité", "chaleur", "froid", "sécheresse", "humidité", "force", "faiblesse", "grandeur", "petitesse", "longueur", "brièveté", "hauteur", "bassesse", "largeur", "étroitesse", "lourdeur", "légèreté", "douceur", "dureté", "ancienneté", "jeunesse", "beauté", "bonté", "méchanceté", "facilité", "difficulté", "rapidité", "lenteur", "propreté", "saleté", "richesse", "pauvreté", "joie", "peine", "plaisir", "douleur", "souffrance", "bonheur", "malheur", "chance", "malchance", "succès", "échec", "réussite", "échec", "amour", "amitié", "haine", "jalousie", "envie", "compassion", "empathie", "sympathie", "antipathie", "indifférence", "curiosité", "ennui", "intérêt", "passion", "vocation", "travail", "loisir", "repos", "fatigue", "énergie", "santé", "maladie", "vieillesse", "jeunesse", "enfance", "adolescence", "âge adulte", "sénescence", "naissance", "mort", "immortalité", "finitude", "infini", "éternité", "instant", "moment", "durée", "période", "époque", "ère", "siècle", "millénaire", "journée", "semaine", "mois", "année", "décennie", "siècle", "millénaire", "continent", "pays", "région", "ville", "village", "quartier", "rue", "avenue", "boulevard", "chemin", "route", "autoroute", "pont", "tunnel", "bâtiment", "maison", "immeuble", "église", "mosquée", "synagogue", "temple", "école", "université", "hôpital", "clinique", "pharmacie", "magasin", "supermarché", "marché", "restaurant", "café", "bar", "hôtel", "bureau", "usine", "ferme", "plage", "piscine", "cinéma", "théâtre", "musée", "bibliothèque", "parc", "jardin", "fôret", "montagne", "rivière", "lac", "océan", "mer", "désert", "ciel", "nuage", "soleil", "lune", "étoile", "planète", "univers", "espace", "temps", "matière", "énergie", "vie", "mort", "naissance", "mariage", "divorce", "famille", "ami", "ennemi", "voisin", "collègue", "connaissance", "inconnu", "étranger", "enfant", "adulte", "vieillard", "homme", "femme", "garçon", "fille", "père", "mère", "frère", "sœur", "grand-père", "grand-mère", "oncle", "tante", "cousin", "cousine", "neveu", "nièce", "animal", "chien", "chat", "oiseau", "poisson", "cheval", "vache", "cochon", "poule", "mouton", "lapin", "souris", "rat", "insecte", "araignée","amour", "haine", "joie", "tristesse", "colère", "peur", "espoir", "désespoir", "courage", "lâcheté", "vérité", "mensonge", "justice", "injustice", "liberté", "esclavage", "paix", "guerre", "vie", "mort", "temps", "espace", "énergie", "matière", "nature", "culture", "société", "histoire", "art", "musique", "littérature", "science", "technologie", "progrès", "évolution", "changement", "stabilité", "ordre", "désordre", "chaos", "harmonie", "équilibre", "déséquilibre", "unité", "diversité", "complexité", "simplicité", "réussite", "échec", "bonheur", "malheur", "chance", "malchance", "vérité", "mensonge", "connaissance", "ignorance", "sagesse", "folie", "raison", "déraison", "conscience", "inconscience", "réalité", "illusion", "apparence", "essence", "existence", "néant", "devenir", "continuité", "rupture", "cause", "conséquence", "but", "moyen", "fin", "début", "origine", "destinée", "mémoire", "oubli", "imagination", "créativité", "logique", "absurdité", "sens", "non-sens", "bien", "mal", "beau", "laideur", "vrai", "faux", "juste", "injuste", "simple", "complexe", "facile", "difficile", "rapide", "lent", "calme", "agitation", "tranquillité", "bruit", "silence", "lumière", "obscurité", "chaleur", "froid", "sécheresse", "humidité", "force", "faiblesse", "grandeur", "petitesse", "longueur", "brièveté", "hauteur", "bassesse", "largeur", "étroitesse", "lourdeur", "légèreté", "douceur", "dureté", "ancienneté", "jeunesse", "beauté", "laideur", "bonté", "méchanceté", "vérité", "fausseté", "justice", "injustice", "simplicité", "complexité", "facilité", "difficulté", "rapidité", "lenteur", "calme", "agitation", "tranquillité", "bruit", "silence", "lumière", "obscurité", "chaleur", "froid", "sécheresse", "humidité", "force", "faiblesse", "grandeur", "petitesse", "longueur", "brièveté", "hauteur", "bassesse", "largeur", "étroitesse", "lourdeur", "légèreté", "douceur", "dureté", "ancienneté", "jeunesse", "liberté", "autorité", "pouvoir", "contrôle", "influence", "dépendance", "indépendance", "autonomie", "hétéronomie", "responsabilité", "irresponsabilité", "culpabilité", "innocence", "droit", "devoir", "obligation", "interdiction", "permission", "tolérance", "intolérance", "respect", "mépris", "dignité", "indignité", "égalité", "inégalité", "fraternité", "solidarité", "individualisme", "collectivisme", "prospérité", "misère", "richesse", "pauvreté", "abondance", "rareté", "suffisance", "manque", "excès", "modération", "tempérance", "excès", "manque", "plaisir", "douleur", "souffrance", "joie", "bonheur", "malheur", "satisfaction", "insatisfaction", "contentement", "mécontentement", "espoir", "déception", "confiance", "méfiance", "amour", "amitié", "haine", "jalousie", "envie", "compassion", "empathie", "sympathie", "antipathie", "indifférence", "curiosité", "ennui", "intérêt", "passion", "vocation", "travail", "loisir", "repos", "fatigue", "énergie", "santé", "maladie", "vieillesse", "jeunesse", "enfance", "adolescence", "âge adulte", "sénescence", "naissance", "mort", "immortalité", "finitude", "infini", "éternité", "instant", "moment", "durée", "période", "époque", "ère", "siècle", "millénaire", "temps", "espace", "dimension", "univers", "multivers", "cosmos", "ordre", "désordre", "chaos", "harmonie", "équilibre", "mouvement", "immobilité", "stabilité", "instabilité", "changement", "permanence", "évolution", "révolution", "progrès", "régression", "développement", "déclin", "croissance", "décroissance", "adaptation", "altruisme", "ambition", "amitié", "analyse", "anarchie", "angoisse", "apathie", "arbitraire", "ardeur", "assurance", "attachement", "authenticité", "avarice", "aversion", "avidité", "bravoure", "capacité", "caractère", "certitude", "charisme", "circonspection", "clarté", "cohérence", "collectivité", "communication", "complexité", "compromis", "concentration", "concept", "confiance", "conformisme", "confusion", "connectivité", "connaissance", "conséquence", "constance", "contemplation", "contentement", "continuité", "contradiction", "contraste", "conviction", "coopération", "crédibilité", "crainte", "critique", "cruauté", "curiosité", "déception", "déclin", "découverte", "déférence", "défiance", "dégoût", "délicatesse", "dépendance", "déplaisir", "désir", "désolation", "détermination", "dévouement", "différence", "difficulté", "dignité", "dilemme", "discipline", "discours", "discrétion", "disponibilité", "dissidence", "distance", "distinction", "diversité", "dogme", "doute", "dynamisme", "efficacité", "effort", "égalité", "élégance", "éloquence", "émancipation", "émerveillement", "émotion", "empathie", "engagement", "ennui", "enthousiasme", "entité", "entourage", "équilibre", "équité", "erreur", "estime", "éthique", "évidence", "exaltation", "exception", "expérience", "expression", "extase", "familiarité", "fanatisme", "fatalité", "fierté", "finesse", "flexibilité", "fragilité", "franchise", "frustration", "générosité", "gentillesse", "globalité", "grandeur", "gratitude", "gravité", "hardiesse", "harmonie", "héroïsme", "honnêteté", "honneur", "humilité", "humour", "idéal", "identité", "ignorance", "illusion", "importance", "improvisation", "impulsion", "inadéquation", "incohérence", "inconfort", "inconscience", "indépendance", "indifférence", "individualité", "indulgence", "inertie", "infériorité", "influence", "ingéniosité", "inhibition", "iniquité", "injustice", "innocence", "insécurité", "inspiration", "instabilité", "instinct", "intelligence", "intensité", "intention", "intérêt", "intériorité", "intolérance", "intuition", "ironie", "isolement", "jalousie", "joie", "jugement", "lâcheté", "légalité", "légitimité", "lenteur", "liberté", "limitation", "logique", "loyauté", "lucidité", "luxe", "magie", "magnanimité", "maladresse", "malheur", "malice", "manifestation", "manipulation", "maturité", "médiocrité", "mélancolie", "mémoire", "mensonge", "mentalité", "métaphore", "méthode", "méfiance", "miséricorde", "mobilité", "modernité", "modestie", "moralité", "mouvement", "mystère", "naïveté", "nécessité", "négativité", "noblesse", "nostalgie", "nouveauté", "obéissance", "objectivité", "obligation", "obscurité", "observation", "obsession", "occasion", "opportunité", "optimisme", "ordre", "originalité", "oubli", "patience", "paresse", "partialité", "participation", "passion", "pauvreté", "paix", "perception", "performance", "permanence", "persévérance", "persuasion", "pessimisme", "philosophie", "piété", "plaisir", "politesse", "ponctualité", "popularité", "positivité", "possibilité", "postérité", "pouvoir", "pragmatisme", "précipitation", "précision", "préjugé", "présence", "préservation", "prévention", "prévoyance", "principe", "priorité", "probabilité", "probité", "profondeur", "progrès", "promesse", "promptitude", "propriété", "prospective", "protection", "provocation", "prudence", "publicité", "pureté", "qualité", "quantité", "question", "quiétude", "raison", "rareté", "réalité", "rébellion", "réciprocité", "reconnaissance", "recueillement", "réflexion", "regret", "régularité", "relation", "relativité", "remords", "renoncement", "repentir", "répression", "réputation", "résignation", "résistance", "respect", "responsabilité", "retenue", "réticence", "retraite", "révélation", "rêve", "richesse", "rigidité", "rigueur", "risque", "ritualité", "robustesse", "romantisme", "ruse", "sagesse", "sainteté", "sarcasme", "satisfaction", "savoir", "sécurité", "sédition", "sérénité", "servitude", "simplicité", "sincérité", "singularité", "solidarité", "solitude", "sophisme", "souffrance", "souplesse", "souveraineté", "spontanéité", "stabilité", "stagnation", "stricte", "subtilité", "succès", "suffisance", "supériorité", "superstition", "surprise", "symbolisme", "sympathie", "talent", "témérité", "tempérance", "tendresse", "tension", "terreur", "théorie", "tolérance", "tranquillité", "transcendance", "transformation", "transparence", "tristesse", "trivialité", "turbulence", "ubiquité", "unité", "universalité", "urbanité", "urgence", "utilité", "utopie", "vacuité", "valeur", "vanité", "véhémence", "vélocité", "vengeance", "vénération", "vérité", "vice", "victoire", "vie", "violence", "virulence", "virtuosité", "vision", "vitalité", "volonté", "vulnérabilité", "zèle"];

            // State variables
            let seriesTimeout = null;
            let currentSeriesIndex = 0;
            let currentWordIndex = 0;
            let lastWord = null;
            let seriesData = [];
            let isPaused = false;
            let currentSettings = {};

            const speakWord = (word) => {
                speechSynthesis.cancel();
                const utterance = new SpeechSynthesisUtterance(word);
                utterance.lang = 'fr-FR';
                speechSynthesis.speak(utterance);
            };

            const generateUniqueWord = () => {
                let randomWord;
                if (availableWords.length < 2) return availableWords[0] || '';
                do {
                    randomWord = availableWords[Math.floor(Math.random() * availableWords.length)];
                } while (randomWord === lastWord);
                lastWord = randomWord;
                return randomWord;
            };

            const updateTable = (seriesIndex, wordIndex, word) => {
                const tableBody = document.getElementById(`series-table-body-${seriesIndex}`);
                if (tableBody) {
                    const row = tableBody.insertRow();
                    row.insertCell(0).textContent = wordIndex + 1;
                    row.cells[0].className = 'index-col';
                    row.insertCell(1).textContent = word;
                }
            };
            
            const runSeries = () => {
                if (currentWordIndex >= currentSettings.numWords) {
                    currentSeriesIndex++;
                    currentWordIndex = 0;
                    if (currentSeriesIndex >= currentSettings.numSeries) {
                        stopSequence("✅ Toutes les séries sont terminées.");
                        return;
                    }
                }

                if (currentWordIndex === 0) {
                    document.querySelectorAll('.series-table').forEach(table => table.style.borderColor = "#ddd");
                    const currentTable = document.getElementById(`series-table-${currentSeriesIndex}`);
                    if (currentTable) currentTable.style.borderColor = "#007bff";
                }

                const word = generateUniqueWord();
                currentWordDisplay.textContent = word;
                speakWord(word);
                updateTable(currentSeriesIndex, currentWordIndex, word);
                seriesData[currentSeriesIndex].push(word);

                currentWordIndex++;
                seriesTimeout = setTimeout(runSeries, currentSettings.duration);
            };

            const startSequence = () => {
                const numSeries = parseInt(numSeriesInput.value);
                const numWords = parseInt(numWordsInput.value);
                const duration = parseInt(durationInput.value) * 1000;

                if (isNaN(numSeries) || isNaN(numWords) || isNaN(duration) || numSeries <= 0 || numWords <= 0 || duration <= 0) {
                    alert("Veuillez entrer des valeurs numériques positives et valides.");
                    return;
                }
                if (availableWords.length < 2) {
                    alert("La liste de mots disponibles est trop courte pour garantir des mots uniques.");
                    return;
                }
                
                currentSettings = { numSeries, numWords, duration };
                isPaused = false;
                
                startButton.classList.add('hidden');
                stopButton.classList.remove('hidden');
                pauseResumeButton.classList.remove('hidden');
                pauseResumeButton.textContent = "⏸️ Pause";
                downloadButton.classList.add('hidden');
                currentWordDisplay.classList.remove('hidden');
                seriesContainer.innerHTML = '';
                seriesData = [];

                for (let i = 0; i < numSeries; i++) {
                    seriesData.push([]);
                    const table = document.createElement('table');
                    table.id = `series-table-${i}`;
                    table.className = 'series-table';
                    table.innerHTML = `<thead><tr><th colspan="2">Série ${i + 1}</th></tr><tr><th class="index-col">#</th><th>Mot</th></tr></thead><tbody id="series-table-body-${i}"></tbody>`;
                    seriesContainer.appendChild(table);
                }
                
                currentSeriesIndex = 0;
                currentWordIndex = 0;
                lastWord = null;

                runSeries();
            };
            
            const pauseResumeHandler = () => {
                isPaused = !isPaused;
                if (isPaused) {
                    clearTimeout(seriesTimeout);
                    speechSynthesis.cancel();
                    pauseResumeButton.textContent = "▶️ Reprendre";
                    currentWordDisplay.textContent = "⏸️ En pause...";
                } else {
                    pauseResumeButton.textContent = "⏸️ Pause";
                    runSeries();
                }
            };

            const stopSequence = (message = "🛑 Séquence arrêtée par l'utilisateur.") => {
                clearTimeout(seriesTimeout);
                speechSynthesis.cancel();
                isPaused = false;
                
                startButton.classList.remove('hidden');
                startButton.disabled = false; // Ensure start button is usable
                stopButton.classList.add('hidden');
                pauseResumeButton.classList.add('hidden');
                downloadButton.classList.remove('hidden');
                currentWordDisplay.textContent = message;
                document.querySelectorAll('.series-table').forEach(table => table.style.borderColor = "#ddd");
            };

            const downloadCSV = () => {
                let csvContent = "data:text/csv;charset=utf-8,";
                seriesData.forEach((series, index) => {
                    csvContent += `Série ${index + 1}\r\n#,Mot\r\n`;
                    series.forEach((word, wordIndex) => {
                        csvContent += `${wordIndex + 1},"${word}"\r\n`;
                    });
                    csvContent += "\r\n";
                });
                const encodedUri = encodeURI(csvContent);
                const link = document.createElement("a");
                link.setAttribute("href", encodedUri);
                link.setAttribute("download", "resultats_mots_aleatoires.csv");
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            };

            // Event Listeners
            startButton.addEventListener('click', startSequence);
            stopButton.addEventListener('click', () => stopSequence());
            pauseResumeButton.addEventListener('click', pauseResumeHandler);
            downloadButton.addEventListener('click', downloadCSV);
        });
    </script>
</body>
</html></div></div>

<div class="et_pb_code_8 et_pb_code et_pb_module"></div>
</div>
</div>
</div>]]></content:encoded>
					
					<wfw:commentRss>https://outilsmemoire.fr/2026/01/27/46/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Diseur de nombres</title>
		<link>https://outilsmemoire.fr/2026/01/27/diseur-de-nombres/</link>
					<comments>https://outilsmemoire.fr/2026/01/27/diseur-de-nombres/#respond</comments>
		
		<dc:creator><![CDATA[pcamino]]></dc:creator>
		<pubDate>Tue, 27 Jan 2026 20:36:26 +0000</pubDate>
				<category><![CDATA[Outil]]></category>
		<guid isPermaLink="false">https://outilsmemoire.fr/?p=41</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[<div class="et_pb_section_4 et_pb_section et_section_regular et_flex_section">
<div class="et_pb_row_4 et_pb_row et_flex_row">
<div class="et_pb_column_4 et_pb_column et-last-child et_flex_column et_pb_css_mix_blend_mode_passthrough et_flex_column_24_24 et_flex_column_24_24_tablet et_flex_column_24_24_phone">
<div class="et_pb_code_10 et_pb_code et_pb_module"><div class="et_pb_code_inner"><!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>🎲 Générateur de Nombres Robuste</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&#038;display=swap" rel="stylesheet">
<style>
:root { --font-main: 'Inter', "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-success: linear-gradient(135deg, #38c1ad 0%, #2f9e8e 100%); --gradient-warning: linear-gradient(135deg, #f6ad55 0%, #dd6b20 100%); --gradient-danger: linear-gradient(135deg, #f56565 0%, #e53e3e 100%); --color-primary-start: #667eea; --color-primary-end: #764ba2; --color-text-primary: #2d3748; --color-text-secondary: #718096; --color-bg-light: #f7fafc; --color-border: #e2e8f0; --color-error: #e53e3e; --color-white: #ffffff; --radius-sm: 8px; --radius-md: 12px; --radius-lg: 20px; --shadow-smooth: 0px 2.8px 2.2px rgba(0, 0, 0, 0.02), 0px 6.7px 5.3px rgba(0, 0, 0, 0.028), 0px 12.5px 10px rgba(0, 0, 0, 0.035), 0px 22.3px 17.9px rgba(0, 0, 0, 0.042), 0px 41.8px 33.4px rgba(0, 0, 0, 0.05), 0px 100px 80px rgba(0, 0, 0, 0.07); }
* { box-sizing: border-box; } body { font-family: var(--font-main); background: var(--gradient-primary); margin: 0; padding: 20px; min-height: 100vh; color: var(--color-text-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .container { background: rgba(255, 255, 255, 0.98); backdrop-filter: blur(10px); padding: 30px; border-radius: var(--radius-lg); box-shadow: var(--shadow-smooth); max-width: 500px; margin: 20px auto; border: 1px solid rgba(255, 255, 255, 0.2); } h1 { text-align: center; font-size: 2em; margin-bottom: 1.2em; margin-top: 0; background: var(--gradient-primary); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.form-group { margin-bottom: 25px; } label { font-weight: 600; display: block; margin-bottom: 8px; font-size: 1em; } input[type="number"], .multiselect-header { width: 100%; padding: 15px; font-size: 16px; border: 2px solid var(--color-border); border-radius: var(--radius-md); transition: all 0.2s ease; background-color: var(--color-white); color: var(--color-text-primary); } input[type="number"]:focus, .multiselect-header:hover, .multiselect-header.active { outline: none; border-color: var(--color-primary-start); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); } .erreur { color: var(--color-error); font-size: 14px; margin-top: 8px; font-weight: 500; } @keyframes shake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 20%, 80% { transform: translate3d(2px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-3px, 0, 0); } 40%, 60% { transform: translate3d(3px, 0, 0); } } .input-error { animation: shake 0.7s cubic-bezier(.36,.07,.19,.97) both; border-color: var(--color-error) !important; } button { padding: 15px 25px; font-size: 16px; border: none; border-radius: var(--radius-md); cursor: pointer; margin-top: 15px; font-weight: 600; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; gap: 10px; } button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); } button:active:not(:disabled) { transform: translateY(0px) scale(0.98); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); } button:disabled { opacity: 0.7; cursor: not-allowed; transform: none !important; box-shadow: none !important; } .primary { background: var(--gradient-primary); color: var(--color-white); width: 100%; } .warning { background: var(--gradient-warning); color: var(--color-white); flex: 1; } .danger { background: var(--gradient-danger); color: var(--color-white); flex: 1; } .success { background: var(--gradient-success); color: var(--color-white); width: 100%; } .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.3); border-radius: 50%; border-top-color: var(--color-white); animation: spin 1s ease-in-out infinite; } @keyframes spin { to { transform: rotate(360deg); } }
#enLectureBox { height: 100px; display: flex; align-items: center; justify-content: center; margin: 20px 0 30px 0; background: var(--gradient-primary); border-radius: var(--radius-lg); font-size: 3em; font-weight: bold; color: var(--color-white); box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); } #resultats { margin-top: 30px; font-family: 'Courier New', monospace; background: var(--color-bg-light); padding: 20px; border-radius: var(--radius-lg); border: 1px solid var(--color-border); max-height: 300px; overflow-y: auto; } .row-buttons { display: flex; gap: 15px; margin-top: 15px; } .serie-line { margin-bottom: 15px; padding: 15px; background: rgba(102, 126, 234, 0.08); border-radius: var(--radius-md); border-left: 4px solid var(--color-primary-start); } .numbers-container { display: flex; flex-wrap: wrap; gap: 8px; line-height: 1.6; } .number-item { min-width: 45px; padding: 8px 12px; background: var(--color-white); border-radius: var(--radius-sm); font-weight: 600; font-size: 1em; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); border: 1px solid var(--color-border); text-align: center; } .radio-group { display: flex; gap: 20px; margin-top: 8px; } .radio-option { display: flex; align-items: center; gap: 8px; cursor: pointer; font-weight: normal; margin-bottom: 0; } .radio-option input[type="radio"] { transform: scale(1.2); } .multiselect-container { position: relative; } .dropdown-arrow { transition: transform 0.3s ease; color: var(--color-primary-start); font-weight: bold; } .multiselect-header.active .dropdown-arrow { transform: rotate(180deg); } .multiselect-dropdown { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 2px solid var(--color-primary-start); border-top: none; border-radius: 0 0 var(--radius-md) var(--radius-md); max-height: 300px; overflow-y: auto; z-index: 1000; display: none; box-shadow: var(--shadow-smooth); } .multiselect-dropdown.active { display: block; } .multiselect-actions { padding: 10px; border-bottom: 1px solid var(--color-border); display: flex; gap: 10px; background: rgba(102, 126, 234, 0.05); } .multiselect-options { display: grid; grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 2px; padding: 10px; } .number-option { display: flex; align-items: center; padding: 8px; cursor: pointer; border-radius: 5px; transition: all 0.2s ease; font-size: 14px; justify-content: center; } .number-option:hover { background: rgba(102, 126, 234, 0.1); } .number-option.selected { background: var(--color-primary-start); color: var(--color-white); font-weight: bold; } .number-option input[type="checkbox"] { display: none; } .select-action { background: rgba(102, 126, 234, 0.1); border: none; color: var(--color-primary-start); font-weight: 600; border-radius: var(--radius-sm); } .select-action:hover { background: var(--color-primary-start); color: white; } .status-message { text-align: center; padding: 15px; border-radius: 10px; margin: 15px 0; font-weight: 500; } .status-message.info { background: rgba(102, 126, 234, 0.1); color: #667eea; border: 1px solid rgba(102, 126, 234, 0.2); } .status-message.error { background: rgba(231, 76, 60, 0.1); color: #e74c3c; border: 1px solid rgba(231, 76, 60, 0.2); }
@media (max-width: 500px) { body { padding: 10px; } .container { padding: 20px; margin: 10px auto; } .row-buttons { flex-direction: column; } h1 { font-size: 1.8em; } #enLectureBox { font-size: 2.5em; } }
</style>
</head>
<body>
<div class="container">
<h1>Générateur de Nombres</h1>
<div class="form-group">
<label>Mode de génération :</label>
<div class="radio-group">
<label class="radio-option">
<input type="radio" name="generationMode" value="range" checked onchange="toggleGenerationMode()">
<span>Plage de nombres</span>
</label>
<label class="radio-option">
<input type="radio" name="generationMode" value="custom" onchange="toggleGenerationMode()">
<span>Nombres personnalisés</span>
</label>
</div>
</div>
<div id="rangeMode" class="form-group">
<div class="range-inputs" style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="range-input-group">
<label for="minRange">Minimum :</label>
<input type="number" id="minRange" value="0" min="0" max="99" aria-describedby="erreurRange" oninput="validateInputs()" />
</div>
<div class="range-input-group">
<label for="maxRange">Maximum :</label>
<input type="number" id="maxRange" value="99" min="0" max="99" aria-describedby="erreurRange" oninput="validateInputs()" />
</div>
</div>
<div id="erreurRange" class="erreur" role="alert"></div>
</div>
<div id="customMode" class="form-group" style="display: none;">
<label for="customNumbers">Sélectionner les nombres :</label>
<small id="customNumbersHint" style="display: block; margin-bottom: 8px; color: var(--color-text-secondary);"></small>
<div class="multiselect-container">
<div class="multiselect-header" onclick="toggleCustomDropdown()" role="button" aria-haspopup="true" aria-expanded="false">
<span id="selectedNumbersText">Aucun nombre sélectionné</span>
<span class="dropdown-arrow">▼</span>
</div>
<div class="multiselect-dropdown" id="customDropdown" role="listbox">
<div class="multiselect-actions">
<button type="button" onclick="selectAllNumbers()" class="select-action">Tout sélectionner</button>
<button type="button" onclick="clearAllNumbers()" class="select-action">Tout désélectionner</button>
</div>
<div class="multiselect-options" id="customNumbersList"></div>
</div>
</div>
<div id="erreurCustomNumbers" class="erreur" role="alert"></div>
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; font-weight: normal;">
<input type="checkbox" id="allowRepetition" checked style="transform: scale(1.3); margin-right: 5px;" onchange="updateCustomNumberHint()">
<span>Autoriser la répétition des nombres</span>
</label>
</div>
<div class="form-group">
<label for="nombreNombres">Nombre de tirages :</label>
<input type="number" id="nombreNombres" value="10" min="1" max="1000" aria-describedby="erreurNombreNombres" oninput="validateInputs()"/>
<div id="erreurNombreNombres" class="erreur" role="alert"></div>
</div>
<div class="form-group">
<label for="delai">Délai entre les nombres (sec) :</label>
<input type="number" id="delai" value="2" min="0" max="60" aria-describedby="erreurDelai" oninput="validateInputs()"/>
<div id="erreurDelai" class="erreur" role="alert"></div>
</div>
<div class="form-group">
<label for="nombreSeries">Nombre de séries :</label>
<input type="number" id="nombreSeries" value="1" min="1" max="100" aria-describedby="erreurNombreSeries" oninput="validateInputs()"/>
<div id="erreurNombreSeries" class="erreur" role="alert"></div>
</div>
<button class="primary" onclick="genererEtLire()" id="generateBtn" aria-describedby="status-message">
🎤 Générer et Lire
</button>
<div id="enLectureBox">
<div id="enLecture" role="status" aria-live="polite" aria-label="Nombre en cours de lecture">&#8212;</div>
</div>
<div class="row-buttons">
<button class="warning" onclick="togglePause()" id="pauseBtn" disabled aria-label="Mettre en pause ou reprendre la lecture">
⏸️ Pause
</button>
<button class="danger" onclick="stopLecture()" id="stopBtn" disabled aria-label="Arrêter la lecture">
⏹️ Stop
</button>
</div>
<button class="success" onclick="telechargerTexte()" id="downloadBtn" disabled aria-label="Télécharger les résultats en fichier texte">
📁 Télécharger
</button>
<div class="form-group" style="margin-top: 20px;">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; font-weight: normal;">
<input type="checkbox" id="compactView" onchange="toggleCompactView()" style="transform: scale(1.3); margin-right: 5px;">
<span>Affichage compact des résultats</span>
</label>
</div>
<div id="status-message" role="status" aria-live="polite"></div>
<div id="resultats" role="region" aria-label="Résultats générés"></div>
</div>

<script>
const state = {
  paused: false,
  stopped: false,
  resultText: "",
  currentUtterance: null,
  isGenerating: false,
  selectedNumbers: new Set(),
  availableNumbers: []
};

const elements = {};
const inputIds = ["nombreNombres", "nombreSeries", "delai", "minRange", "maxRange"];

document.addEventListener('DOMContentLoaded', function() {
  initializeElements();
  checkSpeechSynthesisSupport();
});

function initializeElements() {
  elements.enLecture = document.getElementById("enLecture");
  elements.resultats = document.getElementById("resultats");
  elements.generateBtn = document.getElementById("generateBtn");
  elements.pauseBtn = document.getElementById("pauseBtn");
  elements.stopBtn = document.getElementById("stopBtn");
  elements.downloadBtn = document.getElementById("downloadBtn");
  elements.statusMessage = document.getElementById("status-message");
  elements.customDropdown = document.getElementById("customDropdown");
  elements.customNumbersList = document.getElementById("customNumbersList");
  elements.multiselectHeader = document.querySelector('.multiselect-header');
  initializeCustomNumbers();
  updateCustomNumberHint();
}

function checkSpeechSynthesisSupport() {
  if (!('speechSynthesis' in window)) {
    showStatusMessage('La synthèse vocale n\'est pas supportée par votre navigateur.', 'error');
    elements.generateBtn.disabled = true;
  }
}

function showStatusMessage(message, type = 'info') {
  elements.statusMessage.textContent = message;
  elements.statusMessage.className = `status-message ${type}`;
  if (type === 'info' && message) {
    setTimeout(() => {
      if (elements.statusMessage.textContent === message) {
        elements.statusMessage.textContent = '';
        elements.statusMessage.className = 'status-message';
      }
    }, 4000);
  }
}

function genererNombreAleatoire() {
  const allowRepetition = document.getElementById("allowRepetition").checked;
  if (allowRepetition) {
    const mode = document.querySelector('input[name="generationMode"]:checked').value;
    if (mode === 'range') {
      const min = parseInt(document.getElementById("minRange").value);
      const max = parseInt(document.getElementById("maxRange").value);
      return Math.floor(Math.random() * (max - min + 1) + min).toString().padStart(2, '0');
    } else {
      const numbersArray = [...state.selectedNumbers];
      return numbersArray[Math.floor(Math.random() * numbersArray.length)].toString().padStart(2, '0');
    }
  } else {
    if (state.availableNumbers.length === 0) {
      throw new Error("Aucun nombre unique disponible.");
    }
    const randomIndex = Math.floor(Math.random() * state.availableNumbers.length);
    const selectedNumber = state.availableNumbers.splice(randomIndex, 1)[0];
    return selectedNumber.toString().padStart(2, '0');
  }
}

function lireNombre(nombre) {
  return new Promise((resolve, reject) => {
    if (state.stopped) return reject(new Error("Lecture arrêtée."));
    
    speechSynthesis.cancel();
    elements.enLecture.textContent = nombre;
    
    const utterance = new SpeechSynthesisUtterance(nombre);
    utterance.lang = 'fr-FR';
    utterance.rate = 0.8;
    utterance.pitch = 1.0;
    
    utterance.onend = () => {
      state.currentUtterance = null;
      resolve();
    };
    
    utterance.onerror = (event) => {
      state.currentUtterance = null;
      if (state.stopped) {
        reject(new Error("Lecture arrêtée."));
      } else {
        console.error('Erreur de synthèse vocale:', event);
        reject(event);
      }
    };
    
    state.currentUtterance = utterance;
    speechSynthesis.speak(utterance);
  });
}

function togglePause() {
  state.paused = !state.paused;
  if (state.paused) {
    elements.pauseBtn.innerHTML = "▶️ Reprendre";
    elements.pauseBtn.setAttribute('aria-label', 'Reprendre la lecture');
    if (speechSynthesis.speaking) speechSynthesis.pause();
    showStatusMessage('Lecture en pause');
  } else {
    elements.pauseBtn.innerHTML = "⏸️ Pause";
    elements.pauseBtn.setAttribute('aria-label', 'Mettre en pause la lecture');
    if (speechSynthesis.paused) speechSynthesis.resume();
    showStatusMessage('Lecture reprise');
  }
}

function stopLecture() {
  state.stopped = true;
  state.paused = false;
  
  if (speechSynthesis.speaking || speechSynthesis.pending) {
    speechSynthesis.cancel();
  }
  
  showStatusMessage('Lecture arrêtée');
}

function telechargerTexte() {
  if (!state.resultText.trim()) {
    showStatusMessage('Aucun résultat à télécharger', 'error');
    return;
  }
  try {
    const blob = new Blob([state.resultText], { type: "text/plain;charset=utf-8" });
    const link = document.createElement("a");
    const url = URL.createObjectURL(blob);
    link.href = url;
    link.download = `resultats_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
    showStatusMessage('Fichier téléchargé avec succès');
  } catch (error) {
    console.error('Erreur lors du téléchargement:', error);
    showStatusMessage('Erreur lors du téléchargement', 'error');
  }
}

function validateInputs() {
  inputIds.forEach(id => document.getElementById(id)?.classList.remove('input-error'));
  ["erreurNombreNombres", "erreurNombreSeries", "erreurDelai", "erreurRange", "erreurCustomNumbers"]
    .forEach(id => document.getElementById(id).textContent = "");
  
  let isValid = true;
  
  function addError(inputId, errorId, message) {
    document.getElementById(errorId).textContent = message;
    document.getElementById(inputId)?.classList.add('input-error');
    isValid = false;
  }
  
  const nombreNombres = parseInt(document.getElementById("nombreNombres").value);
  const nombreSeries = parseInt(document.getElementById("nombreSeries").value);
  const delai = parseFloat(document.getElementById("delai").value);
  const mode = document.querySelector('input[name="generationMode"]:checked').value;
  const allowRepetition = document.getElementById("allowRepetition").checked;
  
  if (isNaN(nombreNombres) || nombreNombres < 1 || nombreNombres > 1000)
    addError("nombreNombres", "erreurNombreNombres", "Doit être entre 1 et 1000");
  
  if (isNaN(nombreSeries) || nombreSeries < 1 || nombreSeries > 100)
    addError("nombreSeries", "erreurNombreSeries", "Doit être entre 1 et 100");
  
  if (isNaN(delai) || delai < 0 || delai > 60)
    addError("delai", "erreurDelai", "Doit être entre 0 et 60");
  
  if (mode === 'range') {
    const min = parseInt(document.getElementById("minRange").value);
    const max = parseInt(document.getElementById("maxRange").value);
    
    if (min > max) {
      addError("minRange", "erreurRange", "Le min ne peut dépasser le max.");
      document.getElementById("maxRange")?.classList.add('input-error');
    }
    
    if (!allowRepetition && isValid) {
      const rangeSize = max - min + 1;
      if (rangeSize < nombreNombres)
        addError("nombreNombres", "erreurRange", `Pas assez de nombres uniques (${rangeSize}) pour ${nombreNombres} tirages.`);
    }
  } else {
    const selectedCount = state.selectedNumbers.size;
    
    if (allowRepetition) {
      if (selectedCount < 1) {
        addError("customMode", "erreurCustomNumbers", "Sélectionnez au moins un nombre.");
        elements.multiselectHeader.classList.add('input-error');
      }
    } else {
      if (selectedCount < nombreNombres) {
        addError("nombreNombres", "erreurCustomNumbers", `Pas assez de nombres uniques (${selectedCount}) pour ${nombreNombres} tirages.`);
        elements.multiselectHeader.classList.add('input-error');
      }
    }
  }
  
  return {
    isValid,
    values: {
      nombreNombres,
      nombreSeries,
      delai: delai * 1000,
      mode,
      minRange: parseInt(document.getElementById("minRange").value),
      maxRange: parseInt(document.getElementById("maxRange").value)
    }
  };
}

function updateCustomNumberHint() {
  const hintElement = document.getElementById('customNumbersHint');
  const isCustomMode = document.querySelector('input[name="generationMode"]:checked').value === 'custom';
  
  if (isCustomMode) {
    const allowRepetition = document.getElementById('allowRepetition').checked;
    hintElement.style.display = 'block';
    if (allowRepetition) {
      hintElement.textContent = 'Au moins 1 nombre doit être sélectionné.';
    } else {
      hintElement.textContent = 'Le nombre de tirages ne peut dépasser le nombre de nombres sélectionnés.';
    }
  } else {
    hintElement.style.display = 'none';
  }
  validateInputs();
}

function toggleGenerationMode() {
  const isRange = document.querySelector('input[name="generationMode"]:checked').value === 'range';
  document.getElementById('rangeMode').style.display = isRange ? 'block' : 'none';
  document.getElementById('customMode').style.display = isRange ? 'none' : 'block';
  if (!isRange) updateSelectedNumbersText();
  updateCustomNumberHint();
}

function initializeCustomNumbers() {
  const fragment = document.createDocumentFragment();
  for (let i = 0; i <= 99; i++) {
    const option = document.createElement('div');
    option.className = 'number-option';
    option.setAttribute('role', 'option');
    option.innerHTML = `<input type="checkbox" id="num_${i}" value="${i}" tabindex="-1"><span>${i.toString().padStart(2, '0')}</span>`;
    option.addEventListener('click', () => {
      const checkbox = option.querySelector('input');
      checkbox.checked = !checkbox.checked;
      toggleNumberSelection(i, checkbox.checked, option);
    });
    fragment.appendChild(option);
  }
  elements.customNumbersList.appendChild(fragment);
}

function toggleNumberSelection(number, isSelected, optionElement) {
  const option = optionElement || document.querySelector(`#customNumbersList .number-option:nth-child(${number + 1})`);
  
  if (isSelected) state.selectedNumbers.add(number);
  else state.selectedNumbers.delete(number);
  
  option.classList.toggle('selected', isSelected);
  option.setAttribute('aria-selected', isSelected);
  updateSelectedNumbersText();
  validateInputs();
}

function updateSelectedNumbersText() {
  const count = state.selectedNumbers.size;
  const textElement = document.getElementById('selectedNumbersText');
  
  if (count === 0) textElement.textContent = 'Aucun nombre sélectionné';
  else if (count <= 5) textElement.textContent = [...state.selectedNumbers].sort((a, b) => a - b).map(n => n.toString().padStart(2, '0')).join(', ');
  else textElement.textContent = `${count} nombres sélectionnés`;
}

function toggleCustomDropdown() {
  const isActive = elements.customDropdown.classList.toggle('active');
  elements.multiselectHeader.classList.toggle('active');
  elements.multiselectHeader.setAttribute('aria-expanded', isActive);
  
  if (isActive) elements.customDropdown.querySelector('.select-action').focus();
}

function selectAllNumbers() {
  document.querySelectorAll('#customNumbersList .number-option').forEach((option, index) => {
    if (!state.selectedNumbers.has(index)) toggleNumberSelection(index, true, option);
  });
}

function clearAllNumbers() {
  [...state.selectedNumbers].forEach(num => toggleNumberSelection(num, false));
}

function toggleCompactView() {
  elements.resultats.classList.toggle('compact-view');
}

function updateUIState(isGenerating) {
  state.isGenerating = isGenerating;
  elements.generateBtn.disabled = isGenerating;
  
  if (isGenerating) elements.generateBtn.innerHTML = 'Génération... <span class="spinner"></span>';
  else elements.generateBtn.innerHTML = '🎤 Générer et Lire';
  
  elements.pauseBtn.disabled = !isGenerating;
  elements.stopBtn.disabled = !isGenerating;
  elements.downloadBtn.disabled = isGenerating || !state.resultText.trim();
  
  if (!isGenerating) {
    elements.pauseBtn.innerHTML = "⏸️ Pause";
    elements.pauseBtn.setAttribute('aria-label', 'Mettre en pause la lecture');
    state.paused = false;
    state.stopped = false;
    elements.enLecture.textContent = '--';
  }
}

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function waitWhilePaused() {
  while (state.paused && !state.stopped) await sleep(100);
}

async function genererEtLire() {
  const { isValid, values } = validateInputs();
  if (!isValid) {
    showStatusMessage('Veuillez corriger les erreurs dans le formulaire.', 'error');
    return;
  }
  
  const { nombreNombres, nombreSeries, delai, mode, minRange, maxRange } = values;
  const allowRepetition = document.getElementById("allowRepetition").checked;
  
  elements.resultats.innerHTML = "";
  state.resultText = "";
  updateUIState(true);
  showStatusMessage(`Génération de ${nombreSeries} série(s)...`);
  
  try {
    for (let i = 0; i < nombreSeries; i++) {
      if (state.stopped) break;
      
      if (!allowRepetition) {
        if (mode === 'custom') state.availableNumbers = [...state.selectedNumbers];
        else state.availableNumbers = Array.from({length: maxRange - minRange + 1}, (_, k) => k + minRange);
      }
      
      const lineDiv = document.createElement("div");
      lineDiv.className = "serie-line";
      lineDiv.innerHTML = `<div class="serie-title">Série ${i + 1}:</div><div class="numbers-container"></div>`;
      elements.resultats.appendChild(lineDiv);
      
      const numbersContainer = lineDiv.querySelector('.numbers-container');
      let serieNumbers = [];
      
      for (let j = 0; j < nombreNombres; j++) {
        await waitWhilePaused();
        if (state.stopped) break;
        
        try {
          const nombre = genererNombreAleatoire();
          serieNumbers.push(nombre);
          
          await lireNombre(nombre);
          
          const numberSpan = document.createElement("span");
          numberSpan.className = "number-item";
          numberSpan.textContent = nombre;
          numbersContainer.appendChild(numberSpan);
          
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : '';
          if (errorMessage.includes("Lecture arrêtée")) break;
          if (errorMessage.includes("Aucun nombre unique")) {
            showStatusMessage(errorMessage, 'error');
            break;
          }
          showStatusMessage('Une erreur de synthèse vocale est survenue.', 'error');
          break;
        }
        
        if (j < nombreNombres - 1 && delai > 0) await sleep(delai);
      }
      
      state.resultText += `Série ${i + 1}: ${serieNumbers.join(" ")}\n`;
      
      if (state.stopped) break;
      if (i < nombreSeries - 1 && delai > 0) await sleep(delai);
    }
  } catch (error) {
    console.error('Erreur inattendue:', error);
    showStatusMessage('Une erreur inattendue est survenue.', 'error');
  } finally {
    if (state.stopped) {
      showStatusMessage('Génération arrêtée.');
    } else {
      showStatusMessage('Génération terminée !');
    }
    updateUIState(false);
  }
}

document.addEventListener('click', (event) => {
  if (elements.multiselectHeader && !elements.multiselectHeader.contains(event.target) && !elements.customDropdown.contains(event.target)) {
    elements.customDropdown.classList.remove('active');
    elements.multiselectHeader.classList.remove('active');
    elements.multiselectHeader.setAttribute('aria-expanded', 'false');
  }
});

document.addEventListener('keydown', (event) => {
  if (event.ctrlKey || event.metaKey) return;
  
  if (event.key === ' ' && state.isGenerating) {
    event.preventDefault();
    togglePause();
  } else if (event.key === 'Escape' && state.isGenerating) {
    event.preventDefault();
    stopLecture();
  }
});

window.addEventListener('beforeunload', () => {
  if (speechSynthesis.speaking) speechSynthesis.cancel();
});
</script>
</body>
</html></div></div>
</div>
</div>
</div>]]></content:encoded>
					
					<wfw:commentRss>https://outilsmemoire.fr/2026/01/27/diseur-de-nombres/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
