Se rendre au contenu

Code pour les bordereaux

Merci surtout à Gemini pour son aide.

Télécharger le code


<meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Suppresseur de Publicités pour Bordereaux</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0; /* Couleur de fond initiale du papier */

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important; /* Blanc pour la zone sélectionnée */

            border: 2px solid #3b82f6; /* Bleu pour indiquer la sélection active */

        }

        /* Style pour le message d'erreur/info */

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none; /* Caché par défaut */

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success {

            background-color: #28a745; /* Vert pour succès */

        }

        #message-box.error {

            background-color: #dc3545; /* Rouge pour erreur */

        }

        #message-box.info {

            background-color: #17a2b8; /* Bleu pour info */

        }


        /* Styles pour les icônes dans les quadrants (optionnel, pour visibilité) */

        .quadrant-icon {

            font-size: 10px; /* Ajustez la taille selon vos besoins */

            color: #888;

        }


        /* Dimensions A4 Portrait: 210mm x 297mm. Ratio approx 1:1.414 */

        /* Dimensions A4 Paysage: 297mm x 210mm. Ratio approx 1.414:1 */

        /* Pour l'affichage, utilisons des tailles plus petites en gardant le ratio */

        .portrait {

            width: 100px; /* Largeur fixe pour l'icône portrait */

            height: 141.4px; /* Hauteur calculée pour garder le ratio A4 */

        }

        .landscape {

            width: 141.4px; /* Largeur fixe pour l'icône paysage */

            height: 100px; /* Hauteur calculée pour garder le ratio A4 */

        }

    </style>




    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux</h1>

            <p class="text-gray-600 mt-2">Sélectionnez la zone de la publicité à masquer sur votre bordereau.</p>

        </header>


        <main>

            <section class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Choisissez l'orientation et la zone à masquer :</h2>

                <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez votre bordereau (image) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="imageUpload" accept="image/*" class="block w-full max-w-xs text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4

                        file:rounded-lg file:border-0

                        file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700

                        hover:file:bg-blue-100

                        mb-4 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                   

                    <button id="processButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled="">

                        Masquer la Publicité

                    </button>

                </div>

            </section>


            <section>

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">3. Résultat :</h2>

                <div class="flex flex-col items-center">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                    <a id="downloadLink" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                        Télécharger l'image modifiée

                    </a>

                </div>

            </section>

        </main>


        <footer class="mt-8 text-center">

            <p class="text-xs text-gray-500">© 2025 Outil de suppression de publicités. HG: Haut Gauche, HD: Haut Droite, BG: Bas Gauche, BD: Bas Droite.</p>

        </footer>

    </div>


    <script>

        // Références aux éléments DOM

        const portraitPaper = document.getElementById('portrait-paper');

        const landscapePaper = document.getElementById('landscape-paper');

        const allQuadrants = document.querySelectorAll('.quadrant');

        const imageUpload = document.getElementById('imageUpload');

        const processButton = document.getElementById('processButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const downloadLink = document.getElementById('downloadLink');

        const messageBox = document.getElementById('message-box');

        const ctx = imageCanvas.getContext('2d');


        // État de la sélection

        let selectedQuadrantInfo = {

            orientation: null, // 'portrait' ou 'landscape'

            quadrantIndex: null // 0: TL, 1: TR, 2: BL, 3: BR

        };

        let uploadedImage = null;


        // Fonction pour afficher les messages

        function showMessage(text, type = 'info', duration = 3000) {

            messageBox.textContent = text;

            messageBox.className = ''; // Réinitialiser les classes

            messageBox.classList.add(type); // Ajouter la classe de type (success, error, info)

            messageBox.style.display = 'block';

            setTimeout(() => {

                messageBox.style.display = 'none';

            }, duration);

        }


        // Gestion de la sélection des quadrants

        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                // Désélectionner tous les quadrants

                allQuadrants.forEach(q => q.classList.remove('selected'));

                // Sélectionner le quadrant cliqué

                quadrant.classList.add('selected');

               

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

               

                // Activer le bouton de traitement si une image est chargée et un quadrant est sélectionné

                checkProcessButtonState();

                showMessage(`Zone sélectionnée: ${selectedQuadrantInfo.orientation}, quadrant ${selectedQuadrantInfo.quadrantIndex + 1}`, 'info');

            });

        });


        // Gestion du téléversement d'image

        imageUpload.addEventListener('change', (event) => {

            const file = event.target.files[0];

            if (file && file.type.startsWith('image/')) {

                const reader = new FileReader();

                reader.onload = (e) => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        // Afficher l'image originale sur le canvas (optionnel, ou attendre le traitement)

                        // imageCanvas.width = uploadedImage.width;

                        // imageCanvas.height = uploadedImage.height;

                        // ctx.drawImage(uploadedImage, 0, 0);

                        // imageCanvas.style.display = 'block';

                        checkProcessButtonState();

                        showMessage('Image chargée avec succès.', 'success');

                        downloadLink.classList.add('hidden'); // Cacher le lien de téléchargement si une nouvelle image est chargée

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur lors du chargement de l\'image.', 'error');

                        uploadedImage = null;

                        checkProcessButtonState();

                    }

                    uploadedImage.src = e.target.result;

                };

                reader.readAsDataURL(file);

            } else {

                showMessage('Veuillez sélectionner un fichier image valide (JPEG, PNG, GIF, etc.).', 'error');

                uploadedImage = null;

                imageUpload.value = ''; // Réinitialiser le champ de fichier

                checkProcessButtonState();

            }

        });


        // Vérifier si le bouton de traitement doit être activé

        function checkProcessButtonState() {

            if (uploadedImage && selectedQuadrantInfo.quadrantIndex !== null) {

                processButton.disabled = false;

            } else {

                processButton.disabled = true;

            }

        }


        // Gestion du clic sur le bouton "Masquer la Publicité"

        processButton.addEventListener('click', () => {

            if (!uploadedImage) {

                showMessage('Veuillez d\'abord téléverser une image.', 'error');

                return;

            }

            if (selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Veuillez sélectionner une zone à masquer sur les icônes A4.', 'error');

                return;

            }


            // Configurer le canvas avec les dimensions de l'image

            imageCanvas.width = uploadedImage.width;

            imageCanvas.height = uploadedImage.height;


            // Dessiner l'image originale

            ctx.drawImage(uploadedImage, 0, 0);


            // Calculer les coordonnées du rectangle blanc

            const imgWidth = uploadedImage.width;

            const imgHeight = uploadedImage.height;

            let rectX, rectY, rectWidth, rectHeight;


            rectWidth = imgWidth / 2;

            rectHeight = imgHeight / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: // Haut Gauche

                    rectX = 0;

                    rectY = 0;

                    break;

                case 1: // Haut Droite

                    rectX = imgWidth / 2;

                    rectY = 0;

                    break;

                case 2: // Bas Gauche

                    rectX = 0;

                    rectY = imgHeight / 2;

                    break;

                case 3: // Bas Droite

                    rectX = imgWidth / 2;

                    rectY = imgHeight / 2;

                    break;

            }


            // Dessiner le rectangle blanc

            ctx.fillStyle = 'white';

            ctx.fillRect(rectX, rectY, rectWidth, rectHeight);

           

            // Afficher le canvas et le lien de téléchargement

            imageCanvas.style.display = 'block';

            downloadLink.href = imageCanvas.toDataURL('image/png'); // Proposer en PNG par défaut

            downloadLink.download = 'bordereau_modifie.png';

            downloadLink.classList.remove('hidden');

            showMessage('Publicité masquée ! Vous pouvez télécharger l\'image.', 'success');

        });


        // Initialiser l'état du bouton

        checkProcessButtonState();


    </script>

<meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Suppresseur de Publicités pour Bordereaux</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            cursor: grab; /* Curseur pour indiquer que la zone est déplaçable */

        }

        #imageCanvas.dragging {

            cursor: grabbing;

        }

    </style>




    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux</h1>

            <p class="text-gray-600 mt-2">Masquez les publicités sur vos bordereaux Vinted et autres.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Choisissez la zone initiale de la publicité :</h2>

                <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez votre bordereau (Image ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="imageUpload" accept="image/*,application/pdf" class="block w-full max-w-xs text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-4 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                </div>

            </section>

           

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled="">

                    Afficher l'image et ajuster la zone

                </button>

                <div class="flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                    <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                        Masquer la Pub et Télécharger

                    </button>

                </div>

            </section>

        </main>

        </div>


    <script>

        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const imageUpload = document.getElementById('imageUpload');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const ctx = imageCanvas.getContext('2d');


        // Sections UI

        const step1Selection = document.getElementById('step1Selection');

        const step2Upload = document.getElementById('step2Upload');

        const step3Adjust = document.getElementById('step3Adjust');



        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let uploadedImage = null; // Contiendra l'objet Image (après chargement direct ou conversion PDF)

        let pdfDoc = null; // Pour stocker le document PDF chargé

       

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let isDraggingSelection = false;

        let dragStartCoords = { x: 0, y: 0 }; // Coordonnées souris au début du drag

        let rectStartCoords = { x: 0, y: 0 }; // Coordonnées du rectangle au début du drag


        // --- Fonctions Utilitaires ---

        function showMessage(text, type = 'info', duration = 3500) {

            messageBox.textContent = text;

            messageBox.className = ''; // Reset classes

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(uploadedImage && selectedQuadrantInfo.quadrantIndex !== null);

        }


        // --- Gestion des Événements ---


        // 1. Sélection du Quadrant Initial

        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, quadrant ${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez une image/PDF.`, 'info');

            });

        });


        // 2. Téléversement Image/PDF

        imageUpload.addEventListener('change', (event) => {

            const file = event.target.files[0];

            if (!file) return;


            resetCanvasAndState(); // Réinitialiser si un nouveau fichier est chargé


            if (file.type.startsWith('image/')) {

                const reader = new FileReader();

                reader.onload = (e) => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        showMessage('Image chargée. Cliquez sur "Afficher et ajuster".', 'success');

                        checkInitiateButtonState();

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur de chargement de l\'image.', 'error'); uploadedImage = null;

                    }

                    uploadedImage.src = e.target.result;

                };

                reader.readAsDataURL(file);

            } else if (file.type === 'application/pdf') {

                const fileReader = new FileReader();

                fileReader.onload = function() {

                    const typedarray = new Uint8Array(this.result);

                    pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                        pdfDoc = pdfDoc_;

                        renderPdfPage(1); // Afficher la première page

                    }).catch(err => {

                        showMessage('Erreur chargement PDF: ' + (err.message || err), 'error'); uploadedImage = null;

                        checkInitiateButtonState();

                    });

                };

                fileReader.readAsArrayBuffer(file);

            } else {

                showMessage('Type de fichier non supporté. Utilisez JPEG, PNG, GIF ou PDF.', 'error');

                uploadedImage = null;

                imageUpload.value = '';

            }

            checkInitiateButtonState();

        });


        function renderPdfPage(pageNum) {

            if (!pdfDoc) return;

            pdfDoc.getPage(pageNum).then(page => {

                const viewport = page.getViewport({ scale: 2.0 }); // Augmenter l'échelle pour une meilleure qualité

                const tempCanvas = document.createElement('canvas');

                const tempCtx = tempCanvas.getContext('2d');

                tempCanvas.height = viewport.height;

                tempCanvas.width = viewport.width;


                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        showMessage('Page PDF chargée. Cliquez sur "Afficher et ajuster".', 'success');

                        checkInitiateButtonState();

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur conversion PDF en image.', 'error'); uploadedImage = null;

                    }

                    uploadedImage.src = tempCanvas.toDataURL('image/png'); // Convertir en PNG pour qualité

                });

            }).catch(err => {

                showMessage('Erreur rendu page PDF: ' + (err.message || err), 'error'); uploadedImage = null;

                checkInitiateButtonState();

            });

        }


        // 3. Bouton "Afficher l'image et ajuster la zone"

        initiateProcessingButton.addEventListener('click', () => {

            if (!uploadedImage || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Veuillez sélectionner une zone initiale ET téléverser un fichier.', 'error');

                return;

            }

            setupInteractiveStage();

            initiateProcessingButton.classList.add('hidden');

            applyAndDownloadButton.classList.remove('hidden');

            imageCanvas.style.display = 'block';

            showMessage('Ajustez le rectangle rouge, puis validez.', 'info');

        });


        function setupInteractiveStage() {

            imageCanvas.width = uploadedImage.width;

            imageCanvas.height = uploadedImage.height;

           

            // Calcul du rectangle initial basé sur le quadrant

            const imgW = uploadedImage.width;

            const imgH = uploadedImage.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break; // HG

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break; // HD

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break; // BG

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break; // BD

            }

            currentSelectionRect.isDefined = true;

            drawCanvasWithSelection();

        }

       

        function drawCanvasWithSelection(isFinal=false) {

            if (!uploadedImage) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(uploadedImage, 0, 0);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                } else {

                    ctx.strokeStyle = 'red';

                    ctx.lineWidth = 3;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    // Dessiner une petite poignée (optionnel, pour indiquer la déplaçabilité)

                    ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';

                    ctx.fillRect(currentSelectionRect.x + currentSelectionRect.w/2 - 10, currentSelectionRect.y + currentSelectionRect.h/2 - 10, 20, 20);

                }

            }

        }


        // Gestion du glisser-déposer du rectangle de sélection

        imageCanvas.addEventListener('mousedown', (e) => {

            if (!currentSelectionRect.isDefined || !uploadedImage) return;

            const rect = imageCanvas.getBoundingClientRect();

            const mouseX = (e.clientX - rect.left) * (imageCanvas.width / rect.width);

            const mouseY = (e.clientY - rect.top) * (imageCanvas.height / rect.height);


            // Vérifier si le clic est dans le rectangle

            if (mouseX >= currentSelectionRect.x && mouseX <= currentSelectionRect.x + currentSelectionRect.w &&

                mouseY >= currentSelectionRect.y && mouseY <= currentSelectionRect.y + currentSelectionRect.h) {

                isDraggingSelection = true;

                imageCanvas.classList.add('dragging');

                dragStartCoords.x = mouseX;

                dragStartCoords.y = mouseY;

                rectStartCoords.x = currentSelectionRect.x;

                rectStartCoords.y = currentSelectionRect.y;

            }

        });


        imageCanvas.addEventListener('mousemove', (e) => {

            if (!isDraggingSelection || !currentSelectionRect.isDefined || !uploadedImage) return;

            const rect = imageCanvas.getBoundingClientRect();

            const mouseX = (e.clientX - rect.left) * (imageCanvas.width / rect.width);

            const mouseY = (e.clientY - rect.top) * (imageCanvas.height / rect.height);


            const deltaX = mouseX - dragStartCoords.x;

            const deltaY = mouseY - dragStartCoords.y;


            currentSelectionRect.x = rectStartCoords.x + deltaX;

            currentSelectionRect.y = rectStartCoords.y + deltaY;


            // Contraintes pour que le rectangle ne sorte pas du canvas

            currentSelectionRect.x = Math.max(0, Math.min(currentSelectionRect.x, imageCanvas.width - currentSelectionRect.w));

            currentSelectionRect.y = Math.max(0, Math.min(currentSelectionRect.y, imageCanvas.height - currentSelectionRect.h));

           

            drawCanvasWithSelection();

        });


        imageCanvas.addEventListener('mouseup', () => {

            if (isDraggingSelection) {

                isDraggingSelection = false;

                imageCanvas.classList.remove('dragging');

            }

        });

         imageCanvas.addEventListener('mouseleave', () => { // Arrêter le drag si la souris quitte le canvas

            if (isDraggingSelection) {

                isDraggingSelection = false;

                imageCanvas.classList.remove('dragging');

            }

        });



        // 4. Bouton "Masquer la Pub et Télécharger"

        applyAndDownloadButton.addEventListener('click', () => {

            if (!currentSelectionRect.isDefined || !uploadedImage) {

                showMessage('Aucune zone valide définie ou image chargée.', 'error');

                return;

            }

            drawCanvasWithSelection(true); // true pour dessiner le rectangle blanc final


            // Logique de téléchargement améliorée pour iOS

            try {

                const dataURL = imageCanvas.toDataURL('image/png');

                const newWindow = window.open();

                if (newWindow) {

                    newWindow.document.write('<img src="' + dataURL + '" alt="Image modifiée" style="max-width:100%;"/> <br><p>Sur mobile, maintenez l\'appui sur l\'image pour l\'enregistrer. Sur ordinateur, clic droit > Enregistrer l\'image sous...</p>');

                    showMessage('Image prête dans un nouvel onglet.', 'success');

                } else {

                     // Si window.open est bloqué, fournir un lien direct (peut ne pas fonctionner sur iOS pour le téléchargement direct)

                    const link = document.createElement('a');

                    link.href = dataURL;

                    link.download = 'bordereau_modifie.png';

                    document.body.appendChild(link);

                    link.click();

                    document.body.removeChild(link);

                    showMessage('Tentative de téléchargement direct. Si bloqué, vérifiez les pop-ups.', 'info');

                }

            } catch (err) {

                showMessage('Erreur lors de la préparation du téléchargement: ' + (err.message || err), 'error');

            }

           

            // Réinitialiser pour une nouvelle opération

            // resetCanvasAndState(); // Optionnel: ou laisser l'utilisateur recommencer manuellement

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            // imageCanvas.style.display = 'none'; // Cacher le canvas après téléchargement

        });


        function resetCanvasAndState() {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            uploadedImage = null;

            pdfDoc = null;

            currentSelectionRect.isDefined = false;

            isDraggingSelection = false;

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            imageUpload.value = ''; // Important pour permettre de re-téléverser le même fichier

            // Ne pas réinitialiser selectedQuadrantInfo ici, l'utilisateur peut vouloir l'utiliser à nouveau

        }


        // Initialisation

        checkInitiateButtonState();


    </script>

<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Suppresseur de Publicités pour Bordereaux</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            /* Le curseur sera géré dynamiquement par JavaScript */

        }

        .handle { /* Style pour les poignées de redimensionnement */

            position: absolute; /* Positionné par JS */

            width: 10px;

            height: 10px;

            background-color: rgba(255, 0, 0, 0.7);

            border: 1px solid rgba(255, 255, 255, 0.9);

            border-radius: 50%; /* Rond pour une meilleure prise */

            z-index: 10; /* Au-dessus du canvas mais sous les messages */

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux</h1>

            <p class="text-gray-600 mt-2">Masquez les publicités sur vos bordereaux Vinted et autres.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Choisissez la zone initiale approximative :</h2>

                <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez votre bordereau (Image ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="imageUpload" accept="image/*,application/pdf" class="block w-full max-w-xs text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-4 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher et Ajuster la Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4"> <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                    </div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Masquer la Pub et Télécharger

                </button>

            </section>

        </main>

    </div>


    <script>

        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const imageUpload = document.getElementById('imageUpload');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let uploadedImage = null;

        let pdfDoc = null;

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null; // 'move', 'resize-tl', 'resize-t', 'resize-tr', etc.

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 10; // Taille des poignées de redimensionnement

        let handles = []; // Pour stocker les éléments DOM des poignées


        // --- Fonctions Utilitaires ---

        function showMessage(text, type = 'info', duration = 3500) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(uploadedImage && selectedQuadrantInfo.quadrantIndex !== null);

        }


        // --- Gestion des Événements ---


        // 1. Sélection du Quadrant Initial

        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, quadrant ${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez une image/PDF.`, 'info');

            });

        });


        // 2. Téléversement Image/PDF

        imageUpload.addEventListener('change', (event) => {

            const file = event.target.files[0];

            if (!file) return;

            resetCanvasAndState();


            if (file.type.startsWith('image/')) {

                const reader = new FileReader();

                reader.onload = (e) => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        showMessage('Image chargée. Cliquez sur "Afficher et Ajuster".', 'success');

                        checkInitiateButtonState();

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur de chargement de l\'image.', 'error'); uploadedImage = null;

                    }

                    uploadedImage.src = e.target.result;

                };

                reader.readAsDataURL(file);

            } else if (file.type === 'application/pdf') {

                const fileReader = new FileReader();

                fileReader.onload = function() {

                    const typedarray = new Uint8Array(this.result);

                    pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                        pdfDoc = pdfDoc_;

                        renderPdfPage(1);

                    }).catch(err => {

                        showMessage('Erreur chargement PDF: ' + (err.message || err), 'error'); uploadedImage = null;

                        checkInitiateButtonState();

                    });

                };

                fileReader.readAsArrayBuffer(file);

            } else {

                showMessage('Type de fichier non supporté. Utilisez JPEG, PNG, GIF ou PDF.', 'error');

                uploadedImage = null; imageUpload.value = '';

            }

            checkInitiateButtonState();

        });


        function renderPdfPage(pageNum) {

            if (!pdfDoc) return;

            pdfDoc.getPage(pageNum).then(page => {

                const viewport = page.getViewport({ scale: 2.5 }); // Échelle augmentée pour meilleure qualité

                const tempCanvas = document.createElement('canvas');

                const tempCtx = tempCanvas.getContext('2d');

                tempCanvas.height = viewport.height;

                tempCanvas.width = viewport.width;


                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        showMessage('Page PDF chargée. Cliquez sur "Afficher et Ajuster".', 'success');

                        checkInitiateButtonState();

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur conversion PDF en image.', 'error'); uploadedImage = null;

                    }

                    uploadedImage.src = tempCanvas.toDataURL('image/png');

                });

            }).catch(err => {

                showMessage('Erreur rendu page PDF: ' + (err.message || err), 'error'); uploadedImage = null;

                checkInitiateButtonState();

            });

        }


        // 3. Bouton "Afficher et Ajuster la Zone"

        initiateProcessingButton.addEventListener('click', () => {

            if (!uploadedImage || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Veuillez sélectionner une zone initiale ET téléverser un fichier.', 'error');

                return;

            }

            setupInteractiveStage();

            initiateProcessingButton.classList.add('hidden');

            applyAndDownloadButton.classList.remove('hidden');

            imageCanvas.style.display = 'block';

            showMessage('Ajustez le rectangle (déplacez/redimensionnez), puis validez.', 'info');

        });


        function setupInteractiveStage() {

            imageCanvas.width = uploadedImage.width;

            imageCanvas.height = uploadedImage.height;

            const imgW = uploadedImage.width;

            const imgH = uploadedImage.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles();

        }

        function drawCanvasWithSelectionAndHandles(isFinal = false) {

            if (!uploadedImage) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(uploadedImage, 0, 0);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles(); // Cacher les poignées pour l'image finale

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles(); // S'assurer qu'il n'y a pas d'anciennes poignées

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br']; // Top-Left, Top, Top-Right, etc.

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions(); // Positionner correctement

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0) return;

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect(); // Pour positionner les poignées par rapport au canvas affiché


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                handle.style.left = `${positions[type].left}px`;

                handle.style.top = `${positions[type].top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) {

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }


        // Convertir les coordonnées de la souris de l'écran vers le canvas

        function getMousePosOnCanvas(event) {

            const rect = imageCanvas.getBoundingClientRect();

            return {

                x: (event.clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (event.clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        // Gestion du Drag & Resize

        canvasContainer.addEventListener('mousedown', (e) => {

            if (!currentSelectionRect.isDefined || !uploadedImage) return;

            const target = e.target;

            const mousePos = getMousePosOnCanvas(e);


            if (target.classList.contains('handle')) { // Clic sur une poignée

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas && // Clic sur le canvas

                       mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return; // Clic en dehors

            }

            dragStartCoords = mousePos;

            rectStartCoords = { ...currentSelectionRect }; // Copie de l'état initial du rectangle

            e.preventDefault(); // Empêcher la sélection de texte, etc.

        });


        document.addEventListener('mousemove', (e) => { // Écouter sur document pour un drag plus fluide

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                // Mettre à jour le curseur si on survole le canvas ou les poignées

                if (e.target === imageCanvas || e.target.classList.contains('handle')) {

                    const mousePos = getMousePosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        // Vérifier si on survole une poignée (même si pas en drag)

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            // Convertir les coordonnées de la poignée en coordonnées relatives au canvas

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            // Petite zone de tolérance pour la détection du survol de la poignée

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 2) {

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' && // Si pas sur une poignée, vérifier si sur le rectangle

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            const mousePos = getMousePosOnCanvas(e);

            const deltaX = mousePos.x - dragStartCoords.x;

            const deltaY = mousePos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords; // Partir de l'état au début du drag


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            // Assurer que la largeur/hauteur ne deviennent pas négatives ou trop petites

            const minSize = 20; // Taille minimale pour le rectangle

            if (w < minSize) {

                if (activeDragAction.includes('l')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            // Contraintes pour que le rectangle ne sorte pas du canvas

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            // S'assurer que w et h ne dépassent pas les limites du canvas après déplacement de x,y

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);



            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles();

            e.preventDefault();

        });


        document.addEventListener('mouseup', (e) => {

            if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default'; // Réinitialiser le curseur

                // S'assurer que les poignées sont bien positionnées après le drag

                updateHandlesPositions();

            }

        });

        // 4. Bouton "Masquer la Pub et Télécharger"

        applyAndDownloadButton.addEventListener('click', () => {

            if (!currentSelectionRect.isDefined || !uploadedImage) {

                showMessage('Aucune zone valide définie ou image chargée.', 'error');

                return;

            }

            drawCanvasWithSelectionAndHandles(true); // true pour dessiner le rectangle blanc final


            try {

                const dataURL = imageCanvas.toDataURL('image/png');

                const link = document.createElement('a');

                link.href = dataURL;

                link.download = 'bordereau_modifie.png';

                // Tentative de téléchargement direct

                document.body.appendChild(link);

                link.click();

                document.body.removeChild(link);

                showMessage('Téléchargement lancé...', 'success');


            } catch (err) {

                showMessage('Erreur lors de la préparation du téléchargement. Essayez d\'enregistrer l\'image manuellement.', 'error');

                 // Fallback: ouvrir dans une nouvelle fenêtre si le téléchargement direct échoue ou est bloqué

                try {

                    const dataURL = imageCanvas.toDataURL('image/png');

                    const newWindow = window.open();

                    if (newWindow) {

                        newWindow.document.write('<img src="' + dataURL + '" alt="Image modifiée" style="max-width:100%;"/> <br><p>Sur mobile, maintenez l\'appui sur l\'image pour l\'enregistrer. Sur ordinateur, clic droit > Enregistrer l\'image sous...</p>');

                        showMessage('Image prête dans un nouvel onglet (téléchargement direct a pu échouer).', 'info');

                    } else {

                        showMessage('Impossible d\'ouvrir un nouvel onglet. Veuillez vérifier les bloqueurs de pop-up.', 'error');

                    }

                } catch (fallbackError) {

                     showMessage('Erreur critique de téléchargement: ' + (fallbackError.message || fallbackError), 'error');

                }

            }

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            removeHandles(); // Nettoyer les poignées après l'opération

            // imageCanvas.style.display = 'none'; // Optionnel: cacher le canvas

        });


        function resetCanvasAndState() {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            uploadedImage = null;

            pdfDoc = null;

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            imageUpload.value = '';

        }


        // Initialisation

        checkInitiateButtonState();

        window.addEventListener('resize', () => { // Mettre à jour la position des poignées si la fenêtre est redimensionnée

            if (currentSelectionRect.isDefined && handles.length > 0) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>



<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>Suppresseur de Publicités pour Bordereaux</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            overscroll-behavior-y: contain; /* Empêche le "pull-to-refresh" sur mobile pendant le drag */

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            touch-action: none; /* Important pour la gestion tactile personnalisée */

        }

        .handle {

            position: absolute;

            width: 12px; /* Légèrement plus grand pour le tactile */

            height: 12px;

            background-color: rgba(255, 0, 0, 0.7);

            border: 1px solid rgba(255, 255, 255, 0.9);

            border-radius: 50%;

            z-index: 10;

            touch-action: none; /* Empêche le défilement lors de l'interaction avec les poignées */

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux</h1>

            <p class="text-gray-600 mt-2">Masquez les publicités sur vos bordereaux Vinted et autres.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Choisissez la zone initiale approximative :</h2>

                <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez votre bordereau (Image ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="imageUpload" accept="image/*,application/pdf" class="block w-full max-w-xs text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-4 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher et Ajuster la Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                    </div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Masquer la Pub et Télécharger

                </button>

            </section>

        </main>

    </div>


    <script>

        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const imageUpload = document.getElementById('imageUpload');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let uploadedImage = null;

        let pdfDoc = null;

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null;

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 12; // Augmenté pour le tactile

        let handles = [];


        // --- Fonctions Utilitaires ---

        function showMessage(text, type = 'info', duration = 4000) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(uploadedImage && selectedQuadrantInfo.quadrantIndex !== null);

        }


        // --- Gestion des Événements ---


        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, Q${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez un fichier.`, 'info');

            });

        });


        imageUpload.addEventListener('change', (event) => {

            const file = event.target.files[0];

            if (!file) return;

            resetCanvasAndState();


            if (file.type.startsWith('image/')) {

                const reader = new FileReader();

                reader.onload = (e) => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        showMessage('Image chargée. Cliquez sur "Afficher et Ajuster".', 'success');

                        checkInitiateButtonState();

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur de chargement image.', 'error'); uploadedImage = null;

                    }

                    uploadedImage.src = e.target.result;

                };

                reader.readAsDataURL(file);

            } else if (file.type === 'application/pdf') {

                const fileReader = new FileReader();

                fileReader.onload = function() {

                    const typedarray = new Uint8Array(this.result);

                    pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                        pdfDoc = pdfDoc_;

                        renderPdfPage(1);

                    }).catch(err => {

                        showMessage('Erreur chargement PDF: ' + (err.message || err), 'error'); uploadedImage = null;

                        checkInitiateButtonState();

                    });

                };

                fileReader.readAsArrayBuffer(file);

            } else {

                showMessage('Type de fichier non supporté (Image/PDF).', 'error');

                uploadedImage = null; imageUpload.value = '';

            }

            checkInitiateButtonState();

        });


        function renderPdfPage(pageNum) {

            if (!pdfDoc) return;

            pdfDoc.getPage(pageNum).then(page => {

                const viewport = page.getViewport({ scale: 2.5 });

                const tempCanvas = document.createElement('canvas');

                const tempCtx = tempCanvas.getContext('2d');

                tempCanvas.height = viewport.height;

                tempCanvas.width = viewport.width;


                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                    uploadedImage = new Image();

                    uploadedImage.onload = () => {

                        showMessage('Page PDF chargée. Cliquez sur "Afficher et Ajuster".', 'success');

                        checkInitiateButtonState();

                    };

                    uploadedImage.onerror = () => {

                        showMessage('Erreur conversion PDF.', 'error'); uploadedImage = null;

                    }

                    uploadedImage.src = tempCanvas.toDataURL('image/png');

                });

            }).catch(err => {

                showMessage('Erreur rendu PDF: ' + (err.message || err), 'error'); uploadedImage = null;

                checkInitiateButtonState();

            });

        }


        initiateProcessingButton.addEventListener('click', () => {

            if (!uploadedImage || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Sélectionnez zone ET téléversez un fichier.', 'error');

                return;

            }

            setupInteractiveStage();

            initiateProcessingButton.classList.add('hidden');

            applyAndDownloadButton.classList.remove('hidden');

            imageCanvas.style.display = 'block';

            showMessage('Ajustez le rectangle, puis validez.', 'info');

        });


        function setupInteractiveStage() {

            imageCanvas.width = uploadedImage.width;

            imageCanvas.height = uploadedImage.height;

            const imgW = uploadedImage.width;

            const imgH = uploadedImage.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles();

        }

        function drawCanvasWithSelectionAndHandles(isFinal = false) {

            if (!uploadedImage) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(uploadedImage, 0, 0);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles();

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles();

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'];

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions();

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0 || !imageCanvas.offsetParent) return; // Vérif que canvas est visible

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect();


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                handle.style.left = `${canvasRect.left + positions[type].left - canvasContainer.getBoundingClientRect().left}px`;

                handle.style.top = `${canvasRect.top + positions[type].top - canvasContainer.getBoundingClientRect().top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) {

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }


        function getEventPosOnCanvas(event) {

            const rect = imageCanvas.getBoundingClientRect();

            let clientX, clientY;

            if (event.touches && event.touches.length > 0) {

                clientX = event.touches[0].clientX;

                clientY = event.touches[0].clientY;

            } else {

                clientX = event.clientX;

                clientY = event.clientY;

            }

            return {

                x: (clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        function handleInteractionStart(e) {

            if (!currentSelectionRect.isDefined || !uploadedImage) return;

            const target = e.target;

            const eventPos = getEventPosOnCanvas(e);


            if (target.classList.contains('handle')) {

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas &&

                       eventPos.x >= currentSelectionRect.x && eventPos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       eventPos.y >= currentSelectionRect.y && eventPos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return;

            }

            if (activeDragAction && e.cancelable) { // Empêcher le défilement SEULEMENT si une action de drag/resize commence

                 e.preventDefault();

            }


            dragStartCoords = eventPos;

            rectStartCoords = { ...currentSelectionRect };

        }


        function handleInteractionMove(e) {

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                 // Gérer le curseur pour la souris même si pas en drag

                if (e.type === 'mousemove' && (e.target === imageCanvas || e.target.classList.contains('handle'))) {

                    const mousePos = getEventPosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 1.5) { // Tolérance augmentée

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' &&

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            if (e.cancelable) {

                e.preventDefault(); // Empêcher le défilement pendant le drag/resize actif

            }


            const eventPos = getEventPosOnCanvas(e);

            const deltaX = eventPos.x - dragStartCoords.x;

            const deltaY = eventPos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords;


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            const minSize = 20;

            if (w < minSize) {

                if (activeDragAction.includes('l') || activeDragAction.includes('tl') || activeDragAction.includes('bl')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t') || activeDragAction.includes('tl') || activeDragAction.includes('tr')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);


            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles();

        }


        function handleInteractionEnd(e) {

            if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default';

                updateHandlesPositions();

            }

        }


        // Attacher les écouteurs d'événements pour souris et tactile

        canvasContainer.addEventListener('mousedown', handleInteractionStart);

        document.addEventListener('mousemove', handleInteractionMove);

        document.addEventListener('mouseup', handleInteractionEnd);


        canvasContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });

        document.addEventListener('touchmove', handleInteractionMove, { passive: false });

        document.addEventListener('touchend', handleInteractionEnd);

        applyAndDownloadButton.addEventListener('click', () => {

            if (!currentSelectionRect.isDefined || !uploadedImage) {

                showMessage('Zone non définie ou image non chargée.', 'error');

                return;

            }

            drawCanvasWithSelectionAndHandles(true);


            try {

                const dataURL = imageCanvas.toDataURL('image/png');

                const link = document.createElement('a');

                link.href = dataURL;

                link.download = 'bordereau_modifie.png';

                document.body.appendChild(link);

                link.click();

                document.body.removeChild(link);

                showMessage('Téléchargement lancé...', 'success');


            } catch (err) {

                showMessage('Erreur téléchargement. Essayez d\'enregistrer l\'image manuellement.', 'error');

                try {

                    const dataURL = imageCanvas.toDataURL('image/png');

                    const newWindow = window.open();

                    if (newWindow) {

                        newWindow.document.open();

                        newWindow.document.write(

                            '<!DOCTYPE html><html lang="fr"><head><meta name="viewport" content="width=device-width, initial-scale=1.0">' +

                            '<title>Image Modifiée</title><style>body{margin:0; display:flex; flex-direction:column; justify-content:center; align-items:center; min-height:100vh; background-color:#f0f0f0; text-align:center; font-family:sans-serif;} img{max-width:95%; max-height:80vh; object-fit:contain; box-shadow: 0 0 10px rgba(0,0,0,0.2); margin-bottom:15px;} p{color:#333;}</style></head>' +

                            '<body><img src="' + dataURL + '" alt="Image modifiée"/><p>Sur mobile (iOS/Android): Appuyez longuement sur l\'image et choisissez "Ajouter à Photos" ou "Télécharger l\'image".<br>Sur ordinateur: Clic droit > "Enregistrer l\'image sous...".</p></body></html>'

                        );

                        newWindow.document.close();

                        showMessage('Image prête dans un nouvel onglet. Suivez les instructions pour enregistrer.', 'info');

                    } else {

                        showMessage('Impossible d\'ouvrir un nouvel onglet. Vérifiez les bloqueurs de pop-up.', 'error');

                    }

                } catch (fallbackError) {

                     showMessage('Erreur critique de téléchargement: ' + (fallbackError.message || fallbackError), 'error');

                }

            }

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            removeHandles();

        });


        function resetCanvasAndState() {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            uploadedImage = null;

            pdfDoc = null;

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            imageUpload.value = '';

        }


        checkInitiateButtonState();

        window.addEventListener('resize', () => {

            if (currentSelectionRect.isDefined && handles.length > 0 && imageCanvas.offsetParent) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>





<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>Suppresseur de Publicités pour Bordereaux (Batch)</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            overscroll-behavior-y: contain;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            touch-action: none;

        }

        .handle {

            position: absolute;

            width: 14px; /* Encore un peu plus grand pour le tactile */

            height: 14px;

            background-color: rgba(255, 0, 0, 0.6);

            border: 1px solid rgba(255, 255, 255, 0.8);

            border-radius: 50%;

            z-index: 10;

            touch-action: none;

        }

        /* Style pour le loader */

        #loader {

            border: 5px solid #f3f3f3; /* Light grey */

            border-top: 5px solid #3498db; /* Blue */

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

            margin: 10px auto;

            display: none; /* Caché par défaut */

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux (Batch)</h1>

            <p class="text-gray-600 mt-2">Masquez les pubs sur plusieurs fichiers à la fois.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Zone initiale approximative :</h2>

                 <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez vos fichiers (Images ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="fileUpload" accept="image/*,application/pdf" multiple class="block w-full max-w-md text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-2 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                    <div class="my-2 text-sm text-gray-600">Format de sortie pour images :

                        <select id="imageOutputFormat" class="ml-2 p-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500">

                            <option value="image/png">PNG (Défaut)</option>

                            <option value="image/jpeg">JPEG</option>

                        </select>

                    </div>

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone sur le 1er fichier et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher 1er Fichier et Ajuster Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                </div>

                <div id="loader"></div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Appliquer à Tous et Télécharger

                </button>

            </section>

        </main>

    </div>


    <script>

        const { jsPDF } = window.jspdf; // Accéder à jsPDF depuis l'objet global window.jspdf


        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const fileUpload = document.getElementById('fileUpload');

        const imageOutputFormatSelect = document.getElementById('imageOutputFormat');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const loader = document.getElementById('loader');

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let allUploadedFiles = []; // Stocke tous les fichiers téléversés

        let currentFileForAdjustment = null; // L'objet Image ou PDF du premier fichier pour l'ajustement

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null;

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 14;

        let handles = [];


        function showMessage(text, type = 'info', duration = 4000) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(allUploadedFiles.length > 0 && selectedQuadrantInfo.quadrantIndex !== null);

        }


        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, Q${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez des fichiers.`, 'info');

            });

        });


        fileUpload.addEventListener('change', (event) => {

            if (event.target.files.length === 0) {

                allUploadedFiles = [];

                currentFileForAdjustment = null;

                resetCanvasAndState(false); // Ne pas réinitialiser la sélection de quadrant

                checkInitiateButtonState();

                return;

            }

            allUploadedFiles = Array.from(event.target.files);

            currentFileForAdjustment = null; // Sera défini lors du clic sur "Afficher"

            resetCanvasAndState(false);


            if (allUploadedFiles.length > 0) {

                showMessage(`${allUploadedFiles.length} fichier(s) sélectionné(s). Prêt à ajuster le 1er.`, 'success');

            }

            checkInitiateButtonState();

        });

        async function loadFileForAdjustment(file) {

            return new Promise((resolve, reject) => {

                if (file.type.startsWith('image/')) {

                    const reader = new FileReader();

                    reader.onload = (e) => {

                        const img = new Image();

                        img.onload = () => resolve(img);

                        img.onerror = () => reject(new Error('Erreur chargement image pour ajustement.'));

                        img.src = e.target.result;

                    };

                    reader.onerror = () => reject(new Error('Erreur lecture fichier image.'));

                    reader.readAsDataURL(file);

                } else if (file.type === 'application/pdf') {

                    const fileReader = new FileReader();

                    fileReader.onload = function() {

                        const typedarray = new Uint8Array(this.result);

                        pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                            // Pour l'ajustement, on ne rend que la première page du premier PDF

                            pdfDoc_.getPage(1).then(page => {

                                const viewport = page.getViewport({ scale: 2.0 }); // Échelle suffisante pour l'aperçu

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.height = viewport.height;

                                tempCanvas.width = viewport.width;

                                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                                    const img = new Image();

                                    img.onload = () => resolve(img); // On passe une image de la page PDF

                                    img.onerror = () => reject(new Error('Erreur conversion 1ère page PDF en image.'));

                                    img.src = tempCanvas.toDataURL('image/png');

                                }).catch(reject);

                            }).catch(reject);

                        }).catch(reject);

                    };

                    fileReader.onerror = () => reject(new Error('Erreur lecture fichier PDF.'));

                    fileReader.readAsArrayBuffer(file);

                } else {

                    reject(new Error('Type de fichier non supporté pour l\'ajustement.'));

                }

            });

        }


        initiateProcessingButton.addEventListener('click', async () => {

            if (allUploadedFiles.length === 0 || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Sélectionnez zone ET téléversez au moins un fichier.', 'error');

                return;

            }

            loader.style.display = 'block';

            initiateProcessingButton.disabled = true;


            try {

                currentFileForAdjustment = await loadFileForAdjustment(allUploadedFiles[0]);

                setupInteractiveStage(currentFileForAdjustment); // Passe l'image chargée (ou l'image de la 1ère page PDF)

                initiateProcessingButton.classList.add('hidden');

                applyAndDownloadButton.classList.remove('hidden');

                imageCanvas.style.display = 'block';

                showMessage('Ajustez le rectangle sur ce 1er fichier, puis validez.', 'info');

            } catch (error) {

                showMessage(error.message || 'Erreur chargement du 1er fichier.', 'error');

                console.error("Error loading first file for adjustment:", error);

            } finally {

                loader.style.display = 'none';

                initiateProcessingButton.disabled = false; // Réactiver en cas d'erreur

            }

        });


        function setupInteractiveStage(imageToAdjust) { // imageToAdjust est un objet Image

            imageCanvas.width = imageToAdjust.width;

            imageCanvas.height = imageToAdjust.height;

            const imgW = imageToAdjust.width;

            const imgH = imageToAdjust.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles(imageToAdjust); // Dessine l'image passée

        }

        function drawCanvasWithSelectionAndHandles(imageToDraw, isFinal = false) {

            if (!imageToDraw) return; // S'assurer qu'il y a une image à dessiner

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(imageToDraw, 0, 0, imageCanvas.width, imageCanvas.height); // S'assurer que l'image est dessinée aux dimensions du canvas


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles();

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles();

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'];

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions();

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0 || !imageCanvas.offsetParent) return;

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect();


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                // Positionnement relatif au canvasContainer

                handle.style.left = `${canvasRect.left + positions[type].left - canvasContainer.getBoundingClientRect().left}px`;

                handle.style.top = `${canvasRect.top + positions[type].top - canvasContainer.getBoundingClientRect().top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) { /* ... (inchangé) ... */

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }

        function getEventPosOnCanvas(event) { /* ... (inchangé) ... */

            const rect = imageCanvas.getBoundingClientRect();

            let clientX, clientY;

            if (event.touches && event.touches.length > 0) {

                clientX = event.touches[0].clientX;

                clientY = event.touches[0].clientY;

            } else {

                clientX = event.clientX;

                clientY = event.clientY;

            }

            return {

                x: (clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        function handleInteractionStart(e) { /* ... (inchangé, utilise currentFileForAdjustment pour la condition) ... */

            if (!currentSelectionRect.isDefined || !currentFileForAdjustment) return; // Modifié ici

            const target = e.target;

            const eventPos = getEventPosOnCanvas(e);


            if (target.classList.contains('handle')) {

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas &&

                       eventPos.x >= currentSelectionRect.x && eventPos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       eventPos.y >= currentSelectionRect.y && eventPos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return;

            }

            if (activeDragAction && e.cancelable) {

                 e.preventDefault();

            }

            dragStartCoords = eventPos;

            rectStartCoords = { ...currentSelectionRect };

        }

        function handleInteractionMove(e) { /* ... (inchangé, utilise currentFileForAdjustment pour dessiner) ... */

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                if (e.type === 'mousemove' && (e.target === imageCanvas || e.target.classList.contains('handle'))) {

                    const mousePos = getEventPosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 1.5) {

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' &&

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            if (e.cancelable) {

                e.preventDefault();

            }


            const eventPos = getEventPosOnCanvas(e);

            const deltaX = eventPos.x - dragStartCoords.x;

            const deltaY = eventPos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords;


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            const minSize = 20;

            if (w < minSize) {

                if (activeDragAction.includes('l') || activeDragAction.includes('tl') || activeDragAction.includes('bl')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t') || activeDragAction.includes('tl') || activeDragAction.includes('tr')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);


            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles(currentFileForAdjustment); // Redessine avec l'image du 1er fichier

        }

        function handleInteractionEnd(e) { /* ... (inchangé) ... */

             if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default';

                updateHandlesPositions();

            }

        }


        canvasContainer.addEventListener('mousedown', handleInteractionStart);

        document.addEventListener('mousemove', handleInteractionMove);

        document.addEventListener('mouseup', handleInteractionEnd);

        canvasContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });

        document.addEventListener('touchmove', handleInteractionMove, { passive: false });

        document.addEventListener('touchend', handleInteractionEnd);

        applyAndDownloadButton.addEventListener('click', async () => {

            if (!currentSelectionRect.isDefined || allUploadedFiles.length === 0) {

                showMessage('Zone non définie ou aucun fichier chargé.', 'error');

                return;

            }

            loader.style.display = 'block';

            applyAndDownloadButton.disabled = true;

            let filesProcessedCount = 0;


            for (let i = 0; i < allUploadedFiles.length; i++) {

                const file = allUploadedFiles[i];

                const originalFilename = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;

                showMessage(`Traitement de ${file.name} (${i+1}/${allUploadedFiles.length})...`, 'info', 60000); // Longue durée pour le message


                try {

                    if (file.type.startsWith('image/')) {

                        const img = await loadFileForAdjustment(file); // Réutiliser pour charger l'image

                        const tempCanvas = document.createElement('canvas');

                        const tempCtx = tempCanvas.getContext('2d');

                        tempCanvas.width = img.width;

                        tempCanvas.height = img.height;

                        tempCtx.drawImage(img, 0, 0);

                        tempCtx.fillStyle = 'white';

                        tempCtx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                        const outputFormat = imageOutputFormatSelect.value;

                        const extension = outputFormat === 'image/jpeg' ? 'jpg' : 'png';

                        const dataURL = tempCanvas.toDataURL(outputFormat, outputFormat === 'image/jpeg' ? 0.9 : undefined); // Qualité pour JPEG

                        triggerDownload(dataURL, `${originalFilename}_modifie.${extension}`);

                        filesProcessedCount++;


                    } else if (file.type === 'application/pdf') {

                        const pdfOutput = new jsPDF();

                        const fileReader = new FileReader();

                        // Promesse pour lire le fichier PDF

                        const arrayBuffer = await new Promise((resolve, reject) => {

                            fileReader.onload = () => resolve(fileReader.result);

                            fileReader.onerror = reject;

                            fileReader.readAsArrayBuffer(file);

                        });


                        const pdfDoc = await pdfjsLib.getDocument(new Uint8Array(arrayBuffer)).promise;

                        const numPages = pdfDoc.numPages;


                        for (let j = 1; j <= numPages; j++) {

                            const page = await pdfDoc.getPage(j);

                            const viewport = page.getViewport({ scale: 2.0 }); // Bonne échelle pour la qualité

                            const tempCanvas = document.createElement('canvas');

                            const tempCtx = tempCanvas.getContext('2d');

                            tempCanvas.width = viewport.width;

                            tempCanvas.height = viewport.height;


                            await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                            tempCtx.fillStyle = 'white';

                            tempCtx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                            const pageDataUrl = tempCanvas.toDataURL('image/png');

                            if (j > 1) {

                                pdfOutput.addPage([viewport.width, viewport.height]); // Utiliser les dimensions exactes

                            }

                            pdfOutput.addImage(pageDataUrl, 'PNG', 0, 0, viewport.width, viewport.height);

                        }

                        pdfOutput.save(`${originalFilename}_modifie.pdf`);

                        filesProcessedCount++;

                    }

                    await new Promise(resolve => setTimeout(resolve, 200)); // Petite pause entre les téléchargements

                } catch (err) {

                    console.error("Erreur traitement fichier:", file.name, err);

                    showMessage(`Erreur avec ${file.name}: ${err.message || 'Inconnue'}`, 'error');

                }

            }

            loader.style.display = 'none';

            applyAndDownloadButton.disabled = false;

            if (filesProcessedCount > 0) {

                showMessage(`${filesProcessedCount} fichier(s) traité(s) et téléchargé(s).`, 'success');

            } else if (allUploadedFiles.length > 0) {

                 showMessage(`Aucun fichier n'a pu être traité avec succès.`, 'error');

            }


            // Réinitialisation partielle pour permettre un nouveau lot avec la même sélection

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            // Ne pas appeler resetCanvasAndState() complètement pour garder la sélection

            // fileUpload.value = ''; // L'utilisateur peut vouloir re-téléverser

            // allUploadedFiles = []; // Sera réinitialisé au prochain téléversement

        });


        function triggerDownload(dataURL, filename) {

            const link = document.createElement('a');

            link.href = dataURL;

            link.download = filename;

            document.body.appendChild(link);

            link.click();

            document.body.removeChild(link);

        }


        function resetCanvasAndState(resetQuadrantSelection = true) {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            currentFileForAdjustment = null; // Réinitialiser l'image d'ajustement

            // allUploadedFiles = []; // Ne pas réinitialiser ici, se fait au 'change' de fileUpload

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            initiateProcessingButton.disabled = true; // Sera réactivé par checkInitiateButtonState

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            // fileUpload.value = ''; // Ne pas réinitialiser ici pour permettre à l'utilisateur de voir les fichiers sélectionnés


            if (resetQuadrantSelection) {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

            }

        }


        checkInitiateButtonState();

        window.addEventListener('resize', () => {

            if (currentSelectionRect.isDefined && handles.length > 0 && imageCanvas.offsetParent) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>




<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>Suppresseur de Publicités pour Bordereaux (Batch)</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            overscroll-behavior-y: contain;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            touch-action: none;

        }

        .handle {

            position: absolute;

            width: 14px;

            height: 14px;

            background-color: rgba(255, 0, 0, 0.6);

            border: 1px solid rgba(255, 255, 255, 0.8);

            border-radius: 50%;

            z-index: 10;

            touch-action: none;

        }

        #loader {

            border: 5px solid #f3f3f3;

            border-top: 5px solid #3498db;

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

            margin: 10px auto;

            display: none;

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux (Batch)</h1>

            <p class="text-gray-600 mt-2">Masquez les pubs sur plusieurs fichiers à la fois.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Zone initiale approximative :</h2>

                 <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez vos fichiers (Images ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="fileUpload" accept="image/*,application/pdf" multiple class="block w-full max-w-md text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-2 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                    <div class="my-2 text-sm text-gray-600">Format de sortie pour images :

                        <select id="imageOutputFormat" class="ml-2 p-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500">

                            <option value="image/png">PNG (Défaut)</option>

                            <option value="image/jpeg">JPEG</option>

                            <option value="application/pdf">PDF (pour images)</option>

                        </select>

                    </div>

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone sur le 1er fichier et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher 1er Fichier et Ajuster Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                </div>

                <div id="loader"></div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Appliquer à Tous et Télécharger

                </button>

            </section>

        </main>

    </div>


    <script>

        const { jsPDF } = window.jspdf;


        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const fileUpload = document.getElementById('fileUpload');

        const imageOutputFormatSelect = document.getElementById('imageOutputFormat');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const loader = document.getElementById('loader');

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let allUploadedFiles = [];

        let currentFileForAdjustment = null;

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null;

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 14;

        let handles = [];


        function showMessage(text, type = 'info', duration = 4000) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(allUploadedFiles.length > 0 && selectedQuadrantInfo.quadrantIndex !== null);

        }


        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, Q${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez des fichiers.`, 'info');

            });

        });


        fileUpload.addEventListener('change', (event) => {

            if (event.target.files.length === 0) {

                allUploadedFiles = [];

                currentFileForAdjustment = null;

                resetCanvasAndState(false);

                checkInitiateButtonState();

                return;

            }

            allUploadedFiles = Array.from(event.target.files);

            currentFileForAdjustment = null;

            resetCanvasAndState(false);


            if (allUploadedFiles.length > 0) {

                showMessage(`${allUploadedFiles.length} fichier(s) sélectionné(s). Prêt à ajuster le 1er.`, 'success');

            }

            checkInitiateButtonState();

        });

        async function loadFileForAdjustment(file) {

            return new Promise((resolve, reject) => {

                if (file.type.startsWith('image/')) {

                    const reader = new FileReader();

                    reader.onload = (e) => {

                        const img = new Image();

                        img.onload = () => resolve(img);

                        img.onerror = () => reject(new Error('Erreur chargement image pour ajustement.'));

                        img.src = e.target.result;

                    };

                    reader.onerror = () => reject(new Error('Erreur lecture fichier image.'));

                    reader.readAsDataURL(file);

                } else if (file.type === 'application/pdf') {

                    const fileReader = new FileReader();

                    fileReader.onload = function() {

                        const typedarray = new Uint8Array(this.result);

                        pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                            pdfDoc_.getPage(1).then(page => {

                                const viewport = page.getViewport({ scale: 2.0 });

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.height = viewport.height;

                                tempCanvas.width = viewport.width;

                                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                                    const img = new Image();

                                    img.onload = () => resolve(img);

                                    img.onerror = () => reject(new Error('Erreur conversion 1ère page PDF en image.'));

                                    img.src = tempCanvas.toDataURL('image/png');

                                }).catch(reject);

                            }).catch(reject);

                        }).catch(reject);

                    };

                    fileReader.onerror = () => reject(new Error('Erreur lecture fichier PDF.'));

                    fileReader.readAsArrayBuffer(file);

                } else {

                    reject(new Error('Type de fichier non supporté pour l\'ajustement.'));

                }

            });

        }


        initiateProcessingButton.addEventListener('click', async () => {

            if (allUploadedFiles.length === 0 || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Sélectionnez zone ET téléversez au moins un fichier.', 'error');

                return;

            }

            loader.style.display = 'block';

            initiateProcessingButton.disabled = true;


            try {

                currentFileForAdjustment = await loadFileForAdjustment(allUploadedFiles[0]);

                setupInteractiveStage(currentFileForAdjustment);

                initiateProcessingButton.classList.add('hidden');

                applyAndDownloadButton.classList.remove('hidden');

                imageCanvas.style.display = 'block';

                showMessage('Ajustez le rectangle sur ce 1er fichier, puis validez.', 'info');

            } catch (error) {

                showMessage(error.message || 'Erreur chargement du 1er fichier.', 'error');

                console.error("Error loading first file for adjustment:", error);

            } finally {

                loader.style.display = 'none';

                initiateProcessingButton.disabled = false;

            }

        });


        function setupInteractiveStage(imageToAdjust) {

            imageCanvas.width = imageToAdjust.width;

            imageCanvas.height = imageToAdjust.height;

            const imgW = imageToAdjust.width;

            const imgH = imageToAdjust.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles(imageToAdjust);

        }

        function drawCanvasWithSelectionAndHandles(imageToDraw, isFinal = false) {

            if (!imageToDraw) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(imageToDraw, 0, 0, imageCanvas.width, imageCanvas.height);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles();

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles();

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'];

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions();

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0 || !imageCanvas.offsetParent) return;

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect();


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                handle.style.left = `${canvasRect.left + positions[type].left - canvasContainer.getBoundingClientRect().left}px`;

                handle.style.top = `${canvasRect.top + positions[type].top - canvasContainer.getBoundingClientRect().top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) {

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }

        function getEventPosOnCanvas(event) {

            const rect = imageCanvas.getBoundingClientRect();

            let clientX, clientY;

            if (event.touches && event.touches.length > 0) {

                clientX = event.touches[0].clientX;

                clientY = event.touches[0].clientY;

            } else {

                clientX = event.clientX;

                clientY = event.clientY;

            }

            return {

                x: (clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        function handleInteractionStart(e) {

            if (!currentSelectionRect.isDefined || !currentFileForAdjustment) return;

            const target = e.target;

            const eventPos = getEventPosOnCanvas(e);


            if (target.classList.contains('handle')) {

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas &&

                       eventPos.x >= currentSelectionRect.x && eventPos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       eventPos.y >= currentSelectionRect.y && eventPos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return;

            }

            if (activeDragAction && e.cancelable) {

                 e.preventDefault();

            }

            dragStartCoords = eventPos;

            rectStartCoords = { ...currentSelectionRect };

        }

        function handleInteractionMove(e) {

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                if (e.type === 'mousemove' && (e.target === imageCanvas || e.target.classList.contains('handle'))) {

                    const mousePos = getEventPosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 1.5) {

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' &&

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            if (e.cancelable) {

                e.preventDefault();

            }


            const eventPos = getEventPosOnCanvas(e);

            const deltaX = eventPos.x - dragStartCoords.x;

            const deltaY = eventPos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords;


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            const minSize = 20;

            if (w < minSize) {

                if (activeDragAction.includes('l') || activeDragAction.includes('tl') || activeDragAction.includes('bl')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t') || activeDragAction.includes('tl') || activeDragAction.includes('tr')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);


            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles(currentFileForAdjustment);

        }

        function handleInteractionEnd(e) {

             if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default';

                updateHandlesPositions();

            }

        }


        canvasContainer.addEventListener('mousedown', handleInteractionStart);

        document.addEventListener('mousemove', handleInteractionMove);

        document.addEventListener('mouseup', handleInteractionEnd);

        canvasContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });

        document.addEventListener('touchmove', handleInteractionMove, { passive: false });

        document.addEventListener('touchend', handleInteractionEnd);

        applyAndDownloadButton.addEventListener('click', async () => {

            if (!currentSelectionRect.isDefined || allUploadedFiles.length === 0) {

                showMessage('Zone non définie ou aucun fichier chargé.', 'error');

                return;

            }

            loader.style.display = 'block';

            applyAndDownloadButton.disabled = true;

            let filesProcessedCount = 0;


            for (let i = 0; i < allUploadedFiles.length; i++) {

                const file = allUploadedFiles[i];

                const originalFilename = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;

                showMessage(`Traitement de ${file.name} (${i+1}/${allUploadedFiles.length})...`, 'info', 60000);


                try {

                    if (file.type.startsWith('image/')) {

                        const img = await loadFileForAdjustment(file);

                        const tempCanvas = document.createElement('canvas');

                        const tempCtx = tempCanvas.getContext('2d');

                        tempCanvas.width = img.width;

                        tempCanvas.height = img.height;

                        tempCtx.drawImage(img, 0, 0);

                        tempCtx.fillStyle = 'white';

                        tempCtx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                        const outputFormat = imageOutputFormatSelect.value;


                        if (outputFormat === 'application/pdf') {

                            const pdfOutput = new jsPDF({

                                orientation: img.width > img.height ? 'l' : 'p',

                                unit: 'px', // Using pixels as unit for direct mapping from image dimensions

                                format: [img.width, img.height]

                            });

                            const pageDataUrl = tempCanvas.toDataURL('image/png');

                            pdfOutput.addImage(pageDataUrl, 'PNG', 0, 0, img.width, img.height);

                            pdfOutput.save(`${originalFilename}_modifie.pdf`);

                        } else {

                            const extension = outputFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(outputFormat, outputFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_modifie.${extension}`);

                        }

                        filesProcessedCount++;


                    } else if (file.type === 'application/pdf') {

                        let pdfOutput; // Declare here, initialize on first page


                        const fileReader = new FileReader();

                        const arrayBuffer = await new Promise((resolve, reject) => {

                            fileReader.onload = () => resolve(fileReader.result);

                            fileReader.onerror = reject;

                            fileReader.readAsArrayBuffer(file);

                        });


                        const pdfDocInstance = await pdfjsLib.getDocument(new Uint8Array(arrayBuffer)).promise;

                        const numPages = pdfDocInstance.numPages;


                        for (let j = 1; j <= numPages; j++) {

                            const page = await pdfDocInstance.getPage(j);

                            const viewport = page.getViewport({ scale: 2.0 });

                            if (j === 1) {

                                // Initialize jsPDF for *this* PDF file using its first page's dimensions

                                pdfOutput = new jsPDF({

                                    orientation: viewport.width > viewport.height ? 'l' : 'p',

                                    unit: 'pt', // pdf.js viewport units are points. jsPDF will handle conversion if its internal unit is different.

                                    format: [viewport.width, viewport.height]

                                });

                            } else {

                                // Add subsequent pages with their own dimensions

                                pdfOutput.addPage([viewport.width, viewport.height], viewport.width > viewport.height ? 'l' : 'p');

                            }

                            const tempCanvas = document.createElement('canvas');

                            const tempCtx = tempCanvas.getContext('2d');

                            tempCanvas.width = viewport.width;  

                            tempCanvas.height = viewport.height;


                            await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                            tempCtx.fillStyle = 'white';

                            tempCtx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                            const pageDataUrl = tempCanvas.toDataURL('image/png');

                            pdfOutput.addImage(pageDataUrl, 'PNG', 0, 0, viewport.width, viewport.height);

                        }

                        if (pdfOutput) { // Ensure pdfOutput was initialized

                           pdfOutput.save(`${originalFilename}_modifie.pdf`);

                        } else if (numPages > 0) { // Should not happen if numPages > 0

                            console.error("pdfOutput was not initialized for PDF:", file.name);

                            showMessage(`Erreur interne lors de la création du PDF pour ${file.name}`, 'error');

                        }

                        filesProcessedCount++;

                    }

                    await new Promise(resolve => setTimeout(resolve, 200));

                } catch (err) {

                    console.error("Erreur traitement fichier:", file.name, err);

                    showMessage(`Erreur avec ${file.name}: ${err.message || 'Inconnue'}`, 'error');

                }

            }

            loader.style.display = 'none';

            applyAndDownloadButton.disabled = false;

            if (filesProcessedCount > 0) {

                showMessage(`${filesProcessedCount} fichier(s) traité(s) et téléchargé(s).`, 'success');

            } else if (allUploadedFiles.length > 0) {

                 showMessage(`Aucun fichier n'a pu être traité avec succès.`, 'error');

            }


            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

        });


        function triggerDownload(dataURL, filename) {

            const link = document.createElement('a');

            link.href = dataURL;

            link.download = filename;

            document.body.appendChild(link);

            link.click();

            document.body.removeChild(link);

        }


        function resetCanvasAndState(resetQuadrantSelection = true) {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            currentFileForAdjustment = null;

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');


            if (resetQuadrantSelection) {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

            }

        }


        checkInitiateButtonState();

        window.addEventListener('resize', () => {

            if (currentSelectionRect.isDefined && handles.length > 0 && imageCanvas.offsetParent) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>




<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>Suppresseur de Publicités pour Bordereaux (Batch)</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <!-- PDF.js library -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <!-- jsPDF library -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            overscroll-behavior-y: contain;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            touch-action: none;

        }

        .handle {

            position: absolute;

            width: 14px;

            height: 14px;

            background-color: rgba(255, 0, 0, 0.6);

            border: 1px solid rgba(255, 255, 255, 0.8);

            border-radius: 50%;

            z-index: 10;

            touch-action: none;

        }

        #loader {

            border: 5px solid #f3f3f3;

            border-top: 5px solid #3498db;

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

            margin: 10px auto;

            display: none;

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <h1 class="text-2xl md:text-3xl font-bold text-gray-800">Suppresseur de Publicités pour Bordereaux (Batch)</h1>

            <p class="text-gray-600 mt-2">Masquez les pubs sur plusieurs fichiers à la fois.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Zone initiale approximative :</h2>

                 <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez vos fichiers (Images ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="fileUpload" accept="image/*,application/pdf" multiple class="block w-full max-w-md text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-2 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                    <div class="my-2 text-sm text-gray-600">Format de sortie (si applicable) :

                        <select id="outputFormatSelect" class="ml-2 p-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500">

                            <option value="source">Comme source (PDF reste PDF, Image reste Image)</option>

                            <option value="image/png">PNG</option>

                            <option value="image/jpeg">JPEG</option>

                            <option value="application/pdf">PDF (convertir tout en PDF)</option>

                        </select>

                    </div>

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone sur le 1er fichier et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher 1er Fichier et Ajuster Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                </div>

                <div id="loader"></div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Appliquer à Tous et Télécharger

                </button>

            </section>

        </main>

    </div>


    <script>

        const { jsPDF } = window.jspdf;


        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const fileUpload = document.getElementById('fileUpload');

        const outputFormatSelect = document.getElementById('outputFormatSelect');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const loader = document.getElementById('loader');

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let allUploadedFiles = [];

        let currentFileForAdjustment = null;

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null;

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 14;

        let handles = [];


        // --- Constantes pour PDF A4 (en points, 1pt = 1/72 inch) ---

        const A4_WIDTH_PT = 595.28;

        const A4_HEIGHT_PT = 841.89;



        function showMessage(text, type = 'info', duration = 4000) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(allUploadedFiles.length > 0 && selectedQuadrantInfo.quadrantIndex !== null);

        }


        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, Q${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez des fichiers.`, 'info');

            });

        });


        fileUpload.addEventListener('change', (event) => {

            if (event.target.files.length === 0) {

                allUploadedFiles = [];

                currentFileForAdjustment = null;

                resetCanvasAndState(false);

                checkInitiateButtonState();

                return;

            }

            allUploadedFiles = Array.from(event.target.files);

            currentFileForAdjustment = null;

            resetCanvasAndState(false);


            if (allUploadedFiles.length > 0) {

                showMessage(`${allUploadedFiles.length} fichier(s) sélectionné(s). Prêt à ajuster le 1er.`, 'success');

            }

            checkInitiateButtonState();

        });

        async function loadFileForAdjustment(file) {

            return new Promise((resolve, reject) => {

                if (file.type.startsWith('image/')) {

                    const reader = new FileReader();

                    reader.onload = (e) => {

                        const img = new Image();

                        img.onload = () => resolve(img);

                        img.onerror = () => reject(new Error('Erreur chargement image pour ajustement.'));

                        img.src = e.target.result;

                    };

                    reader.onerror = () => reject(new Error('Erreur lecture fichier image.'));

                    reader.readAsDataURL(file);

                } else if (file.type === 'application/pdf') {

                    const fileReader = new FileReader();

                    fileReader.onload = function() {

                        const typedarray = new Uint8Array(this.result);

                        pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                            pdfDoc_.getPage(1).then(page => {

                                const viewport = page.getViewport({ scale: 2.0 }); // Scale for good quality preview

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.height = viewport.height;

                                tempCanvas.width = viewport.width;

                                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                                    const img = new Image();

                                    img.onload = () => resolve(img);

                                    img.onerror = () => reject(new Error('Erreur conversion 1ère page PDF en image.'));

                                    img.src = tempCanvas.toDataURL('image/png');

                                }).catch(reject);

                            }).catch(reject);

                        }).catch(reject);

                    };

                    fileReader.onerror = () => reject(new Error('Erreur lecture fichier PDF.'));

                    fileReader.readAsArrayBuffer(file);

                } else {

                    reject(new Error('Type de fichier non supporté pour l\'ajustement.'));

                }

            });

        }


        initiateProcessingButton.addEventListener('click', async () => {

            if (allUploadedFiles.length === 0 || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Sélectionnez zone ET téléversez au moins un fichier.', 'error');

                return;

            }

            loader.style.display = 'block';

            initiateProcessingButton.disabled = true;


            try {

                currentFileForAdjustment = await loadFileForAdjustment(allUploadedFiles[0]);

                setupInteractiveStage(currentFileForAdjustment);

                initiateProcessingButton.classList.add('hidden');

                applyAndDownloadButton.classList.remove('hidden');

                imageCanvas.style.display = 'block';

                showMessage('Ajustez le rectangle sur ce 1er fichier, puis validez.', 'info');

            } catch (error) {

                showMessage(error.message || 'Erreur chargement du 1er fichier.', 'error');

                console.error("Error loading first file for adjustment:", error);

            } finally {

                loader.style.display = 'none';

                initiateProcessingButton.disabled = false;

            }

        });


        function setupInteractiveStage(imageToAdjust) {

            imageCanvas.width = imageToAdjust.width;

            imageCanvas.height = imageToAdjust.height;

            const imgW = imageToAdjust.width;

            const imgH = imageToAdjust.height;

            // Scale the initial rectangle based on the preview image dimensions

            // The currentSelectionRect coordinates will be in the pixel space of this preview image.

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles(imageToAdjust);

        }

        function drawCanvasWithSelectionAndHandles(imageToDraw, isFinal = false) {

            if (!imageToDraw) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(imageToDraw, 0, 0, imageCanvas.width, imageCanvas.height);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles();

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles();

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'];

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions();

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0 || !imageCanvas.offsetParent) return;

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect();


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                handle.style.left = `${canvasRect.left + positions[type].left - canvasContainer.getBoundingClientRect().left}px`;

                handle.style.top = `${canvasRect.top + positions[type].top - canvasContainer.getBoundingClientRect().top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) {

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }

        function getEventPosOnCanvas(event) {

            const rect = imageCanvas.getBoundingClientRect();

            let clientX, clientY;

            if (event.touches && event.touches.length > 0) {

                clientX = event.touches[0].clientX;

                clientY = event.touches[0].clientY;

            } else {

                clientX = event.clientX;

                clientY = event.clientY;

            }

            return {

                x: (clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        function handleInteractionStart(e) {

            if (!currentSelectionRect.isDefined || !currentFileForAdjustment) return;

            const target = e.target;

            const eventPos = getEventPosOnCanvas(e);


            if (target.classList.contains('handle')) {

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas &&

                       eventPos.x >= currentSelectionRect.x && eventPos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       eventPos.y >= currentSelectionRect.y && eventPos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return;

            }

            if (activeDragAction && e.cancelable) {

                 e.preventDefault();

            }

            dragStartCoords = eventPos;

            rectStartCoords = { ...currentSelectionRect };

        }

        function handleInteractionMove(e) {

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                if (e.type === 'mousemove' && (e.target === imageCanvas || e.target.classList.contains('handle'))) {

                    const mousePos = getEventPosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 1.5) {

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' &&

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            if (e.cancelable) {

                e.preventDefault();

            }


            const eventPos = getEventPosOnCanvas(e);

            const deltaX = eventPos.x - dragStartCoords.x;

            const deltaY = eventPos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords;


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            const minSize = 20;

            if (w < minSize) {

                if (activeDragAction.includes('l') || activeDragAction.includes('tl') || activeDragAction.includes('bl')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t') || activeDragAction.includes('tl') || activeDragAction.includes('tr')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);


            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles(currentFileForAdjustment);

        }

        function handleInteractionEnd(e) {

             if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default';

                updateHandlesPositions();

            }

        }


        canvasContainer.addEventListener('mousedown', handleInteractionStart);

        document.addEventListener('mousemove', handleInteractionMove);

        document.addEventListener('mouseup', handleInteractionEnd);

        canvasContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });

        document.addEventListener('touchmove', handleInteractionMove, { passive: false });

        document.addEventListener('touchend', handleInteractionEnd);

        applyAndDownloadButton.addEventListener('click', async () => {

            if (!currentSelectionRect.isDefined || allUploadedFiles.length === 0) {

                showMessage('Zone non définie ou aucun fichier chargé.', 'error');

                return;

            }

            loader.style.display = 'block';

            applyAndDownloadButton.disabled = true;

            let filesProcessedCount = 0;

            const chosenOutputFormat = outputFormatSelect.value;


            for (let i = 0; i < allUploadedFiles.length; i++) {

                const file = allUploadedFiles[i];

                const originalFilename = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;

                showMessage(`Traitement de ${file.name} (${i+1}/${allUploadedFiles.length})...`, 'info', 60000);


                try {

                    // Determine target format based on original file type and user selection

                    let targetFormat = chosenOutputFormat;

                    if (chosenOutputFormat === 'source') {

                        targetFormat = file.type.startsWith('image/') ? 'image/png' : 'application/pdf'; // Default to PNG for images if "source"

                        if (file.type.startsWith('image/jpeg')) targetFormat = 'image/jpeg';

                    }

                    // --- Process IMAGE type input file ---

                    if (file.type.startsWith('image/')) {

                        const img = await loadFileForAdjustment(file);

                        const tempCanvas = document.createElement('canvas');

                        const tempCtx = tempCanvas.getContext('2d');

                        tempCanvas.width = img.width;

                        tempCanvas.height = img.height;

                        tempCtx.drawImage(img, 0, 0);

                        tempCtx.fillStyle = 'white';

                        // Apply selection rectangle. Coordinates are relative to the source image dimensions.

                        tempCtx.fillRect(currentSelectionRect.x * (img.width / currentFileForAdjustment.width),

                                         currentSelectionRect.y * (img.height / currentFileForAdjustment.height),

                                         currentSelectionRect.w * (img.width / currentFileForAdjustment.width),

                                         currentSelectionRect.h * (img.height / currentFileForAdjustment.height));

                        if (targetFormat === 'application/pdf') {

                            const pdfOutput = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });

                            const pageDataUrl = tempCanvas.toDataURL('image/png');

                            const targetWidthPt = img.width > img.height ? A4_HEIGHT_PT : A4_WIDTH_PT; // Use A4 landscape if image is landscape

                            const targetHeightPt = img.width > img.height ? A4_WIDTH_PT : A4_HEIGHT_PT;

                            if(img.width < img.height) { // Portrait image

                                pdfOutput.internal.pageSize.width = A4_WIDTH_PT;

                                pdfOutput.internal.pageSize.height = A4_HEIGHT_PT;

                            } else { // Landscape image

                                 pdfOutput.internal.pageSize.width = A4_HEIGHT_PT;

                                 pdfOutput.internal.pageSize.height = A4_WIDTH_PT;

                            }


                            const ratio = Math.min(targetWidthPt / img.width, targetHeightPt / img.height);

                            const scaledWidth = img.width * ratio;

                            const scaledHeight = img.height * ratio;

                            const xOffset = (targetWidthPt - scaledWidth) / 2;

                            const yOffset = (targetHeightPt - scaledHeight) / 2;


                            pdfOutput.addImage(pageDataUrl, 'PNG', xOffset, yOffset, scaledWidth, scaledHeight);

                            pdfOutput.save(`${originalFilename}_modifie.pdf`);

                        } else {

                            const extension = targetFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(targetFormat, targetFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_modifie.${extension}`);

                        }

                        filesProcessedCount++;


                    // --- Process PDF type input file ---

                    } else if (file.type === 'application/pdf') {

                        const fileReader = new FileReader();

                        const arrayBuffer = await new Promise((resolve, reject) => {

                            fileReader.onload = () => resolve(fileReader.result);

                            fileReader.onerror = reject;

                            fileReader.readAsArrayBuffer(file);

                        });

                        const pdfDocInstance = await pdfjsLib.getDocument(new Uint8Array(arrayBuffer)).promise;

                        const numPages = pdfDocInstance.numPages;


                        if (targetFormat === 'image/png' || targetFormat === 'image/jpeg') { // PDF to Single Image (first page)

                            const page = await pdfDocInstance.getPage(1);

                            const viewport = page.getViewport({ scale: 2.0 }); // Good scale for image quality

                            const tempCanvas = document.createElement('canvas');

                            const tempCtx = tempCanvas.getContext('2d');

                            tempCanvas.width = viewport.width;

                            tempCanvas.height = viewport.height;

                            await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                            tempCtx.fillStyle = 'white';

                            // Apply selection rectangle, scaling it from preview image space to current PDF page space

                            const scaleToPageX = viewport.width / currentFileForAdjustment.width;

                            const scaleToPageY = viewport.height / currentFileForAdjustment.height;

                            tempCtx.fillRect(currentSelectionRect.x * scaleToPageX,

                                             currentSelectionRect.y * scaleToPageY,

                                             currentSelectionRect.w * scaleToPageX,

                                             currentSelectionRect.h * scaleToPageY);


                            const extension = targetFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(targetFormat, targetFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_page1_modifie.${extension}`);


                        } else { // PDF to PDF (multi-page)

                            let pdfOutput;

                            for (let j = 1; j <= numPages; j++) {

                                const page = await pdfDocInstance.getPage(j);

                                const viewport = page.getViewport({ scale: 2.0 }); // Render at good resolution

                                const pageTargetWidthPt = viewport.width > viewport.height ? A4_HEIGHT_PT : A4_WIDTH_PT;

                                const pageTargetHeightPt = viewport.width > viewport.height ? A4_WIDTH_PT : A4_HEIGHT_PT;

                                let orientation = viewport.width > viewport.height ? 'l' : 'p';


                                if (j === 1) {

                                    pdfOutput = new jsPDF({ orientation: orientation, unit: 'pt', format: [pageTargetWidthPt, pageTargetHeightPt] });

                                } else {

                                    pdfOutput.addPage([pageTargetWidthPt, pageTargetHeightPt], orientation);

                                }

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.width = viewport.width;  

                                tempCanvas.height = viewport.height;

                                await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                                tempCtx.fillStyle = 'white';

                                // Scale selection rectangle from preview image space to current PDF page space

                                const scaleToPageX = viewport.width / currentFileForAdjustment.width;

                                const scaleToPageY = viewport.height / currentFileForAdjustment.height;

                                tempCtx.fillRect(currentSelectionRect.x * scaleToPageX,

                                                 currentSelectionRect.y * scaleToPageY,

                                                 currentSelectionRect.w * scaleToPageX,

                                                 currentSelectionRect.h * scaleToPageY);

                                const pageDataUrl = tempCanvas.toDataURL('image/png');

                                // Add image to fit the A4-like page

                                const ratio = Math.min(pageTargetWidthPt / viewport.width, pageTargetHeightPt / viewport.height);

                                const scaledWidth = viewport.width * ratio;

                                const scaledHeight = viewport.height * ratio;

                                const xOffset = (pageTargetWidthPt - scaledWidth) / 2;

                                const yOffset = (pageTargetHeightPt - scaledHeight) / 2;

                                pdfOutput.addImage(pageDataUrl, 'PNG', xOffset, yOffset, scaledWidth, scaledHeight);

                            }

                            if (pdfOutput) {

                               pdfOutput.save(`${originalFilename}_modifie.pdf`);

                            } else if (numPages > 0) {

                                console.error("pdfOutput was not initialized for PDF:", file.name);

                                showMessage(`Erreur interne lors de la création du PDF pour ${file.name}`, 'error');

                            }

                        }

                        filesProcessedCount++;

                    }

                    await new Promise(resolve => setTimeout(resolve, 200));

                } catch (err) {

                    console.error("Erreur traitement fichier:", file.name, err);

                    showMessage(`Erreur avec ${file.name}: ${err.message || 'Inconnue'}`, 'error');

                }

            }

            loader.style.display = 'none';

            applyAndDownloadButton.disabled = false;

            if (filesProcessedCount > 0) {

                showMessage(`${filesProcessedCount} fichier(s) traité(s) et téléchargé(s).`, 'success');

            } else if (allUploadedFiles.length > 0) {

                 showMessage(`Aucun fichier n'a pu être traité avec succès.`, 'error');

            }


            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

        });


        function triggerDownload(dataURL, filename) {

            const link = document.createElement('a');

            link.href = dataURL;

            link.download = filename;

            document.body.appendChild(link);

            link.click();

            document.body.removeChild(link);

        }


        function resetCanvasAndState(resetQuadrantSelection = true) {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            currentFileForAdjustment = null;

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');


            if (resetQuadrantSelection) {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

            }

        }


        checkInitiateButtonState();

        window.addEventListener('resize', () => {

            if (currentSelectionRect.isDefined && handles.length > 0 && imageCanvas.offsetParent) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>






<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>Suppresseur de Publicités pour Bordereaux (Batch)</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <!-- PDF.js library -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <!-- jsPDF library -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            overscroll-behavior-y: contain;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            touch-action: none;

        }

        .handle {

            position: absolute;

            width: 14px;

            height: 14px;

            background-color: rgba(255, 0, 0, 0.6);

            border: 1px solid rgba(255, 255, 255, 0.8);

            border-radius: 50%;

            z-index: 10;

            touch-action: none;

        }

        #loader {

            border: 5px solid #f3f3f3;

            border-top: 5px solid #3498db;

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

            margin: 10px auto;

            display: none;

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <div class="flex justify-between items-center">

                <h1 class="text-2xl md:text-3xl font-bold text-gray-800 flex-grow text-center">Suppresseur de Publicités</h1>

                <button id="resetAllButton" title="Réinitialiser tous les paramètres" class="p-2 text-gray-500 hover:text-blue-600 transition-colors">

                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw">

                        <path d="M3 2v6h6"/>

                        <path d="M3.31 15A9 9 0 0 0 20.7 7.5"/>

                        <path d="M21 22v-6h-6"/>

                        <path d="M20.7 9A9 9 0 0 0 3.31 16.5"/>

                    </svg>

                </button>

            </div>

            <p class="text-gray-600 mt-1">Masquez les pubs sur plusieurs fichiers à la fois.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Zone initiale approximative :</h2>

                 <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez vos fichiers (Images ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="fileUpload" accept="image/*,application/pdf" multiple class="block w-full max-w-md text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-2 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                    <div class="my-2 text-sm text-gray-600">Format de sortie (si applicable) :

                        <select id="outputFormatSelect" class="ml-2 p-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500">

                            <option value="source">Comme source (PDF reste PDF, Image reste Image)</option>

                            <option value="image/png">PNG</option>

                            <option value="image/jpeg">JPEG</option>

                            <option value="application/pdf">PDF (convertir tout en PDF)</option>

                        </select>

                    </div>

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone sur le 1er fichier et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher 1er Fichier et Ajuster Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                </div>

                <div id="loader"></div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Appliquer à Tous et Télécharger

                </button>

            </section>

        </main>

    </div>


    <script>

        const { jsPDF } = window.jspdf;


        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const fileUpload = document.getElementById('fileUpload');

        const outputFormatSelect = document.getElementById('outputFormatSelect');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const loader = document.getElementById('loader');

        const resetAllButton = document.getElementById('resetAllButton'); // Nouveau bouton

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let allUploadedFiles = [];

        let currentFileForAdjustment = null;

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null;

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 14;

        let handles = [];


        // --- Constantes pour PDF A4 (en points, 1pt = 1/72 inch) ---

        const A4_WIDTH_PT = 595.28;

        const A4_HEIGHT_PT = 841.89;



        function showMessage(text, type = 'info', duration = 4000) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(allUploadedFiles.length > 0 && selectedQuadrantInfo.quadrantIndex !== null);

        }


        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, Q${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez des fichiers.`, 'info');

            });

        });


        fileUpload.addEventListener('change', (event) => {

            if (event.target.files.length === 0) {

                allUploadedFiles = [];

                currentFileForAdjustment = null;

                resetCanvasAndState(false);

                checkInitiateButtonState();

                return;

            }

            allUploadedFiles = Array.from(event.target.files);

            currentFileForAdjustment = null;

            resetCanvasAndState(false);


            if (allUploadedFiles.length > 0) {

                showMessage(`${allUploadedFiles.length} fichier(s) sélectionné(s). Prêt à ajuster le 1er.`, 'success');

            }

            checkInitiateButtonState();

        });

        async function loadFileForAdjustment(file) {

            return new Promise((resolve, reject) => {

                if (file.type.startsWith('image/')) {

                    const reader = new FileReader();

                    reader.onload = (e) => {

                        const img = new Image();

                        img.onload = () => resolve(img);

                        img.onerror = () => reject(new Error('Erreur chargement image pour ajustement.'));

                        img.src = e.target.result;

                    };

                    reader.onerror = () => reject(new Error('Erreur lecture fichier image.'));

                    reader.readAsDataURL(file);

                } else if (file.type === 'application/pdf') {

                    const fileReader = new FileReader();

                    fileReader.onload = function() {

                        const typedarray = new Uint8Array(this.result);

                        pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                            pdfDoc_.getPage(1).then(page => {

                                const viewport = page.getViewport({ scale: 2.0 });

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.height = viewport.height;

                                tempCanvas.width = viewport.width;

                                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                                    const img = new Image();

                                    img.onload = () => resolve(img);

                                    img.onerror = () => reject(new Error('Erreur conversion 1ère page PDF en image.'));

                                    img.src = tempCanvas.toDataURL('image/png');

                                }).catch(reject);

                            }).catch(reject);

                        }).catch(reject);

                    };

                    fileReader.onerror = () => reject(new Error('Erreur lecture fichier PDF.'));

                    fileReader.readAsArrayBuffer(file);

                } else {

                    reject(new Error('Type de fichier non supporté pour l\'ajustement.'));

                }

            });

        }


        initiateProcessingButton.addEventListener('click', async () => {

            if (allUploadedFiles.length === 0 || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Sélectionnez zone ET téléversez au moins un fichier.', 'error');

                return;

            }

            loader.style.display = 'block';

            initiateProcessingButton.disabled = true;


            try {

                currentFileForAdjustment = await loadFileForAdjustment(allUploadedFiles[0]);

                setupInteractiveStage(currentFileForAdjustment);

                initiateProcessingButton.classList.add('hidden');

                applyAndDownloadButton.classList.remove('hidden');

                imageCanvas.style.display = 'block';

                showMessage('Ajustez le rectangle sur ce 1er fichier, puis validez.', 'info');

            } catch (error) {

                showMessage(error.message || 'Erreur chargement du 1er fichier.', 'error');

                console.error("Error loading first file for adjustment:", error);

            } finally {

                loader.style.display = 'none';

                initiateProcessingButton.disabled = false;

            }

        });


        function setupInteractiveStage(imageToAdjust) {

            imageCanvas.width = imageToAdjust.width;

            imageCanvas.height = imageToAdjust.height;

            const imgW = imageToAdjust.width;

            const imgH = imageToAdjust.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles(imageToAdjust);

        }

        function drawCanvasWithSelectionAndHandles(imageToDraw, isFinal = false) {

            if (!imageToDraw) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(imageToDraw, 0, 0, imageCanvas.width, imageCanvas.height);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles();

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles();

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'];

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions();

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0 || !imageCanvas.offsetParent) return;

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect();


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                handle.style.left = `${canvasRect.left + positions[type].left - canvasContainer.getBoundingClientRect().left}px`;

                handle.style.top = `${canvasRect.top + positions[type].top - canvasContainer.getBoundingClientRect().top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) {

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }

        function getEventPosOnCanvas(event) {

            const rect = imageCanvas.getBoundingClientRect();

            let clientX, clientY;

            if (event.touches && event.touches.length > 0) {

                clientX = event.touches[0].clientX;

                clientY = event.touches[0].clientY;

            } else {

                clientX = event.clientX;

                clientY = event.clientY;

            }

            return {

                x: (clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        function handleInteractionStart(e) {

            if (!currentSelectionRect.isDefined || !currentFileForAdjustment) return;

            const target = e.target;

            const eventPos = getEventPosOnCanvas(e);


            if (target.classList.contains('handle')) {

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas &&

                       eventPos.x >= currentSelectionRect.x && eventPos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       eventPos.y >= currentSelectionRect.y && eventPos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return;

            }

            if (activeDragAction && e.cancelable) {

                 e.preventDefault();

            }

            dragStartCoords = eventPos;

            rectStartCoords = { ...currentSelectionRect };

        }

        function handleInteractionMove(e) {

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                if (e.type === 'mousemove' && (e.target === imageCanvas || e.target.classList.contains('handle'))) {

                    const mousePos = getEventPosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 1.5) {

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' &&

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            if (e.cancelable) {

                e.preventDefault();

            }


            const eventPos = getEventPosOnCanvas(e);

            const deltaX = eventPos.x - dragStartCoords.x;

            const deltaY = eventPos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords;


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            const minSize = 20;

            if (w < minSize) {

                if (activeDragAction.includes('l') || activeDragAction.includes('tl') || activeDragAction.includes('bl')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t') || activeDragAction.includes('tl') || activeDragAction.includes('tr')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);


            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles(currentFileForAdjustment);

        }

        function handleInteractionEnd(e) {

             if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default';

                updateHandlesPositions();

            }

        }


        canvasContainer.addEventListener('mousedown', handleInteractionStart);

        document.addEventListener('mousemove', handleInteractionMove);

        document.addEventListener('mouseup', handleInteractionEnd);

        canvasContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });

        document.addEventListener('touchmove', handleInteractionMove, { passive: false });

        document.addEventListener('touchend', handleInteractionEnd);

        applyAndDownloadButton.addEventListener('click', async () => {

            if (!currentSelectionRect.isDefined || allUploadedFiles.length === 0) {

                showMessage('Zone non définie ou aucun fichier chargé.', 'error');

                return;

            }

            loader.style.display = 'block';

            applyAndDownloadButton.disabled = true;

            let filesProcessedCount = 0;

            const chosenOutputFormat = outputFormatSelect.value;


            for (let i = 0; i < allUploadedFiles.length; i++) {

                const file = allUploadedFiles[i];

                const originalFilename = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;

                showMessage(`Traitement de ${file.name} (${i+1}/${allUploadedFiles.length})...`, 'info', 60000);


                try {

                    let effectiveOutputFormat = chosenOutputFormat;

                    if (chosenOutputFormat === 'source') {

                        effectiveOutputFormat = file.type.startsWith('image/') ? (file.type === 'image/jpeg' ? 'image/jpeg' : 'image/png') : 'application/pdf';

                    }

                    if (file.type.startsWith('image/')) {

                        const img = await loadFileForAdjustment(file);

                        const tempCanvas = document.createElement('canvas');

                        const tempCtx = tempCanvas.getContext('2d');

                        tempCanvas.width = img.width;

                        tempCanvas.height = img.height;

                        tempCtx.drawImage(img, 0, 0);

                        tempCtx.fillStyle = 'white';

                        const scaleX = img.width / currentFileForAdjustment.width;

                        const scaleY = img.height / currentFileForAdjustment.height;

                        tempCtx.fillRect(currentSelectionRect.x * scaleX,

                                         currentSelectionRect.y * scaleY,

                                         currentSelectionRect.w * scaleX,

                                         currentSelectionRect.h * scaleY);

                        if (effectiveOutputFormat === 'application/pdf') {

                            const pdfOutput = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });

                            const pageDataUrl = tempCanvas.toDataURL('image/png');

                            let pageOrientation = img.width > img.height ? 'l' : 'p';

                            let targetWidthPt = pageOrientation === 'l' ? A4_HEIGHT_PT : A4_WIDTH_PT;

                            let targetHeightPt = pageOrientation === 'l' ? A4_WIDTH_PT : A4_HEIGHT_PT;

                            pdfOutput.internal.pageSize.width = targetWidthPt;

                            pdfOutput.internal.pageSize.height = targetHeightPt;


                            const ratio = Math.min(targetWidthPt / img.width, targetHeightPt / img.height);

                            const scaledWidth = img.width * ratio;

                            const scaledHeight = img.height * ratio;

                            const xOffset = (targetWidthPt - scaledWidth) / 2;

                            const yOffset = (targetHeightPt - scaledHeight) / 2;


                            pdfOutput.addImage(pageDataUrl, 'PNG', xOffset, yOffset, scaledWidth, scaledHeight);

                            pdfOutput.save(`${originalFilename}_modifie.pdf`);

                        } else {

                            const extension = effectiveOutputFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(effectiveOutputFormat, effectiveOutputFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_modifie.${extension}`);

                        }

                        filesProcessedCount++;


                    } else if (file.type === 'application/pdf') {

                        const fileReader = new FileReader();

                        const arrayBuffer = await new Promise((resolve, reject) => {

                            fileReader.onload = () => resolve(fileReader.result);

                            fileReader.onerror = reject;

                            fileReader.readAsArrayBuffer(file);

                        });

                        const pdfDocInstance = await pdfjsLib.getDocument(new Uint8Array(arrayBuffer)).promise;

                        const numPages = pdfDocInstance.numPages;


                        if (effectiveOutputFormat === 'image/png' || effectiveOutputFormat === 'image/jpeg') {

                            const page = await pdfDocInstance.getPage(1); // Only first page for image conversion

                            const viewport = page.getViewport({ scale: 2.0 });

                            const tempCanvas = document.createElement('canvas');

                            const tempCtx = tempCanvas.getContext('2d');

                            tempCanvas.width = viewport.width;

                            tempCanvas.height = viewport.height;

                            await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                            tempCtx.fillStyle = 'white';

                            const scaleToPageX = viewport.width / currentFileForAdjustment.width;

                            const scaleToPageY = viewport.height / currentFileForAdjustment.height;

                            tempCtx.fillRect(currentSelectionRect.x * scaleToPageX,

                                             currentSelectionRect.y * scaleToPageY,

                                             currentSelectionRect.w * scaleToPageX,

                                             currentSelectionRect.h * scaleToPageY);


                            const extension = effectiveOutputFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(effectiveOutputFormat, effectiveOutputFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_page1_modifie.${extension}`);

                        } else { // PDF to PDF

                            let pdfOutput;

                            for (let j = 1; j <= numPages; j++) {

                                const page = await pdfDocInstance.getPage(j);

                                const viewport = page.getViewport({ scale: 2.0 });

                                let pageOrientation = viewport.width > viewport.height ? 'l' : 'p';

                                let pageTargetWidthPt = pageOrientation === 'l' ? A4_HEIGHT_PT : A4_WIDTH_PT;

                                let pageTargetHeightPt = pageOrientation === 'l' ? A4_WIDTH_PT : A4_HEIGHT_PT;


                                if (j === 1) {

                                    pdfOutput = new jsPDF({ orientation: pageOrientation, unit: 'pt', format: [pageTargetWidthPt, pageTargetHeightPt] });

                                } else {

                                    pdfOutput.addPage([pageTargetWidthPt, pageTargetHeightPt], pageOrientation);

                                }

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.width = viewport.width;  

                                tempCanvas.height = viewport.height;

                                await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                                tempCtx.fillStyle = 'white';

                                const scaleToPageX = viewport.width / currentFileForAdjustment.width;

                                const scaleToPageY = viewport.height / currentFileForAdjustment.height;

                                tempCtx.fillRect(currentSelectionRect.x * scaleToPageX,

                                                 currentSelectionRect.y * scaleToPageY,

                                                 currentSelectionRect.w * scaleToPageX,

                                                 currentSelectionRect.h * scaleToPageY);

                                const pageDataUrl = tempCanvas.toDataURL('image/png');

                                const ratio = Math.min(pageTargetWidthPt / viewport.width, pageTargetHeightPt / viewport.height);

                                const scaledWidth = viewport.width * ratio;

                                const scaledHeight = viewport.height * ratio;

                                const xOffset = (pageTargetWidthPt - scaledWidth) / 2;

                                const yOffset = (pageTargetHeightPt - scaledHeight) / 2;

                                pdfOutput.addImage(pageDataUrl, 'PNG', xOffset, yOffset, scaledWidth, scaledHeight);

                            }

                            if (pdfOutput) {

                               pdfOutput.save(`${originalFilename}_modifie.pdf`);

                            } else if (numPages > 0) {

                                console.error("pdfOutput was not initialized for PDF:", file.name);

                                showMessage(`Erreur interne lors de la création du PDF pour ${file.name}`, 'error');

                            }

                        }

                        filesProcessedCount++;

                    }

                    await new Promise(resolve => setTimeout(resolve, 200));

                } catch (err) {

                    console.error("Erreur traitement fichier:", file.name, err);

                    showMessage(`Erreur avec ${file.name}: ${err.message || 'Inconnue'}`, 'error');

                }

            }

            loader.style.display = 'none';

            applyAndDownloadButton.disabled = false;

            if (filesProcessedCount > 0) {

                showMessage(`${filesProcessedCount} fichier(s) traité(s) et téléchargé(s).`, 'success');

            } else if (allUploadedFiles.length > 0) {

                 showMessage(`Aucun fichier n'a pu être traité avec succès.`, 'error');

            }


            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

        });


        function triggerDownload(dataURL, filename) {

            const link = document.createElement('a');

            link.href = dataURL;

            link.download = filename;

            document.body.appendChild(link);

            link.click();

            document.body.removeChild(link);

        }

        // --- Fonction de Réinitialisation Globale ---

        resetAllButton.addEventListener('click', () => {

            resetApplicationState();

            showMessage('Tous les paramètres ont été réinitialisés.', 'info');

        });


        function resetApplicationState() {

            // Réinitialiser la sélection de quadrant

            allQuadrants.forEach(q => q.classList.remove('selected'));

            selectedQuadrantInfo = { orientation: null, quadrantIndex: null };


            // Réinitialiser les fichiers téléversés

            allUploadedFiles = [];

            fileUpload.value = ''; // Effacer la sélection de l'input file


            // Réinitialiser le canvas et l'état associé

            resetCanvasAndState(true); // true pour s'assurer que tout est nettoyé


            // Réinitialiser le sélecteur de format de sortie à sa valeur par défaut

            outputFormatSelect.value = 'source';


            // S'assurer que les boutons sont dans leur état initial

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            loader.style.display = 'none';


            // Cacher les messages

            messageBox.style.display = 'none';

        }



        function resetCanvasAndState(resetQuadrantSelectionInternal = true) { // Renommé pour éviter conflit

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            currentFileForAdjustment = null;

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            // La désactivation/masquage des boutons est gérée par resetApplicationState ou le flux normal

            if (resetQuadrantSelectionInternal) { // Changé pour utiliser le paramètre interne

                allQuadrants.forEach(q => q.classList.remove('selected'));

                selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

            }

             // S'assurer que le bouton d'initiation est correctement (dés)activé

            checkInitiateButtonState();

        }


        checkInitiateButtonState(); // Appel initial

        window.addEventListener('resize', () => {

            if (currentSelectionRect.isDefined && handles.length > 0 && imageCanvas.offsetParent) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>






<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

    <title>Suppresseur de Publicités pour Bordereaux (Batch)</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <!-- PDF.js library -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>

    <script>

        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

    </script>

    <!-- jsPDF library -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            overscroll-behavior-y: contain;

        }

        .a4-paper {

            border: 2px solid #000;

            display: grid;

            grid-template-columns: repeat(2, 1fr);

            grid-template-rows: repeat(2, 1fr);

            cursor: pointer;

            background-color: #f0f0f0;

        }

        .quadrant {

            border: 1px dashed #666;

            display: flex;

            justify-content: center;

            align-items: center;

            transition: background-color 0.2s ease-in-out;

        }

        .quadrant:hover {

            background-color: #e0e0e0;

        }

        .quadrant.selected {

            background-color: #ffffff !important;

            border: 2px solid #3b82f6;

        }

        #message-box {

            position: fixed;

            top: 20px;

            left: 50%;

            transform: translateX(-50%);

            padding: 10px 20px;

            border-radius: 8px;

            color: white;

            z-index: 1000;

            display: none;

            box-shadow: 0 4px 6px rgba(0,0,0,0.1);

        }

        #message-box.success { background-color: #28a745; }

        #message-box.error { background-color: #dc3545; }

        #message-box.info { background-color: #17a2b8; }


        .quadrant-icon { font-size: 10px; color: #888; }

        .portrait { width: 100px; height: 141.4px; }

        .landscape { width: 141.4px; height: 100px; }


        #imageCanvas {

            touch-action: none;

        }

        .handle {

            position: absolute;

            width: 14px;

            height: 14px;

            background-color: rgba(255, 0, 0, 0.6);

            border: 1px solid rgba(255, 255, 255, 0.8);

            border-radius: 50%;

            z-index: 10;

            touch-action: none;

        }

        #loader {

            border: 5px solid #f3f3f3;

            border-top: 5px solid #3498db;

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

            margin: 10px auto;

            display: none;

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

    </style>

</head>

<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4">


    <div id="message-box"></div>


    <div class="bg-white p-6 md:p-8 rounded-xl shadow-2xl w-full max-w-3xl">

        <header class="mb-6 text-center">

            <div class="flex justify-between items-center">

                <h1 class="text-2xl md:text-3xl font-bold text-gray-800 flex-grow text-center">Suppresseur de Publicités</h1>

                <button id="resetAllButtonTop" title="Réinitialiser tous les paramètres" class="p-2 text-gray-500 hover:text-blue-600 transition-colors">

                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw">

                        <path d="M3 2v6h6"/>

                        <path d="M3.31 15A9 9 0 0 0 20.7 7.5"/>

                        <path d="M21 22v-6h-6"/>

                        <path d="M20.7 9A9 9 0 0 0 3.31 16.5"/>

                    </svg>

                </button>

            </div>

            <p class="text-gray-600 mt-1">Masquez les pubs sur plusieurs fichiers à la fois.</p>

        </header>


        <main>

            <section id="step1Selection" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">1. Zone initiale approximative :</h2>

                 <div class="flex flex-col sm:flex-row justify-center items-center gap-6 sm:gap-10">

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Portrait</p>

                        <div id="portrait-paper" class="a4-paper portrait rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="portrait" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="portrait" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                    <div>

                        <p class="text-center text-sm font-medium text-gray-600 mb-1">Paysage</p>

                        <div id="landscape-paper" class="a4-paper landscape rounded-md overflow-hidden mx-auto">

                            <div class="quadrant" data-orientation="landscape" data-quadrant="0"><span class="quadrant-icon">HG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="1"><span class="quadrant-icon">HD</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="2"><span class="quadrant-icon">BG</span></div>

                            <div class="quadrant" data-orientation="landscape" data-quadrant="3"><span class="quadrant-icon">BD</span></div>

                        </div>

                    </div>

                </div>

            </section>


            <section id="step2Upload" class="mb-6">

                <h2 class="text-xl font-semibold text-gray-700 mb-3 text-center">2. Téléversez vos fichiers (Images ou PDF) :</h2>

                <div class="flex flex-col items-center">

                    <input type="file" id="fileUpload" accept="image/*,application/pdf" multiple class="block w-full max-w-md text-sm text-gray-500

                        file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold

                        file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100

                        mb-2 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">

                    <div class="my-2 text-sm text-gray-600">Format de sortie (si applicable) :

                        <select id="outputFormatSelect" class="ml-2 p-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-1 focus:ring-blue-500">

                            <option value="source">Comme source (PDF reste PDF, Image reste Image)</option>

                            <option value="image/png">PNG</option>

                            <option value="image/jpeg">JPEG</option>

                            <option value="application/pdf">PDF (convertir tout en PDF)</option>

                        </select>

                    </div>

                </div>

            </section>

            <section id="step3Adjust" class="mb-6 text-center">

                 <h2 class="text-xl font-semibold text-gray-700 mb-3">3. Ajustez la zone sur le 1er fichier et validez :</h2>

                <button id="initiateProcessingButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out disabled:opacity-50" disabled>

                    Afficher 1er Fichier et Ajuster Zone

                </button>

                <div id="canvasContainer" class="relative flex flex-col items-center mt-4">

                    <canvas id="imageCanvas" class="border border-gray-400 rounded-lg shadow-md max-w-full h-auto" style="display: none;"></canvas>

                </div>

                <div id="loader"></div>

                <button id="applyAndDownloadButton" class="hidden mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow-md transition duration-150 ease-in-out">

                    Appliquer à Tous et Télécharger

                </button>

            </section>

        </main>

        <footer class="mt-8 pt-4 border-t border-gray-200 flex justify-center">

             <button id="resetAllButtonBottom" title="Réinitialiser tous les paramètres" class="p-2 text-gray-500 hover:text-blue-600 transition-colors">

                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw">

                    <path d="M3 2v6h6"/>

                    <path d="M3.31 15A9 9 0 0 0 20.7 7.5"/>

                    <path d="M21 22v-6h-6"/>

                    <path d="M20.7 9A9 9 0 0 0 3.31 16.5"/>

                </svg>

            </button>

        </footer>

    </div>


    <script>

        const { jsPDF } = window.jspdf;


        // Références DOM

        const allQuadrants = document.querySelectorAll('.quadrant');

        const fileUpload = document.getElementById('fileUpload');

        const outputFormatSelect = document.getElementById('outputFormatSelect');

        const initiateProcessingButton = document.getElementById('initiateProcessingButton');

        const imageCanvas = document.getElementById('imageCanvas');

        const canvasContainer = document.getElementById('canvasContainer');

        const applyAndDownloadButton = document.getElementById('applyAndDownloadButton');

        const messageBox = document.getElementById('message-box');

        const loader = document.getElementById('loader');

        const resetAllButtonTop = document.getElementById('resetAllButtonTop');

        const resetAllButtonBottom = document.getElementById('resetAllButtonBottom');

        const ctx = imageCanvas.getContext('2d');


        // État global

        let selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

        let allUploadedFiles = [];

        let currentFileForAdjustment = null;

        let currentSelectionRect = { x: 0, y: 0, w: 0, h: 0, isDefined: false };

        let activeDragAction = null;

        let dragStartCoords = { x: 0, y: 0 };

        let rectStartCoords = { x: 0, y: 0, w: 0, h: 0 };

        const HANDLE_SIZE = 14;

        let handles = [];


        // --- Constantes pour PDF A4 (en points, 1pt = 1/72 inch) ---

        const A4_WIDTH_PT = 595.28;

        const A4_HEIGHT_PT = 841.89;



        function showMessage(text, type = 'info', duration = 4000) {

            messageBox.textContent = text;

            messageBox.className = '';

            messageBox.classList.add(type);

            messageBox.style.display = 'block';

            setTimeout(() => { messageBox.style.display = 'none'; }, duration);

        }


        function checkInitiateButtonState() {

            initiateProcessingButton.disabled = !(allUploadedFiles.length > 0 && selectedQuadrantInfo.quadrantIndex !== null);

        }


        allQuadrants.forEach(quadrant => {

            quadrant.addEventListener('click', () => {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                quadrant.classList.add('selected');

                selectedQuadrantInfo.orientation = quadrant.dataset.orientation;

                selectedQuadrantInfo.quadrantIndex = parseInt(quadrant.dataset.quadrant);

                checkInitiateButtonState();

                showMessage(`Zone initiale: ${selectedQuadrantInfo.orientation}, Q${selectedQuadrantInfo.quadrantIndex + 1}. Téléversez des fichiers.`, 'info');

            });

        });


        fileUpload.addEventListener('change', (event) => {

            if (event.target.files.length === 0) {

                allUploadedFiles = [];

                currentFileForAdjustment = null;

                resetCanvasAndState(false);

                checkInitiateButtonState();

                return;

            }

            allUploadedFiles = Array.from(event.target.files);

            currentFileForAdjustment = null;

            resetCanvasAndState(false);


            if (allUploadedFiles.length > 0) {

                showMessage(`${allUploadedFiles.length} fichier(s) sélectionné(s). Prêt à ajuster le 1er.`, 'success');

            }

            checkInitiateButtonState();

        });

        async function loadFileForAdjustment(file) {

            return new Promise((resolve, reject) => {

                if (file.type.startsWith('image/')) {

                    const reader = new FileReader();

                    reader.onload = (e) => {

                        const img = new Image();

                        img.onload = () => resolve(img);

                        img.onerror = () => reject(new Error('Erreur chargement image pour ajustement.'));

                        img.src = e.target.result;

                    };

                    reader.onerror = () => reject(new Error('Erreur lecture fichier image.'));

                    reader.readAsDataURL(file);

                } else if (file.type === 'application/pdf') {

                    const fileReader = new FileReader();

                    fileReader.onload = function() {

                        const typedarray = new Uint8Array(this.result);

                        pdfjsLib.getDocument(typedarray).promise.then(pdfDoc_ => {

                            pdfDoc_.getPage(1).then(page => {

                                const viewport = page.getViewport({ scale: 2.0 });

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.height = viewport.height;

                                tempCanvas.width = viewport.width;

                                page.render({ canvasContext: tempCtx, viewport: viewport }).promise.then(() => {

                                    const img = new Image();

                                    img.onload = () => resolve(img);

                                    img.onerror = () => reject(new Error('Erreur conversion 1ère page PDF en image.'));

                                    img.src = tempCanvas.toDataURL('image/png');

                                }).catch(reject);

                            }).catch(reject);

                        }).catch(reject);

                    };

                    fileReader.onerror = () => reject(new Error('Erreur lecture fichier PDF.'));

                    fileReader.readAsArrayBuffer(file);

                } else {

                    reject(new Error('Type de fichier non supporté pour l\'ajustement.'));

                }

            });

        }


        initiateProcessingButton.addEventListener('click', async () => {

            if (allUploadedFiles.length === 0 || selectedQuadrantInfo.quadrantIndex === null) {

                showMessage('Sélectionnez zone ET téléversez au moins un fichier.', 'error');

                return;

            }

            loader.style.display = 'block';

            initiateProcessingButton.disabled = true;


            try {

                currentFileForAdjustment = await loadFileForAdjustment(allUploadedFiles[0]);

                setupInteractiveStage(currentFileForAdjustment);

                initiateProcessingButton.classList.add('hidden');

                applyAndDownloadButton.classList.remove('hidden');

                imageCanvas.style.display = 'block';

                showMessage('Ajustez le rectangle sur ce 1er fichier, puis validez.', 'info');

            } catch (error) {

                showMessage(error.message || 'Erreur chargement du 1er fichier.', 'error');

                console.error("Error loading first file for adjustment:", error);

            } finally {

                loader.style.display = 'none';

                initiateProcessingButton.disabled = false;

            }

        });


        function setupInteractiveStage(imageToAdjust) {

            imageCanvas.width = imageToAdjust.width;

            imageCanvas.height = imageToAdjust.height;

            const imgW = imageToAdjust.width;

            const imgH = imageToAdjust.height;

            currentSelectionRect.w = imgW / 2;

            currentSelectionRect.h = imgH / 2;


            switch (selectedQuadrantInfo.quadrantIndex) {

                case 0: currentSelectionRect.x = 0; currentSelectionRect.y = 0; break;

                case 1: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = 0; break;

                case 2: currentSelectionRect.x = 0; currentSelectionRect.y = imgH / 2; break;

                case 3: currentSelectionRect.x = imgW / 2; currentSelectionRect.y = imgH / 2; break;

            }

            currentSelectionRect.isDefined = true;

            createHandles();

            drawCanvasWithSelectionAndHandles(imageToAdjust);

        }

        function drawCanvasWithSelectionAndHandles(imageToDraw, isFinal = false) {

            if (!imageToDraw) return;

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            ctx.drawImage(imageToDraw, 0, 0, imageCanvas.width, imageCanvas.height);


            if (currentSelectionRect.isDefined) {

                if (isFinal) {

                    ctx.fillStyle = 'white';

                    ctx.fillRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    removeHandles();

                } else {

                    ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';

                    ctx.lineWidth = 2;

                    ctx.strokeRect(currentSelectionRect.x, currentSelectionRect.y, currentSelectionRect.w, currentSelectionRect.h);

                    updateHandlesPositions();

                }

            }

        }


        function createHandles() {

            removeHandles();

            const handleTypes = ['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'];

            handleTypes.forEach(type => {

                const handle = document.createElement('div');

                handle.classList.add('handle');

                handle.dataset.type = type;

                canvasContainer.appendChild(handle);

                handles.push(handle);

            });

            updateHandlesPositions();

        }


        function removeHandles() {

            handles.forEach(h => h.remove());

            handles = [];

        }


        function updateHandlesPositions() {

            if (!currentSelectionRect.isDefined || handles.length === 0 || !imageCanvas.offsetParent) return;

            const { x, y, w, h } = currentSelectionRect;

            const canvasRect = imageCanvas.getBoundingClientRect();


            const scaleX = canvasRect.width / imageCanvas.width;

            const scaleY = canvasRect.height / imageCanvas.height;


            const positions = {

                'tl': { left: x * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                't':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'tr': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: y * scaleY - HANDLE_SIZE / 2 },

                'l':  { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'r':  { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h / 2) * scaleY - HANDLE_SIZE / 2 },

                'bl': { left: x * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'b':  { left: (x + w / 2) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 },

                'br': { left: (x + w) * scaleX - HANDLE_SIZE / 2, top: (y + h) * scaleY - HANDLE_SIZE / 2 }

            };


            handles.forEach(handle => {

                const type = handle.dataset.type;

                handle.style.left = `${canvasRect.left + positions[type].left - canvasContainer.getBoundingClientRect().left}px`;

                handle.style.top = `${canvasRect.top + positions[type].top - canvasContainer.getBoundingClientRect().top}px`;

                handle.style.cursor = getResizeCursor(type);

                handle.style.display = 'block';

            });

        }

        function getResizeCursor(handleType) {

            switch (handleType) {

                case 'tl': case 'br': return 'nwse-resize';

                case 'tr': case 'bl': return 'nesw-resize';

                case 't':  case 'b':  return 'ns-resize';

                case 'l':  case 'r':  return 'ew-resize';

                default: return 'default';

            }

        }

        function getEventPosOnCanvas(event) {

            const rect = imageCanvas.getBoundingClientRect();

            let clientX, clientY;

            if (event.touches && event.touches.length > 0) {

                clientX = event.touches[0].clientX;

                clientY = event.touches[0].clientY;

            } else {

                clientX = event.clientX;

                clientY = event.clientY;

            }

            return {

                x: (clientX - rect.left) * (imageCanvas.width / rect.width),

                y: (clientY - rect.top) * (imageCanvas.height / rect.height)

            };

        }

        function handleInteractionStart(e) {

            if (!currentSelectionRect.isDefined || !currentFileForAdjustment) return;

            const target = e.target;

            const eventPos = getEventPosOnCanvas(e);


            if (target.classList.contains('handle')) {

                activeDragAction = `resize-${target.dataset.type}`;

            } else if (target === imageCanvas &&

                       eventPos.x >= currentSelectionRect.x && eventPos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                       eventPos.y >= currentSelectionRect.y && eventPos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                activeDragAction = 'move';

            } else {

                return;

            }

            if (activeDragAction && e.cancelable) {

                 e.preventDefault();

            }

            dragStartCoords = eventPos;

            rectStartCoords = { ...currentSelectionRect };

        }

        function handleInteractionMove(e) {

            if (!activeDragAction || !currentSelectionRect.isDefined) {

                if (e.type === 'mousemove' && (e.target === imageCanvas || e.target.classList.contains('handle'))) {

                    const mousePos = getEventPosOnCanvas(e);

                    let cursor = 'default';

                    if (currentSelectionRect.isDefined) {

                        for (const handle of handles) {

                            const handleRect = handle.getBoundingClientRect();

                            const canvasRect = imageCanvas.getBoundingClientRect();

                            const handleCanvasX = (handleRect.left - canvasRect.left + HANDLE_SIZE/2) * (imageCanvas.width / canvasRect.width);

                            const handleCanvasY = (handleRect.top - canvasRect.top + HANDLE_SIZE/2) * (imageCanvas.height / canvasRect.height);

                            if (Math.abs(mousePos.x - handleCanvasX) < HANDLE_SIZE && Math.abs(mousePos.y - handleCanvasY) < HANDLE_SIZE * 1.5) {

                                cursor = getResizeCursor(handle.dataset.type);

                                break;

                            }

                        }

                        if (cursor === 'default' &&

                            mousePos.x >= currentSelectionRect.x && mousePos.x <= currentSelectionRect.x + currentSelectionRect.w &&

                            mousePos.y >= currentSelectionRect.y && mousePos.y <= currentSelectionRect.y + currentSelectionRect.h) {

                            cursor = 'move';

                        }

                    }

                    imageCanvas.style.cursor = cursor;

                }

                return;

            }


            if (e.cancelable) {

                e.preventDefault();

            }


            const eventPos = getEventPosOnCanvas(e);

            const deltaX = eventPos.x - dragStartCoords.x;

            const deltaY = eventPos.y - dragStartCoords.y;

            let { x, y, w, h } = rectStartCoords;


            if (activeDragAction === 'move') {

                x += deltaX;

                y += deltaY;

            } else if (activeDragAction.startsWith('resize-')) {

                const type = activeDragAction.split('-')[1];

                if (type.includes('l')) { x += deltaX; w -= deltaX; }

                if (type.includes('r')) { w += deltaX; }

                if (type.includes('t')) { y += deltaY; h -= deltaY; }

                if (type.includes('b')) { h += deltaY; }

            }


            const minSize = 20;

            if (w < minSize) {

                if (activeDragAction.includes('l') || activeDragAction.includes('tl') || activeDragAction.includes('bl')) x = rectStartCoords.x + rectStartCoords.w - minSize;

                w = minSize;

            }

            if (h < minSize) {

                if (activeDragAction.includes('t') || activeDragAction.includes('tl') || activeDragAction.includes('tr')) y = rectStartCoords.y + rectStartCoords.h - minSize;

                h = minSize;

            }

            x = Math.max(0, Math.min(x, imageCanvas.width - w));

            y = Math.max(0, Math.min(y, imageCanvas.height - h));

            w = Math.min(w, imageCanvas.width - x);

            h = Math.min(h, imageCanvas.height - y);


            currentSelectionRect = { x, y, w, h, isDefined: true };

            drawCanvasWithSelectionAndHandles(currentFileForAdjustment);

        }

        function handleInteractionEnd(e) {

             if (activeDragAction) {

                activeDragAction = null;

                imageCanvas.style.cursor = 'default';

                updateHandlesPositions();

            }

        }


        canvasContainer.addEventListener('mousedown', handleInteractionStart);

        document.addEventListener('mousemove', handleInteractionMove);

        document.addEventListener('mouseup', handleInteractionEnd);

        canvasContainer.addEventListener('touchstart', handleInteractionStart, { passive: false });

        document.addEventListener('touchmove', handleInteractionMove, { passive: false });

        document.addEventListener('touchend', handleInteractionEnd);

        applyAndDownloadButton.addEventListener('click', async () => {

            if (!currentSelectionRect.isDefined || allUploadedFiles.length === 0) {

                showMessage('Zone non définie ou aucun fichier chargé.', 'error');

                return;

            }

            loader.style.display = 'block';

            applyAndDownloadButton.disabled = true;

            let filesProcessedCount = 0;

            const chosenOutputFormat = outputFormatSelect.value;


            for (let i = 0; i < allUploadedFiles.length; i++) {

                const file = allUploadedFiles[i];

                const originalFilename = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;

                showMessage(`Traitement de ${file.name} (${i+1}/${allUploadedFiles.length})...`, 'info', 60000);


                try {

                    let effectiveOutputFormat = chosenOutputFormat;

                    if (chosenOutputFormat === 'source') {

                        effectiveOutputFormat = file.type.startsWith('image/') ? (file.type === 'image/jpeg' ? 'image/jpeg' : 'image/png') : 'application/pdf';

                    }

                    if (file.type.startsWith('image/')) {

                        const img = await loadFileForAdjustment(file);

                        const tempCanvas = document.createElement('canvas');

                        const tempCtx = tempCanvas.getContext('2d');

                        tempCanvas.width = img.width;

                        tempCanvas.height = img.height;

                        tempCtx.drawImage(img, 0, 0);

                        tempCtx.fillStyle = 'white';

                        const scaleX = img.width / currentFileForAdjustment.width;

                        const scaleY = img.height / currentFileForAdjustment.height;

                        tempCtx.fillRect(currentSelectionRect.x * scaleX,

                                         currentSelectionRect.y * scaleY,

                                         currentSelectionRect.w * scaleX,

                                         currentSelectionRect.h * scaleY);

                        if (effectiveOutputFormat === 'application/pdf') {

                            const pdfOutput = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });

                            const pageDataUrl = tempCanvas.toDataURL('image/png');

                            let pageOrientation = img.width > img.height ? 'l' : 'p';

                            let targetWidthPt = pageOrientation === 'l' ? A4_HEIGHT_PT : A4_WIDTH_PT;

                            let targetHeightPt = pageOrientation === 'l' ? A4_WIDTH_PT : A4_HEIGHT_PT;

                            pdfOutput.internal.pageSize.width = targetWidthPt;

                            pdfOutput.internal.pageSize.height = targetHeightPt;


                            const ratio = Math.min(targetWidthPt / img.width, targetHeightPt / img.height);

                            const scaledWidth = img.width * ratio;

                            const scaledHeight = img.height * ratio;

                            const xOffset = (targetWidthPt - scaledWidth) / 2;

                            const yOffset = (targetHeightPt - scaledHeight) / 2;


                            pdfOutput.addImage(pageDataUrl, 'PNG', xOffset, yOffset, scaledWidth, scaledHeight);

                            pdfOutput.save(`${originalFilename}_modifie.pdf`);

                        } else {

                            const extension = effectiveOutputFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(effectiveOutputFormat, effectiveOutputFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_modifie.${extension}`);

                        }

                        filesProcessedCount++;


                    } else if (file.type === 'application/pdf') {

                        const fileReader = new FileReader();

                        const arrayBuffer = await new Promise((resolve, reject) => {

                            fileReader.onload = () => resolve(fileReader.result);

                            fileReader.onerror = reject;

                            fileReader.readAsArrayBuffer(file);

                        });

                        const pdfDocInstance = await pdfjsLib.getDocument(new Uint8Array(arrayBuffer)).promise;

                        const numPages = pdfDocInstance.numPages;


                        if (effectiveOutputFormat === 'image/png' || effectiveOutputFormat === 'image/jpeg') {

                            const page = await pdfDocInstance.getPage(1);

                            const viewport = page.getViewport({ scale: 2.0 });

                            const tempCanvas = document.createElement('canvas');

                            const tempCtx = tempCanvas.getContext('2d');

                            tempCanvas.width = viewport.width;

                            tempCanvas.height = viewport.height;

                            await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                            tempCtx.fillStyle = 'white';

                            const scaleToPageX = viewport.width / currentFileForAdjustment.width;

                            const scaleToPageY = viewport.height / currentFileForAdjustment.height;

                            tempCtx.fillRect(currentSelectionRect.x * scaleToPageX,

                                             currentSelectionRect.y * scaleToPageY,

                                             currentSelectionRect.w * scaleToPageX,

                                             currentSelectionRect.h * scaleToPageY);


                            const extension = effectiveOutputFormat === 'image/jpeg' ? 'jpg' : 'png';

                            const dataURL = tempCanvas.toDataURL(effectiveOutputFormat, effectiveOutputFormat === 'image/jpeg' ? 0.9 : undefined);

                            triggerDownload(dataURL, `${originalFilename}_page1_modifie.${extension}`);

                        } else { // PDF to PDF

                            let pdfOutput;

                            for (let j = 1; j <= numPages; j++) {

                                const page = await pdfDocInstance.getPage(j);

                                const viewport = page.getViewport({ scale: 2.0 });

                                let pageOrientation = viewport.width > viewport.height ? 'l' : 'p';

                                let pageTargetWidthPt = pageOrientation === 'l' ? A4_HEIGHT_PT : A4_WIDTH_PT;

                                let pageTargetHeightPt = pageOrientation === 'l' ? A4_WIDTH_PT : A4_HEIGHT_PT;


                                if (j === 1) {

                                    pdfOutput = new jsPDF({ orientation: pageOrientation, unit: 'pt', format: [pageTargetWidthPt, pageTargetHeightPt] });

                                } else {

                                    pdfOutput.addPage([pageTargetWidthPt, pageTargetHeightPt], pageOrientation);

                                }

                                const tempCanvas = document.createElement('canvas');

                                const tempCtx = tempCanvas.getContext('2d');

                                tempCanvas.width = viewport.width;  

                                tempCanvas.height = viewport.height;

                                await page.render({ canvasContext: tempCtx, viewport: viewport }).promise;

                                tempCtx.fillStyle = 'white';

                                const scaleToPageX = viewport.width / currentFileForAdjustment.width;

                                const scaleToPageY = viewport.height / currentFileForAdjustment.height;

                                tempCtx.fillRect(currentSelectionRect.x * scaleToPageX,

                                                 currentSelectionRect.y * scaleToPageY,

                                                 currentSelectionRect.w * scaleToPageX,

                                                 currentSelectionRect.h * scaleToPageY);

                                const pageDataUrl = tempCanvas.toDataURL('image/png');

                                const ratio = Math.min(pageTargetWidthPt / viewport.width, pageTargetHeightPt / viewport.height);

                                const scaledWidth = viewport.width * ratio;

                                const scaledHeight = viewport.height * ratio;

                                const xOffset = (pageTargetWidthPt - scaledWidth) / 2;

                                const yOffset = (pageTargetHeightPt - scaledHeight) / 2;

                                pdfOutput.addImage(pageDataUrl, 'PNG', xOffset, yOffset, scaledWidth, scaledHeight);

                            }

                            if (pdfOutput) {

                               pdfOutput.save(`${originalFilename}_modifie.pdf`);

                            } else if (numPages > 0) {

                                console.error("pdfOutput was not initialized for PDF:", file.name);

                                showMessage(`Erreur interne lors de la création du PDF pour ${file.name}`, 'error');

                            }

                        }

                        filesProcessedCount++;

                    }

                    await new Promise(resolve => setTimeout(resolve, 200));

                } catch (err) {

                    console.error("Erreur traitement fichier:", file.name, err);

                    showMessage(`Erreur avec ${file.name}: ${err.message || 'Inconnue'}`, 'error');

                }

            }

            loader.style.display = 'none';

            applyAndDownloadButton.disabled = false;

            if (filesProcessedCount > 0) {

                showMessage(`${filesProcessedCount} fichier(s) traité(s) et téléchargé(s).`, 'success');

            } else if (allUploadedFiles.length > 0) {

                 showMessage(`Aucun fichier n'a pu être traité avec succès.`, 'error');

            }


            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

        });


        function triggerDownload(dataURL, filename) {

            const link = document.createElement('a');

            link.href = dataURL;

            link.download = filename;

            document.body.appendChild(link);

            link.click();

            document.body.removeChild(link);

        }

        resetAllButtonTop.addEventListener('click', () => {

            resetApplicationState();

            showMessage('Tous les paramètres ont été réinitialisés.', 'info');

        });

        resetAllButtonBottom.addEventListener('click', () => {

            resetApplicationState();

            showMessage('Tous les paramètres ont été réinitialisés.', 'info');

        });



        function resetApplicationState() {

            allQuadrants.forEach(q => q.classList.remove('selected'));

            selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

            allUploadedFiles = [];

            fileUpload.value = '';

            resetCanvasAndState(true);

            outputFormatSelect.value = 'source';

            initiateProcessingButton.disabled = true;

            initiateProcessingButton.classList.remove('hidden');

            applyAndDownloadButton.classList.add('hidden');

            loader.style.display = 'none';

            messageBox.style.display = 'none';

        }



        function resetCanvasAndState(resetQuadrantSelectionInternal = true) {

            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);

            imageCanvas.style.display = 'none';

            removeHandles();

            currentFileForAdjustment = null;

            currentSelectionRect.isDefined = false;

            activeDragAction = null;

            if (resetQuadrantSelectionInternal) {

                allQuadrants.forEach(q => q.classList.remove('selected'));

                selectedQuadrantInfo = { orientation: null, quadrantIndex: null };

            }

            checkInitiateButtonState();

        }


        checkInitiateButtonState();

        window.addEventListener('resize', () => {

            if (currentSelectionRect.isDefined && handles.length > 0 && imageCanvas.offsetParent) {

                updateHandlesPositions();

            }

        });


    </script>

</body>

</html>







Code du convertisseur de Fichiers

J'espère qu'il vous plaira !

Télécharger le code

<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Convertisseur & Fusionneur PDF / Image</title>

    <!-- Tailwind CSS -->

    <script src="https://cdn.tailwindcss.com"></script>

    <!-- Bibliothèques externes pour la manipulation des fichiers -->

    <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" defer></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js" defer></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js" defer></script>

    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>

        body { font-family: 'Inter', sans-serif; }

        .step-indicator-item { transition: all 0.3s ease-in-out; }

        .step-indicator-item.active .step-circle {

            background-color: #4f46e5;

            color: white;

            border-color: #4f46e5;

            transform: scale(1.1);

        }

        .step-indicator-item.completed .step-circle {

            background-color: #16a34a;

            color: white;

            border-color: #16a34a;

        }

        .step-indicator-item .step-line {

            transition: background-color 0.3s ease-in-out;

        }

        .step-indicator-item.completed .step-line {

            background-color: #16a34a;

        }

        .loader {

            border: 4px solid #f3f3f3;

            border-top: 4px solid #4f46e5;

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

        .file-list-item {

            cursor: grab;

        }

        .file-list-item.dragging {

            opacity: 0.5;

            background: #eef2ff;

        }

    </style>

</head>

<body class="bg-gray-50 py-8 px-4">


    <div class="max-w-3xl mx-auto">

        <header class="text-center mb-8">

            <h1 class="text-4xl font-bold text-gray-800">Convertisseur de Fichiers</h1>

            <p class="text-gray-500 mt-2">Fusionnez et convertissez PDF, JPG, PNG simplement.</p>

        </header>


        <!-- Indicateur d'étapes -->

        <div id="step-indicator" class="mb-8 p-4">

            <div class="flex items-center justify-between">

                <!-- Étape 1 -->

                <div id="step-1" class="step-indicator-item flex flex-col items-center active">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">1</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Sélection</p>

                </div>

                <div class="step-line flex-1 h-1 mx-4 bg-gray-200"></div>

                <!-- Étape 2 -->

                <div id="step-2" class="step-indicator-item flex flex-col items-center">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">2</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Options</p>

                </div>

                <div class="step-line flex-1 h-1 mx-4 bg-gray-200"></div>

                <!-- Étape 3 -->

                <div id="step-3" class="step-indicator-item flex flex-col items-center">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">3</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Traitement</p>

                </div>

                <div class="step-line flex-1 h-1 mx-4 bg-gray-200"></div>

                <!-- Étape 4 -->

                <div id="step-4" class="step-indicator-item flex flex-col items-center">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">4</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Télécharger</p>

                </div>

            </div>

        </div>


        <main id="app-container" class="bg-white rounded-2xl shadow-lg p-6 md:p-8">

            <!-- Contenu des étapes sera injecté ici par JS -->

        </main>

    </div>


<script>

document.addEventListener('DOMContentLoaded', () => {


    // --- State Management ---

    let state = {

        selectedFiles: [],

        convertedFile: null,

        failedFiles: [],

        currentStep: 'upload',

        settings: {

            targetFormat: 'pdf',

            mergeFiles: true,

            standardizeA4: true,

        },

        dragActive: false,

        isProcessing: false,

        processingProgress: 0,

        processingMessage: ''

    };


    // --- DOM Elements ---

    const appContainer = document.getElementById('app-container');

    const stepIndicator = document.getElementById('step-indicator');


    // --- Templates HTML pour chaque étape ---

    const templates = {

        upload: `

            <div id="drop-zone" class="text-center">

                <div class="border-2 border-dashed rounded-2xl p-12 text-center transition-all duration-300 bg-gray-50 border-gray-300">

                    <div class="w-20 h-20 bg-gradient-to-br from-indigo-500 to-blue-600 rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg">

                        <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>

                    </div>

                    <h3 class="text-2xl font-bold text-gray-800 mb-2">Glissez et déposez vos fichiers ici</h3>

                    <p class="text-gray-500 mb-6">ou cliquez pour les sélectionner</p>

                    <button id="select-files-btn" class="px-6 py-3 bg-indigo-600 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700">Choisir des fichiers</button>

                    <input id="file-input" type="file" multiple accept=".pdf,.jpg,.jpeg,.png" class="hidden" />

                </div>

            </div>`,

        settings: `

            <div class="space-y-6">

                <div class="grid md:grid-cols-2 gap-6">

                    <div class="space-y-4">

                        <h3 class="text-xl font-bold text-gray-800">Fichiers (<span id="file-count">0</span>)</h3>

                        <div id="file-list" class="space-y-2 max-h-96 overflow-y-auto pr-2"></div>

                    </div>

                    <div class="space-y-4">

                        <h3 class="text-xl font-bold text-gray-800">Options de conversion</h3>

                        <div>

                            <label class="block text-sm font-medium text-gray-700 mb-2">Convertir en :</label>

                            <select id="target-format" class="w-full p-2 border border-gray-300 rounded-md">

                                <option value="pdf">PDF</option> <option value="jpg">JPG</option> <option value="png">PNG</option>

                            </select>

                        </div>

                        <div id="pdf-options" class="space-y-2 pt-2"></div>

                    </div>

                </div>

                <div class="flex justify-between items-center pt-4">

                    <button id="reset-btn" class="px-6 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300">Réinitialiser</button>

                    <button id="start-conversion-btn" class="px-6 py-3 bg-indigo-600 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700">Lancer la conversion</button>

                </div>

            </div>`,

        processing: `

            <div class="text-center py-12">

                <div class="w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6">

                     <div id="loader" class="loader"></div>

                </div>

                <h3 id="processing-title" class="text-2xl font-bold text-gray-800 mb-2">Traitement en cours...</h3>

                <p id="processing-message" class="text-gray-500 mb-4">Initialisation...</p>

                <div class="w-full bg-gray-200 rounded-full h-2.5">

                    <div id="progress-bar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%; transition: width 0.3s ease-in-out;"></div>

                </div>

            </div>`,

        results: `

            <div class="text-center py-12">

                 <div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">

                     <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>

                 </div>

                 <h3 class="text-2xl font-bold text-gray-800 mb-2">Conversion terminée !</h3>

                 <p id="result-message" class="text-gray-500 mb-6"></p>

                 <div id="failed-files-container" class="hidden mb-6 text-left bg-red-50 border border-red-200 rounded-lg p-4">

                    <h4 class="font-bold text-red-700">Certains fichiers n'ont pas pu être traités :</h4>

                    <ul id="failed-files-list" class="list-disc list-inside text-sm text-red-600 mt-2"></ul>

                 </div>

                 <div class="flex justify-center space-x-4">

                    <button id="reset-btn-results" class="px-6 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300">Recommencer</button>

                    <button id="download-btn" class="px-6 py-3 bg-green-600 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 flex items-center space-x-2">

                        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>

                         <span id="download-btn-text">Télécharger</span>

                    </button>

                 </div>

             </div>`

    };


    // --- Helper Functions ---

    const formatFileSize = (bytes) => {

        if (bytes === 0) return '0 B';

        const k = 1024;

        const sizes = ['B', 'KB', 'MB', 'GB'];

        const i = Math.floor(Math.log(bytes) / Math.log(k));

        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];

    };

   

    const triggerDownload = (blob, fileName) => {

        if (!blob || !fileName) {

            console.error("Informations de téléchargement manquantes.");

            return;

        }

        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');

        a.href = url;

        a.download = fileName;

        document.body.appendChild(a);

        a.click();

        document.body.removeChild(a);

        URL.revokeObjectURL(url);

    };


    // --- Core Functions ---

    const setState = (newState) => {

        const oldStep = state.currentStep;

        state = { ...state, ...newState };

        if (oldStep !== state.currentStep) {

            render();

        } else {

            updateDOM();

        }

    };


    const render = () => {

        appContainer.innerHTML = templates[state.currentStep];

        bindEventListeners();

        updateDOM();

    };


    const updateDOM = () => {

        const steps = ['upload', 'settings', 'processing', 'results'];

        const currentStepIndex = steps.indexOf(state.currentStep);

        for (let i = 0; i < steps.length; i++) {

            const stepEl = stepIndicator.querySelector(`#step-${i + 1}`);

            const lineEl = stepEl.nextElementSibling;

            stepEl.classList.toggle('active', i === currentStepIndex);

            stepEl.classList.toggle('completed', i < currentStepIndex);

            if (lineEl) {

                lineEl.classList.toggle('bg-green-500', i < currentStepIndex);

                lineEl.classList.toggle('bg-gray-200', i >= currentStepIndex);

            }

        }


        if (state.currentStep === 'settings') {

            document.getElementById('file-count').textContent = state.selectedFiles.length;

            document.getElementById('target-format').value = state.settings.targetFormat;

            renderFileList();

            renderPdfOptions();

        }

        if (state.currentStep === 'processing') {

            document.getElementById('progress-bar').style.width = `${state.processingProgress}%`;

            document.getElementById('processing-message').textContent = state.processingMessage;

            const loader = document.getElementById('loader');

            if(loader) loader.classList.toggle('animate-spin', state.isProcessing);

            document.getElementById('processing-title').textContent = state.isProcessing ? "Traitement en cours..." : "Terminé !";

        }

        if (state.currentStep === 'results') {

            const resultMsg = document.getElementById('result-message');

            const failedContainer = document.getElementById('failed-files-container');

            const failedList = document.getElementById('failed-files-list');

            const downloadBtn = document.getElementById('download-btn');


            if(state.failedFiles.length === 0) {

                 if (state.convertedFile && state.convertedFile.multiDownload) {

                    resultMsg.textContent = "Vos fichiers ont commencé à être téléchargés individuellement.";

                } else {

                    resultMsg.textContent = "Votre fichier est prêt à être téléchargé.";

                }

                failedContainer.classList.add('hidden');

            } else {

                resultMsg.textContent = `Le processus est terminé. ${state.selectedFiles.length - state.failedFiles.length} fichier(s) ont été traités avec succès.`;

                failedList.innerHTML = state.failedFiles.map(name => `<li>${name}</li>`).join('');

                failedContainer.classList.remove('hidden');

            }

           

            if(state.convertedFile && !state.convertedFile.multiDownload) {

                 downloadBtn.classList.remove('hidden');

                 document.getElementById('download-btn-text').textContent = `Télécharger ${state.convertedFile.fileName.includes('zip') ? 'l\'archive' : 'le fichier'}`;

            } else {

                 downloadBtn.classList.add('hidden');

            }

        }

    };

   

    // --- Specific Render Functions ---

    const renderFileList = () => {

        const fileListEl = document.getElementById('file-list');

        fileListEl.innerHTML = '';

        state.selectedFiles.forEach((file, index) => {

            const fileEl = document.createElement('div');

            fileEl.className = 'file-list-item flex items-center justify-between p-3 bg-gray-50 rounded-lg border';

            fileEl.setAttribute('draggable', 'true');

            fileEl.dataset.originalIndex = index;

            fileEl.innerHTML = `

                <div class="flex items-center space-x-3 overflow-hidden">

                     <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-gray-400 flex-shrink-0 cursor-grab"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg>

                    ${file.type === 'application/pdf' ? `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-red-600 flex-shrink-0"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>` : `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-blue-600 flex-shrink-0"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`}

                    <div class="overflow-hidden">

                        <p class="font-medium text-gray-800 truncate">${file.name}</p>

                        <p class="text-sm text-gray-500">${formatFileSize(file.size)}</p>

                    </div>

                </div>

                <button data-index="${index}" class="remove-file-btn p-1 text-gray-400 hover:text-red-600 hover:bg-red-100 rounded-full">

                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>

                </button>`;

            fileListEl.appendChild(fileEl);

        });

    };


    const renderPdfOptions = () => {

        const pdfOptionsEl = document.getElementById('pdf-options');

        if (state.selectedFiles.length > 1 && state.settings.targetFormat === 'pdf') {

            pdfOptionsEl.innerHTML = `

                <label class="flex items-center space-x-2">

                    <input type="checkbox" id="merge-files-checkbox" class="h-4 w-4 text-indigo-600 border-gray-300 rounded"/>

                    <span>Fusionner en un seul PDF</span>

                </label>

                <div id="a4-option-container" class="hidden pl-6">

                    <label class="flex items-center space-x-2">

                        <input type="checkbox" id="a4-checkbox" class="h-4 w-4 text-indigo-600 border-gray-300 rounded"/>

                        <span>Standardiser au format A4</span>

                    </label>

                </div>`;

            document.getElementById('merge-files-checkbox').checked = state.settings.mergeFiles;

            document.getElementById('a4-checkbox').checked = state.settings.standardizeA4;

            document.getElementById('a4-option-container').classList.toggle('hidden', !state.settings.mergeFiles);

        } else {

            pdfOptionsEl.innerHTML = '';

        }

    };


    // --- Event Handling ---

    let draggedItem = null;


    const getDragAfterElement = (container, y) => {

        const draggableElements = [...container.querySelectorAll('.file-list-item:not(.dragging)')];

        return draggableElements.reduce((closest, child) => {

            const box = child.getBoundingClientRect();

            const offset = y - box.top - box.height / 2;

            if (offset < 0 && offset > closest.offset) {

                return { offset: offset, element: child };

            } else {

                return closest;

            }

        }, { offset: Number.NEGATIVE_INFINITY }).element;

    };


    const bindEventListeners = () => {

        if (state.currentStep === 'upload') {

            const dropZone = document.getElementById('drop-zone');

            ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {

                dropZone.addEventListener(eventName, e => {

                    e.preventDefault();

                    e.stopPropagation();

                    const dropDiv = dropZone.querySelector('div');

                    if (eventName === 'dragenter' || eventName === 'dragover') {

                        dropDiv.classList.add('border-indigo-500', 'bg-indigo-50');

                    } else if (eventName === 'dragleave' || eventName === 'drop') {

                        dropDiv.classList.remove('border-indigo-500', 'bg-indigo-50');

                    }

                    if (eventName === 'drop') handleFileDrop(e);

                });

            });

        }

        if (state.currentStep === 'settings' && state.selectedFiles.length > 1) {

            const fileList = document.getElementById('file-list');

            fileList.addEventListener('dragstart', e => {

                if (e.target.classList.contains('file-list-item')) {

                    draggedItem = e.target;

                    setTimeout(() => draggedItem.classList.add('dragging'), 0);

                }

            });

            fileList.addEventListener('dragover', e => {

                e.preventDefault();

                const afterElement = getDragAfterElement(fileList, e.clientY);

                if (draggedItem) {

                    if (afterElement == null) {

                        fileList.appendChild(draggedItem);

                    } else {

                        fileList.insertBefore(draggedItem, afterElement);

                    }

                }

            });

            fileList.addEventListener('dragend', () => {

                if (draggedItem) {

                    draggedItem.classList.remove('dragging');

                    draggedItem = null;

                }

            });

            fileList.addEventListener('drop', () => {

                 if (!draggedItem) return;

                const newFileOrder = [];

                fileList.querySelectorAll('.file-list-item').forEach(item => {

                    const originalIndex = parseInt(item.dataset.originalIndex, 10);

                    newFileOrder.push(state.selectedFiles[originalIndex]);

                });

                setState({ selectedFiles: newFileOrder });

            });

        }

        appContainer.addEventListener('click', handleAppClick);

        appContainer.addEventListener('change', handleAppChange);

    };


    const handleAppClick = (e) => {

        const target = e.target;

        if (target.id === 'select-files-btn') document.getElementById('file-input').click();

        if (target.id === 'reset-btn' || target.id === 'reset-btn-results') resetApp();

        if (target.id === 'start-conversion-btn') startConversion();

        if (target.id === 'download-btn') triggerDownload(state.convertedFile.blob, state.convertedFile.fileName);

       

        const removeBtn = target.closest('.remove-file-btn');

        if (removeBtn) {

            removeFile(parseInt(removeBtn.dataset.index, 10));

        }

    };


    const handleAppChange = (e) => {

        const target = e.target;

        if (target.id === 'file-input') handleFileInput(e);

        if (['target-format', 'merge-files-checkbox', 'a4-checkbox'].includes(target.id)) {

            handleSettingsChange(e);

        }

    };


    const handleFileDrop = (e) => {

        const droppedFiles = Array.from(e.dataTransfer.files).filter(f => f.type === "application/pdf" || f.type.startsWith("image/"));

        if (droppedFiles.length > 0) addFiles(droppedFiles);

    };

   

    const handleFileInput = (e) => {

        const newFiles = Array.from(e.target.files).filter(f => f.type === "application/pdf" || f.type.startsWith("image/"));

        if (newFiles.length > 0) addFiles(newFiles);

        e.target.value = '';

    };


    const addFiles = (newFiles) => {

        const updatedFiles = [...state.selectedFiles, ...newFiles];

        setState({ selectedFiles: updatedFiles, currentStep: 'settings' });

    };

   

    const removeFile = (indexToRemove) => {

        const newFiles = state.selectedFiles.filter((_, i) => i !== indexToRemove);

        const nextStep = newFiles.length > 0 ? 'settings' : 'upload';

        setState({ selectedFiles: newFiles, currentStep: nextStep });

    };

   

    const handleSettingsChange = (e) => {

        const target = e.target;

        let newSettings = { ...state.settings };

        if (target.id === 'target-format') newSettings.targetFormat = target.value;

        if (target.id === 'merge-files-checkbox') newSettings.mergeFiles = target.checked;

        if (target.id === 'a4-checkbox') newSettings.standardizeA4 = target.checked;

        setState({ settings: newSettings });

    };


    const resetApp = () => {

        setState({

            selectedFiles: [],

            convertedFile: null,

            failedFiles: [],

            currentStep: 'upload',

            settings: { targetFormat: 'pdf', mergeFiles: true, standardizeA4: true },

        });

    };


    // --- Conversion Logic ---

    const startConversion = async () => {

        setState({ isProcessing: true, currentStep: 'processing', processingMessage: 'Initialisation...', failedFiles: [] });

        await new Promise(resolve => setTimeout(resolve, 50));


        try {

            if (!window.PDFLib || !window.pdfjsLib || !window.JSZip) {

                throw new Error("Bibliothèques externes non chargées. Actualisez la page.");

            }

             if (window.pdfjsLib.GlobalWorkerOptions.workerSrc === '') {

                 window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

            }

            const { targetFormat, mergeFiles } = state.settings;

            if (state.selectedFiles.length === 1 || (targetFormat === 'pdf' && mergeFiles)) {

                await processSingleOutput();

            } else {

                await processMultipleOutputs();

            }

            setState({ processingMessage: 'Conversion terminée !', processingProgress: 100 });

            setTimeout(() => setState({ currentStep: 'results', isProcessing: false }), 1000);

        } catch (error) {

            console.error('Erreur:', error);

            setState({ processingMessage: `Erreur: ${error.message}`, isProcessing: false });

        }

    };


    async function processSingleOutput() {

        const { PDFDocument } = window.PDFLib;

        const finalPdfDoc = await PDFDocument.create();

        const A4_WIDTH = 595.28, A4_HEIGHT = 841.89;

        let localFailedFiles = [];


        for (let i = 0; i < state.selectedFiles.length; i++) {

            const file = state.selectedFiles[i];

            setState({ processingProgress: Math.round(((i + 1) / state.selectedFiles.length) * 95), processingMessage: `Traitement de ${file.name}...` });

            try {

                const fileBytes = await file.arrayBuffer();

                const fileType = file.name.split('.').pop().toLowerCase();

                if (fileType === 'pdf') {

                    const pdfToMerge = await PDFDocument.load(fileBytes, { ignoreEncryption: true });

                    const indices = pdfToMerge.getPageIndices();

                    for (const index of indices) {

                        try {

                            const [embeddedPage] = await finalPdfDoc.embedPdf(pdfToMerge, [index]);

                            if (embeddedPage && embeddedPage.width > 0 && embeddedPage.height > 0) {

                               const newPage = finalPdfDoc.addPage(state.settings.standardizeA4 ? [A4_WIDTH, A4_HEIGHT] : [embeddedPage.width, embeddedPage.height]);

                                const ratio = Math.min(A4_WIDTH / embeddedPage.width, A4_HEIGHT / embeddedPage.height);

                                newPage.drawPage(embeddedPage, {

                                    x: state.settings.standardizeA4 ? (A4_WIDTH - embeddedPage.width * ratio) / 2 : 0,

                                    y: state.settings.standardizeA4 ? (A4_HEIGHT - embeddedPage.height * ratio) / 2 : 0,

                                    width: state.settings.standardizeA4 ? embeddedPage.width * ratio : embeddedPage.width,

                                    height: state.settings.standardizeA4 ? embeddedPage.height * ratio : embeddedPage.height,

                                });

                            }

                        } catch(pageError) {

                            console.error(`Erreur en traitant la page ${index + 1} du fichier ${file.name}. Page ignorée.`, pageError);

                        }

                    }

                } else {

                    const image = ['png'].includes(fileType) ? await finalPdfDoc.embedPng(fileBytes) : await finalPdfDoc.embedJpg(fileBytes);

                    const { width, height } = image.scale(1);

                    if (width > 0 && height > 0) {

                        const newPage = finalPdfDoc.addPage(state.settings.standardizeA4 ? [A4_WIDTH, A4_HEIGHT] : [width, height]);

                        const ratio = Math.min(A4_WIDTH / width, A4_HEIGHT / height);

                        newPage.drawImage(image, state.settings.standardizeA4 ? { x: (A4_WIDTH - width * ratio) / 2, y: (A4_HEIGHT - height * ratio) / 2, width: width * ratio, height: height * ratio } : {});

                    }

                }

            } catch (err) { console.error(err); localFailedFiles.push(file.name); }

        }

       

        if (finalPdfDoc.getPageCount() === 0) throw new Error("Aucun fichier valide n'a pu être traité.");

        const pdfBytes = await finalPdfDoc.save();

        setState({ failedFiles: localFailedFiles, convertedFile: { blob: new Blob([pdfBytes], { type: 'application/pdf' }), fileName: 'document_fusionne.pdf' } });

    }


    async function processMultipleOutputs() {

        const { PDFDocument } = window.PDFLib;

        const pdfjsLib = window.pdfjsLib;

        let localFailedFiles = [];

        let successCount = 0;


        for (let i = 0; i < state.selectedFiles.length; i++) {

            const file = state.selectedFiles[i];

            setState({ processingProgress: Math.round(((i + 1) / state.selectedFiles.length) * 95), processingMessage: `Conversion de ${file.name}...` });

            try {

                const fileBytes = await file.arrayBuffer();

                const fileType = file.name.split('.').pop().toLowerCase().replace('jpeg', 'jpg');

                const baseName = file.name.replace(/\.[^/.]+$/, "");

               

                if (state.settings.targetFormat === 'pdf') {

                    let data, fileName;

                    if (fileType === 'pdf') {

                        data = fileBytes;

                        fileName = file.name;

                    } else { // Image to PDF

                        const pdfDoc = await PDFDocument.create();

                        const image = (fileType === 'png') ? await pdfDoc.embedPng(fileBytes) : await pdfDoc.embedJpg(fileBytes);

                        if(image.width > 0 && image.height > 0) {

                            pdfDoc.addPage([image.width, image.height]).drawImage(image);

                            data = await pdfDoc.save();

                            fileName = `${baseName}.pdf`;

                        }

                    }

                    if(data) {

                        triggerDownload(new Blob([data], {type: 'application/pdf'}), fileName);

                        successCount++;

                    }

                } else { // Target is JPG or PNG

                    const mimeType = `image/${state.settings.targetFormat === 'jpg' ? 'jpeg' : 'png'}`;

                    if (fileType === 'pdf') {

                        const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(fileBytes) }).promise;

                        for (let p = 1; p <= pdf.numPages; p++) {

                            const page = await pdf.getPage(p);

                            const viewport = page.getViewport({ scale: 2.0 });

                            const canvas = document.createElement('canvas');

                            canvas.height = viewport.height; canvas.width = viewport.width;

                            await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;

                            const blob = await new Promise(resolve => canvas.toBlob(resolve, mimeType, 0.9));

                            triggerDownload(blob, `${baseName}_page_${p}.${state.settings.targetFormat}`);

                            successCount++;

                            await new Promise(r => setTimeout(r, 200));

                        }

                    } else { // Image to Image

                        let blob;

                        if (fileType === state.settings.targetFormat) {

                            blob = new Blob([fileBytes], {type: file.type});

                        } else {

                            const imgBlob = new Blob([fileBytes], { type: file.type });

                            const img = await new Promise(r => { let i = new Image(); i.onload=()=>r(i); i.src=URL.createObjectURL(imgBlob);});

                            URL.revokeObjectURL(img.src);

                            const canvas = document.createElement('canvas');

                            canvas.width = img.width; canvas.height = img.height;

                            const ctx = canvas.getContext('2d');

                            if (state.settings.targetFormat === 'jpg' && fileType === 'png') {

                                ctx.fillStyle = '#FFFFFF';

                                ctx.fillRect(0, 0, canvas.width, canvas.height);

                            }

                            ctx.drawImage(img, 0, 0);

                            blob = await new Promise(resolve => canvas.toBlob(resolve, mimeType, 0.9));

                        }

                        triggerDownload(blob, `${baseName}.${state.settings.targetFormat}`);

                        successCount++;

                    }

                }

                await new Promise(r => setTimeout(r, 300)); // Stagger downloads

            } catch (err) {

                console.error(err);

                localFailedFiles.push(file.name);

            }

        }

       

        if (successCount === 0 && localFailedFiles.length === state.selectedFiles.length) {

            throw new Error("Aucun fichier valide n'a pu être traité.");

        }

       

        setState({ failedFiles: localFailedFiles, convertedFile: { multiDownload: true } });

    }


    // --- Initial Render ---

    render();

});

</script>

</body>

</html>



<!DOCTYPE html>

<html lang="fr">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Convertisseur & Fusionneur PDF / Image</title>

    <!-- Tailwind CSS -->

    <script src="https://cdn.tailwindcss.com"></script>

    <!-- Bibliothèques externes pour la manipulation des fichiers -->

    <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" defer></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js" defer></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js" defer></script>

    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>

        body { font-family: 'Inter', sans-serif; }

        .step-indicator-item { transition: all 0.3s ease-in-out; }

        .step-indicator-item.active .step-circle {

            background-color: #4f46e5;

            color: white;

            border-color: #4f46e5;

            transform: scale(1.1);

        }

        .step-indicator-item.completed .step-circle {

            background-color: #16a34a;

            color: white;

            border-color: #16a34a;

        }

        .step-indicator-item .step-line {

            transition: background-color 0.3s ease-in-out;

        }

        .step-indicator-item.completed .step-line {

            background-color: #16a34a;

        }

        .loader {

            border: 4px solid #f3f3f3;

            border-top: 4px solid #4f46e5;

            border-radius: 50%;

            width: 40px;

            height: 40px;

            animation: spin 1s linear infinite;

        }

        @keyframes spin {

            0% { transform: rotate(0deg); }

            100% { transform: rotate(360deg); }

        }

        .file-list-item {

            cursor: grab;

        }

        .file-list-item.dragging {

            opacity: 0.5;

            background: #eef2ff;

        }

    </style>

</head>

<body class="bg-gray-50 py-8 px-4">


    <div class="max-w-3xl mx-auto">

        <header class="text-center mb-8">

            <h1 class="text-4xl font-bold text-gray-800">Convertisseur de Fichiers</h1>

            <p class="text-gray-500 mt-2">Fusionnez et convertissez PDF, JPG, PNG simplement.</p>

        </header>


        <!-- Indicateur d'étapes -->

        <div id="step-indicator" class="mb-8 p-4">

            <div class="flex items-center justify-between">

                <!-- Étape 1 -->

                <div id="step-1" class="step-indicator-item flex flex-col items-center active">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">1</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Sélection</p>

                </div>

                <div class="step-line flex-1 h-1 mx-4 bg-gray-200"></div>

                <!-- Étape 2 -->

                <div id="step-2" class="step-indicator-item flex flex-col items-center">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">2</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Options</p>

                </div>

                <div class="step-line flex-1 h-1 mx-4 bg-gray-200"></div>

                <!-- Étape 3 -->

                <div id="step-3" class="step-indicator-item flex flex-col items-center">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">3</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Traitement</p>

                </div>

                <div class="step-line flex-1 h-1 mx-4 bg-gray-200"></div>

                <!-- Étape 4 -->

                <div id="step-4" class="step-indicator-item flex flex-col items-center">

                    <div class="step-circle w-10 h-10 rounded-full flex items-center justify-center font-bold border-2 bg-white text-gray-400 border-gray-300">4</div>

                    <p class="mt-2 text-sm font-medium text-gray-500">Télécharger</p>

                </div>

            </div>

        </div>


        <main id="app-container" class="bg-white rounded-2xl shadow-lg p-6 md:p-8">

            <!-- Contenu des étapes sera injecté ici par JS -->

        </main>

    </div>


<script>

document.addEventListener('DOMContentLoaded', () => {


    // --- State Management ---

    let state = {

        selectedFiles: [],

        convertedFile: null,

        failedFiles: [],

        currentStep: 'upload',

        settings: {

            targetFormat: 'pdf',

            mergeFiles: true,

            standardizeA4: true,

        },

        dragActive: false,

        isProcessing: false,

        processingProgress: 0,

        processingMessage: ''

    };


    // --- DOM Elements ---

    const appContainer = document.getElementById('app-container');

    const stepIndicator = document.getElementById('step-indicator');


    // --- Templates HTML pour chaque étape ---

    const templates = {

        upload: `

            <div id="drop-zone" class="text-center">

                <div class="border-2 border-dashed rounded-2xl p-12 text-center transition-all duration-300 bg-gray-50 border-gray-300">

                    <div class="w-20 h-20 bg-gradient-to-br from-indigo-500 to-blue-600 rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg">

                        <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>

                    </div>

                    <h3 class="text-2xl font-bold text-gray-800 mb-2">Glissez et déposez vos fichiers ici</h3>

                    <p class="text-gray-500 mb-6">ou cliquez pour les sélectionner</p>

                    <button id="select-files-btn" class="px-6 py-3 bg-indigo-600 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700">Choisir des fichiers</button>

                    <input id="file-input" type="file" multiple accept=".pdf,.jpg,.jpeg,.png" class="hidden" />

                </div>

            </div>`,

        settings: `

            <div class="space-y-6">

                <div class="grid md:grid-cols-2 gap-6">

                    <div class="space-y-4">

                        <h3 class="text-xl font-bold text-gray-800">Fichiers (<span id="file-count">0</span>)</h3>

                        <div id="file-list" class="space-y-2 max-h-96 overflow-y-auto pr-2"></div>

                    </div>

                    <div class="space-y-4">

                        <h3 class="text-xl font-bold text-gray-800">Options de conversion</h3>

                        <div>

                            <label class="block text-sm font-medium text-gray-700 mb-2">Convertir en :</label>

                            <select id="target-format" class="w-full p-2 border border-gray-300 rounded-md">

                                <option value="pdf">PDF</option> <option value="jpg">JPG</option> <option value="png">PNG</option>

                            </select>

                        </div>

                        <div id="pdf-options" class="space-y-2 pt-2"></div>

                    </div>

                </div>

                <div class="flex justify-between items-center pt-4">

                    <button id="reset-btn" class="px-6 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300">Réinitialiser</button>

                    <button id="start-conversion-btn" class="px-6 py-3 bg-indigo-600 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700">Lancer la conversion</button>

                </div>

            </div>`,

        processing: `

            <div class="text-center py-12">

                <div class="w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6">

                     <div id="loader" class="loader"></div>

                </div>

                <h3 id="processing-title" class="text-2xl font-bold text-gray-800 mb-2">Traitement en cours...</h3>

                <p id="processing-message" class="text-gray-500 mb-4">Initialisation...</p>

                <div class="w-full bg-gray-200 rounded-full h-2.5">

                    <div id="progress-bar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%; transition: width 0.3s ease-in-out;"></div>

                </div>

            </div>`,

        results: `

            <div class="text-center py-12">

                 <div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">

                     <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>

                 </div>

                 <h3 class="text-2xl font-bold text-gray-800 mb-2">Conversion terminée !</h3>

                 <p id="result-message" class="text-gray-500 mb-6"></p>

                 <div id="failed-files-container" class="hidden mb-6 text-left bg-red-50 border border-red-200 rounded-lg p-4">

                    <h4 class="font-bold text-red-700">Certains fichiers n'ont pas pu être traités :</h4>

                    <ul id="failed-files-list" class="list-disc list-inside text-sm text-red-600 mt-2"></ul>

                 </div>

                 <div class="flex justify-center space-x-4">

                    <button id="reset-btn-results" class="px-6 py-2 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300">Recommencer</button>

                    <button id="download-btn" class="px-6 py-3 bg-green-600 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 flex items-center space-x-2">

                        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>

                         <span id="download-btn-text">Télécharger</span>

                    </button>

                 </div>

             </div>`

    };


    // --- Helper Functions ---

    const formatFileSize = (bytes) => {

        if (bytes === 0) return '0 B';

        const k = 1024;

        const sizes = ['B', 'KB', 'MB', 'GB'];

        const i = Math.floor(Math.log(bytes) / Math.log(k));

        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];

    };

    const triggerDownload = (blob, fileName) => {

        if (!blob || !fileName) {

            console.error("Informations de téléchargement manquantes.");

            return;

        }

        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');

        a.href = url;

        a.download = fileName;

        document.body.appendChild(a);

        a.click();

        document.body.removeChild(a);

        URL.revokeObjectURL(url);

    };


    // --- Core Functions ---

    const setState = (newState) => {

        const oldStep = state.currentStep;

        state = { ...state, ...newState };

        if (oldStep !== state.currentStep) {

            render();

        } else {

            updateDOM();

        }

    };


    const render = () => {

        appContainer.innerHTML = templates[state.currentStep];

        bindEventListeners();

        updateDOM();

    };


    const updateDOM = () => {

        const steps = ['upload', 'settings', 'processing', 'results'];

        const currentStepIndex = steps.indexOf(state.currentStep);

        for (let i = 0; i < steps.length; i++) {

            const stepEl = stepIndicator.querySelector(`#step-${i + 1}`);

            const lineEl = stepEl.nextElementSibling;

            stepEl.classList.toggle('active', i === currentStepIndex);

            stepEl.classList.toggle('completed', i < currentStepIndex);

            if (lineEl) {

                lineEl.classList.toggle('bg-green-500', i < currentStepIndex);

                lineEl.classList.toggle('bg-gray-200', i >= currentStepIndex);

            }

        }


        if (state.currentStep === 'settings') {

            document.getElementById('file-count').textContent = state.selectedFiles.length;

            document.getElementById('target-format').value = state.settings.targetFormat;

            renderFileList();

            renderPdfOptions();

        }

        if (state.currentStep === 'processing') {

            document.getElementById('progress-bar').style.width = `${state.processingProgress}%`;

            document.getElementById('processing-message').textContent = state.processingMessage;

            const loader = document.getElementById('loader');

            if(loader) loader.classList.toggle('animate-spin', state.isProcessing);

            document.getElementById('processing-title').textContent = state.isProcessing ? "Traitement en cours..." : "Terminé !";

        }

        if (state.currentStep === 'results') {

            const resultMsg = document.getElementById('result-message');

            const failedContainer = document.getElementById('failed-files-container');

            const failedList = document.getElementById('failed-files-list');

            const downloadBtn = document.getElementById('download-btn');


            if(state.failedFiles.length === 0) {

                 if (state.convertedFile && state.convertedFile.multiDownload) {

                    resultMsg.textContent = "Vos fichiers ont commencé à être téléchargés individuellement.";

                } else {

                    resultMsg.textContent = "Votre fichier est prêt à être téléchargé.";

                }

                failedContainer.classList.add('hidden');

            } else {

                resultMsg.textContent = `Le processus est terminé. ${state.selectedFiles.length - state.failedFiles.length} fichier(s) ont été traités avec succès.`;

                failedList.innerHTML = state.failedFiles.map(name => `<li>${name}</li>`).join('');

                failedContainer.classList.remove('hidden');

            }

            if(state.convertedFile && !state.convertedFile.multiDownload) {

                 downloadBtn.classList.remove('hidden');

                 document.getElementById('download-btn-text').textContent = `Télécharger ${state.convertedFile.fileName.includes('zip') ? 'l\'archive' : 'le fichier'}`;

            } else {

                 downloadBtn.classList.add('hidden');

            }

        }

    };

    // --- Specific Render Functions ---

    const renderFileList = () => {

        const fileListEl = document.getElementById('file-list');

        fileListEl.innerHTML = '';

        state.selectedFiles.forEach((file, index) => {

            const fileEl = document.createElement('div');

            fileEl.className = 'file-list-item flex items-center justify-between p-3 bg-gray-50 rounded-lg border';

            fileEl.setAttribute('draggable', 'true');

            fileEl.dataset.originalIndex = index;

            fileEl.innerHTML = `

                <div class="flex items-center space-x-3 overflow-hidden">

                     <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-gray-400 flex-shrink-0 cursor-grab"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg>

                    ${file.type === 'application/pdf' ? `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-red-600 flex-shrink-0"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>` : `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-blue-600 flex-shrink-0"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`}

                    <div class="overflow-hidden">

                        <p class="font-medium text-gray-800 truncate">${file.name}</p>

                        <p class="text-sm text-gray-500">${formatFileSize(file.size)}</p>

                    </div>

                </div>

                <button data-index="${index}" class="remove-file-btn p-1 text-gray-400 hover:text-red-600 hover:bg-red-100 rounded-full">

                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>

                </button>`;

            fileListEl.appendChild(fileEl);

        });

    };


    const renderPdfOptions = () => {

        const pdfOptionsEl = document.getElementById('pdf-options');

        if (state.selectedFiles.length > 1 && state.settings.targetFormat === 'pdf') {

            pdfOptionsEl.innerHTML = `

                <label class="flex items-center space-x-2">

                    <input type="checkbox" id="merge-files-checkbox" class="h-4 w-4 text-indigo-600 border-gray-300 rounded"/>

                    <span>Fusionner en un seul PDF</span>

                </label>

                <div id="a4-option-container" class="hidden pl-6">

                    <label class="flex items-center space-x-2">

                        <input type="checkbox" id="a4-checkbox" class="h-4 w-4 text-indigo-600 border-gray-300 rounded"/>

                        <span>Standardiser au format A4</span>

                    </label>

                </div>`;

            document.getElementById('merge-files-checkbox').checked = state.settings.mergeFiles;

            document.getElementById('a4-checkbox').checked = state.settings.standardizeA4;

            document.getElementById('a4-option-container').classList.toggle('hidden', !state.settings.mergeFiles);

        } else {

            pdfOptionsEl.innerHTML = '';

        }

    };


    // --- Event Handling ---

    let draggedItem = null;


    const getDragAfterElement = (container, y) => {

        const draggableElements = [...container.querySelectorAll('.file-list-item:not(.dragging)')];

        return draggableElements.reduce((closest, child) => {

            const box = child.getBoundingClientRect();

            const offset = y - box.top - box.height / 2;

            if (offset < 0 && offset > closest.offset) {

                return { offset: offset, element: child };

            } else {

                return closest;

            }

        }, { offset: Number.NEGATIVE_INFINITY }).element;

    };


    const bindEventListeners = () => {

        if (state.currentStep === 'upload') {

            const dropZone = document.getElementById('drop-zone');

            ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {

                dropZone.addEventListener(eventName, e => {

                    e.preventDefault();

                    e.stopPropagation();

                    const dropDiv = dropZone.querySelector('div');

                    if (eventName === 'dragenter' || eventName === 'dragover') {

                        dropDiv.classList.add('border-indigo-500', 'bg-indigo-50');

                    } else if (eventName === 'dragleave' || eventName === 'drop') {

                        dropDiv.classList.remove('border-indigo-500', 'bg-indigo-50');

                    }

                    if (eventName === 'drop') handleFileDrop(e);

                });

            });

        }

        if (state.currentStep === 'settings' && state.selectedFiles.length > 1) {

            const fileList = document.getElementById('file-list');

            fileList.addEventListener('dragstart', e => {

                if (e.target.classList.contains('file-list-item')) {

                    draggedItem = e.target;

                    setTimeout(() => draggedItem.classList.add('dragging'), 0);

                }

            });

            fileList.addEventListener('dragover', e => {

                e.preventDefault();

                const afterElement = getDragAfterElement(fileList, e.clientY);

                if (draggedItem) {

                    if (afterElement == null) {

                        fileList.appendChild(draggedItem);

                    } else {

                        fileList.insertBefore(draggedItem, afterElement);

                    }

                }

            });

            fileList.addEventListener('dragend', () => {

                if (draggedItem) {

                    draggedItem.classList.remove('dragging');

                    draggedItem = null;

                }

            });

            fileList.addEventListener('drop', () => {

                 if (!draggedItem) return;

                const newFileOrder = [];

                fileList.querySelectorAll('.file-list-item').forEach(item => {

                    const originalIndex = parseInt(item.dataset.originalIndex, 10);

                    newFileOrder.push(state.selectedFiles[originalIndex]);

                });

                setState({ selectedFiles: newFileOrder });

            });

        }

        appContainer.addEventListener('click', handleAppClick);

        appContainer.addEventListener('change', handleAppChange);

    };


    const handleAppClick = (e) => {

        const target = e.target;

        if (target.id === 'select-files-btn') document.getElementById('file-input').click();

        if (target.id === 'reset-btn' || target.id === 'reset-btn-results') resetApp();

        if (target.id === 'start-conversion-btn') startConversion();

        if (target.id === 'download-btn') triggerDownload(state.convertedFile.blob, state.convertedFile.fileName);

        const removeBtn = target.closest('.remove-file-btn');

        if (removeBtn) {

            removeFile(parseInt(removeBtn.dataset.index, 10));

        }

    };


    const handleAppChange = (e) => {

        const target = e.target;

        if (target.id === 'file-input') handleFileInput(e);

        if (['target-format', 'merge-files-checkbox', 'a4-checkbox'].includes(target.id)) {

            handleSettingsChange(e);

        }

    };


    const handleFileDrop = (e) => {

        const droppedFiles = Array.from(e.dataTransfer.files).filter(f => f.type === "application/pdf" || f.type.startsWith("image/"));

        if (droppedFiles.length > 0) addFiles(droppedFiles);

    };

    const handleFileInput = (e) => {

        const newFiles = Array.from(e.target.files).filter(f => f.type === "application/pdf" || f.type.startsWith("image/"));

        if (newFiles.length > 0) addFiles(newFiles);

        e.target.value = '';

    };


    const addFiles = (newFiles) => {

        const updatedFiles = [...state.selectedFiles, ...newFiles];

        setState({ selectedFiles: updatedFiles, currentStep: 'settings' });

    };

    const removeFile = (indexToRemove) => {

        const newFiles = state.selectedFiles.filter((_, i) => i !== indexToRemove);

        const nextStep = newFiles.length > 0 ? 'settings' : 'upload';

        setState({ selectedFiles: newFiles, currentStep: nextStep });

    };

    const handleSettingsChange = (e) => {

        const target = e.target;

        let newSettings = { ...state.settings };

        if (target.id === 'target-format') newSettings.targetFormat = target.value;

        if (target.id === 'merge-files-checkbox') newSettings.mergeFiles = target.checked;

        if (target.id === 'a4-checkbox') newSettings.standardizeA4 = target.checked;

        setState({ settings: newSettings });

    };


    const resetApp = () => {

        setState({

            selectedFiles: [],

            convertedFile: null,

            failedFiles: [],

            currentStep: 'upload',

            settings: { targetFormat: 'pdf', mergeFiles: true, standardizeA4: true },

        });

    };


    // --- Conversion Logic ---

    const startConversion = async () => {

        setState({ isProcessing: true, currentStep: 'processing', processingMessage: 'Initialisation...', failedFiles: [] });

        await new Promise(resolve => setTimeout(resolve, 50));


        try {

            if (!window.PDFLib || !window.pdfjsLib) {

                throw new Error("Bibliothèques externes non chargées. Actualisez la page.");

            }

             if (window.pdfjsLib.GlobalWorkerOptions.workerSrc === '') {

                 window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

            }

            const { targetFormat, mergeFiles } = state.settings;


            if (state.selectedFiles.length > 1 && targetFormat === 'pdf' && mergeFiles) {

                 await mergeAndConvert();

            } else {

                await batchConvertAndDownloadIndividually();

            }


            setState({ processingMessage: 'Conversion terminée !', processingProgress: 100 });

            setTimeout(() => setState({ currentStep: 'results', isProcessing: false }), 1000);

        } catch (error) {

            console.error('Erreur:', error);

            setState({ processingMessage: `Erreur: ${error.message}`, isProcessing: false });

        }

    };


    async function mergeAndConvert() {

        const { PDFDocument } = window.PDFLib;

        const finalPdfDoc = await PDFDocument.create();

        const A4_WIDTH = 595.28, A4_HEIGHT = 841.89;

        let localFailedFiles = [];


        for (let i = 0; i < state.selectedFiles.length; i++) {

            const file = state.selectedFiles[i];

            setState({ processingProgress: Math.round(((i + 1) / state.selectedFiles.length) * 95), processingMessage: `Traitement de ${file.name}...` });

            try {

                const fileBytes = await file.arrayBuffer();

                const fileType = file.name.split('.').pop().toLowerCase();

                if (fileType === 'pdf') {

                    const pdfToMerge = await PDFDocument.load(fileBytes, { ignoreEncryption: true });

                    const indices = pdfToMerge.getPageIndices();

                    for (const index of indices) {

                        try {

                            const [embeddedPage] = await finalPdfDoc.embedPdf(pdfToMerge, [index]);

                            if (embeddedPage && embeddedPage.width > 0 && embeddedPage.height > 0) {

                               const newPage = finalPdfDoc.addPage(state.settings.standardizeA4 ? [A4_WIDTH, A4_HEIGHT] : [embeddedPage.width, embeddedPage.height]);

                                const ratio = Math.min(A4_WIDTH / embeddedPage.width, A4_HEIGHT / embeddedPage.height);

                                newPage.drawPage(embeddedPage, {

                                    x: state.settings.standardizeA4 ? (A4_WIDTH - embeddedPage.width * ratio) / 2 : 0,

                                    y: state.settings.standardizeA4 ? (A4_HEIGHT - embeddedPage.height * ratio) / 2 : 0,

                                    width: state.settings.standardizeA4 ? embeddedPage.width * ratio : embeddedPage.width,

                                    height: state.settings.standardizeA4 ? embeddedPage.height * ratio : embeddedPage.height,

                                });

                            }

                        } catch(pageError) {

                            console.error(`Erreur en traitant la page ${index + 1} du fichier ${file.name}. Page ignorée.`, pageError);

                        }

                    }

                } else {

                    const image = ['png'].includes(fileType) ? await finalPdfDoc.embedPng(fileBytes) : await finalPdfDoc.embedJpg(fileBytes);

                    const { width, height } = image.scale(1);

                    if (width > 0 && height > 0) {

                        const newPage = finalPdfDoc.addPage(state.settings.standardizeA4 ? [A4_WIDTH, A4_HEIGHT] : [width, height]);

                        const ratio = Math.min(A4_WIDTH / width, A4_HEIGHT / height);

                        newPage.drawImage(image, state.settings.standardizeA4 ? { x: (A4_WIDTH - width * ratio) / 2, y: (A4_HEIGHT - height * ratio) / 2, width: width * ratio, height: height * ratio } : {});

                    }

                }

            } catch (err) { console.error(err); localFailedFiles.push(file.name); }

        }

        if (finalPdfDoc.getPageCount() === 0) throw new Error("Aucun fichier valide n'a pu être traité.");

        const pdfBytes = await finalPdfDoc.save();

        setState({ failedFiles: localFailedFiles, convertedFile: { blob: new Blob([pdfBytes], { type: 'application/pdf' }), fileName: 'document_fusionne.pdf' } });

    }


    async function batchConvertAndDownloadIndividually() {

        const { PDFDocument } = window.PDFLib;

        const pdfjsLib = window.pdfjsLib;

        let localFailedFiles = [];

        let successCount = 0;


        for (let i = 0; i < state.selectedFiles.length; i++) {

            const file = state.selectedFiles[i];

            setState({ processingProgress: Math.round(((i + 1) / state.selectedFiles.length) * 95), processingMessage: `Conversion de ${file.name}...` });

            try {

                const fileBytes = await file.arrayBuffer();

                const fileType = file.name.split('.').pop().toLowerCase().replace('jpeg', 'jpg');

                const baseName = file.name.replace(/\.[^/.]+$/, "");

                let results = []; // Array of {blob, fileName}


                if (state.settings.targetFormat === 'pdf') {

                    let data, fileName;

                    if (fileType === 'pdf') {

                        data = fileBytes;

                        fileName = file.name;

                    } else { // Image to PDF

                        const pdfDoc = await PDFDocument.create();

                        const image = (fileType === 'png') ? await pdfDoc.embedPng(fileBytes) : await pdfDoc.embedJpg(fileBytes);

                        if(image.width > 0 && image.height > 0) {

                            pdfDoc.addPage([image.width, image.height]).drawImage(image);

                            data = await pdfDoc.save();

                            fileName = `${baseName}.pdf`;

                        }

                    }

                    if(data) {

                        results.push({blob: new Blob([data], {type: 'application/pdf'}), fileName});

                    }

                } else { // Target is JPG or PNG

                    const mimeType = `image/${state.settings.targetFormat === 'jpg' ? 'jpeg' : 'png'}`;

                    if (fileType === 'pdf') {

                        const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(fileBytes) }).promise;

                        for (let p = 1; p <= pdf.numPages; p++) {

                            const page = await pdf.getPage(p);

                            const viewport = page.getViewport({ scale: 2.0 });

                            const canvas = document.createElement('canvas');

                            canvas.height = viewport.height; canvas.width = viewport.width;

                            await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;

                            const blob = await new Promise(resolve => canvas.toBlob(resolve, mimeType, 0.9));

                            results.push({ blob, fileName: `${baseName}_page_${p}.${state.settings.targetFormat}`});

                        }

                    } else { // Image to Image

                        let blob;

                        let fileName = `${baseName}.${state.settings.targetFormat}`;

                        if (fileType === state.settings.targetFormat) {

                            blob = new Blob([fileBytes], {type: file.type});

                            fileName = file.name;

                        } else {

                            const imgBlob = new Blob([fileBytes], { type: file.type });

                            const img = await new Promise((r, j) => { let i = new Image(); i.onload=()=>r(i); i.onerror=j; i.src=URL.createObjectURL(imgBlob);});

                            URL.revokeObjectURL(img.src);

                            const canvas = document.createElement('canvas');

                            canvas.width = img.width; canvas.height = img.height;

                            const ctx = canvas.getContext('2d');

                            if (state.settings.targetFormat === 'jpg' && fileType === 'png') {

                                ctx.fillStyle = '#FFFFFF';

                                ctx.fillRect(0, 0, canvas.width, canvas.height);

                            }

                            ctx.drawImage(img, 0, 0);

                            blob = await new Promise(resolve => canvas.toBlob(resolve, mimeType, 0.9));

                        }

                        results.push({blob, fileName});

                    }

                }

                if (results.length === 0) {

                     throw new Error("La conversion n'a produit aucun fichier.");

                }


                if (state.selectedFiles.length === 1) {

                    setState({ convertedFile: { blob: results[0].blob, fileName: results[0].fileName, multiDownload: results.length > 1 } });

                    if (results.length > 1) {

                         for(const result of results) {

                            triggerDownload(result.blob, result.fileName);

                            await new Promise(r => setTimeout(r, 200));

                        }

                    }

                } else {

                    for(const result of results) {

                        triggerDownload(result.blob, result.fileName);

                        successCount++;

                        await new Promise(r => setTimeout(r, 200));

                    }

                }


            } catch (err) { console.error(err); localFailedFiles.push(file.name); }

        }

        setState({ failedFiles: localFailedFiles });


        if (state.selectedFiles.length > 1) {

             if (successCount === 0 && localFailedFiles.length === state.selectedFiles.length) {

                throw new Error("Aucun fichier valide n'a pu être traité.");

            }

             setState({ convertedFile: { multiDownload: true } });

        }

    }


    // --- Initial Render ---

    render();

});

</script>

</body>

</html>