{"id":1027,"date":"2026-10-26T17:47:27","date_gmt":"2026-10-26T17:47:27","guid":{"rendered":"https:\/\/srabons.de\/?page_id=1027"},"modified":"2026-10-26T18:04:34","modified_gmt":"2026-10-26T18:04:34","slug":"pdf-to-image-converter","status":"publish","type":"page","link":"https:\/\/srabons.de\/en\/pdf-to-image-converter\/","title":{"rendered":"PDF to Image Converter"},"content":{"rendered":"\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=5.0\">  <title>srabons.de | PDF to Image Converter<\/title>\n\n  <!-- Tailwind CSS CDN -->\n  <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n  <script>\n    tailwind.config = {\n      darkMode: 'class',\n      theme: {\n        extend: {\n          colors: {\n            tealBrand: {\n              50: '#f0fdfa',\n              100: '#ccfbf1',\n              200: '#99f6e4',\n              300: '#5eead4',\n              400: '#2dd4bf',\n              500: '#14b8a6',\n              600: '#0d9488',\n              700: '#0f766e',\n              800: '#115e59',\n              900: '#134e4a',\n              950: '#042f2e',\n            }\n          },\n          fontFamily: {\n            sans: ['Inter', 'system-ui', '-apple-system', 'Segoe UI', 'Roboto', 'sans-serif'],\n          }\n        }\n      }\n    }\n  <\/script>\n\n  <!-- PDF.js CDN (Stable 3.11.174) -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js\"><\/script>\n  <!-- JSZip CDN for batch zip packaging -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\n  <!-- FontAwesome 6 CDN -->\n  <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.5.1\/css\/all.min.css\">\n\n  <style>\n    \/* Responsive dynamic grid columns controlled by CSS custom variable *\/\n    #pagesContainer.grid-mode {\n      display: grid;\n      grid-template-columns: repeat(auto-fill, minmax(var(--card-min-size, 260px), 1fr));\n      gap: 1.25rem;\n    }\n    #pagesContainer.list-mode {\n      display: flex;\n      flex-direction: column;\n      gap: 1.25rem;\n    }\n\n    \/* Checkerboard backdrop for transparent and clear preview *\/\n    .checker-pattern {\n      background-color: #f8fafc;\n      background-image: \n        linear-gradient(45deg, #e2e8f0 25%, transparent 25%),\n        linear-gradient(-45deg, #e2e8f0 25%, transparent 25%),\n        linear-gradient(45deg, transparent 75%, #e2e8f0 75%),\n        linear-gradient(-45deg, transparent 75%, #e2e8f0 75%);\n      background-size: 16px 16px;\n      background-position: 0 0, 0 8px, 8px -8px, -8px 0px;\n    }\n    .dark .checker-pattern {\n      background-color: #0f172a;\n      background-image: \n        linear-gradient(45deg, #1e293b 25%, transparent 25%),\n        linear-gradient(-45deg, #1e293b 25%, transparent 25%),\n        linear-gradient(45deg, transparent 75%, #1e293b 75%),\n        linear-gradient(-45deg, transparent 75%, #1e293b 75%);\n    }\n\n    \/* Scrollbar styling *\/\n    ::-webkit-scrollbar { width: 8px; height: 8px; }\n    ::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.04); }\n    .dark ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.04); }\n    ::-webkit-scrollbar-thumb { background: rgba(100, 116, 139, 0.4); border-radius: 9999px; }\n    ::-webkit-scrollbar-thumb:hover { background: rgba(100, 116, 139, 0.7); }\n  <\/style>\n\n\n\n  <!-- Top Navigation Bar -->\n  <header class=\"sticky top-0 z-40 bg-white\/90 dark:bg-slate-900\/90 backdrop-blur border-b border-slate-200 dark:border-slate-800 shadow-xs\">\n    <div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4\">\n      \n      <!-- Brand Logo -->\n      <a href=\"https:\/\/srabons.de\" class=\"flex items-center gap-3 group focus:outline-none\">\n        <div class=\"w-10 h-10 rounded-xl bg-gradient-to-tr from-tealBrand-600 to-cyan-600 text-white flex items-center justify-center font-bold text-lg shadow-md shadow-tealBrand-600\/25 group-hover:scale-105 transition-transform\">\n          <i class=\"fa-solid fa-layer-group\"><\/i>\n        <\/div>\n        <div class=\"flex flex-col\">\n          <span class=\"font-extrabold tracking-tight text-xl text-slate-900 dark:text-white leading-none\">\n            srabons<span class=\"text-tealBrand-600 dark:text-tealBrand-400\">.de<\/span>\n          <\/span>\n          <span class=\"text-[11px] text-slate-500 dark:text-slate-400 font-medium\">\n            PDF to Image Converter\n          <\/span>\n        <\/div>\n      <\/a>\n\n      <!-- Quick Action Badges -->\n      <div class=\"flex items-center gap-2 sm:gap-3\">\n        <span class=\"hidden md:inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-tealBrand-50 dark:bg-tealBrand-950\/80 text-tealBrand-700 dark:text-tealBrand-300 border border-tealBrand-200 dark:border-tealBrand-900\">\n          <i class=\"fa-solid fa-lock text-[10px]\"><\/i> Local Client-Side Execution\n        <\/span>\n\n        <!-- Instant Multi-Page Test PDF Button -->\n        <button id=\"generateSampleBtn\" class=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 transition-colors\" title=\"Generate an immediate 3-page PDF to test page 2 and 3 rendering\">\n          <i class=\"fa-solid fa-flask-vial text-tealBrand-600 dark:text-tealBrand-400\"><\/i>\n          <span class=\"hidden sm:inline\">Try Sample PDF<\/span>\n        <\/button>\n\n        <!-- Dark\/Light Mode Toggle -->\n        <button id=\"themeToggleBtn\" aria-label=\"Toggle Theme\" class=\"p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 transition-colors\">\n          <i id=\"themeIcon\" class=\"fa-solid fa-moon\"><\/i>\n        <\/button>\n\n        <!-- Clear State -->\n        <button id=\"clearAllBtn\" class=\"hidden px-3 py-1.5 rounded-xl text-xs font-semibold bg-rose-50 text-rose-600 hover:bg-rose-100 dark:bg-rose-950\/40 dark:text-rose-400 border border-rose-200 dark:border-rose-900 transition-colors\">\n          <i class=\"fa-solid fa-arrow-rotate-left mr-1\"><\/i> Reset\n        <\/button>\n      <\/div>\n    <\/div>\n  <\/header>\n\n  <!-- Main Content Wrapper -->\n  <main class=\"flex-1 max-w-7xl w-full mx-auto p-4 sm:p-6 lg:p-8 flex flex-col gap-6\">\n\n    <!-- Upload Hero Section (Default view) -->\n    <section id=\"uploadSection\" class=\"transition-all duration-200\">\n      <div id=\"dropzone\" class=\"border-2 border-dashed border-slate-300 dark:border-slate-700 rounded-3xl p-8 sm:p-14 text-center bg-white dark:bg-slate-900 shadow-sm hover:border-tealBrand-500 dark:hover:border-tealBrand-400 hover:bg-tealBrand-50\/10 dark:hover:bg-tealBrand-950\/10 transition-all cursor-pointer group flex flex-col items-center justify-center relative\">\n        <input type=\"file\" id=\"fileInput\" accept=\"application\/pdf\" class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10\">\n        \n        <div class=\"w-20 h-20 rounded-2xl bg-tealBrand-50 dark:bg-slate-800 text-tealBrand-600 dark:text-tealBrand-400 flex items-center justify-center text-3xl mb-4 group-hover:scale-110 group-hover:bg-tealBrand-600 group-hover:text-white transition-all shadow-sm\">\n          <i class=\"fa-solid fa-file-arrow-up\"><\/i>\n        <\/div>\n        \n        <h2 class=\"text-xl sm:text-2xl font-bold text-slate-800 dark:text-slate-100 mb-2\">\n          Drop your PDF document here\n        <\/h2>\n        <p class=\"text-sm text-slate-500 dark:text-slate-400 max-w-md mb-6 leading-relaxed\">\n          Instantly convert all pages into sharp PNG, JPEG, or WebP images. Every page renders reliably in sequence directly on your device.\n        <\/p>\n        \n        <div class=\"inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-tealBrand-600 hover:bg-tealBrand-700 text-white font-semibold text-sm shadow-lg shadow-tealBrand-600\/25 transition-all\">\n          <i class=\"fa-regular fa-folder-open\"><\/i>\n          <span>Choose PDF File<\/span>\n        <\/div>\n\n        <div class=\"mt-8 flex flex-wrap items-center justify-center gap-6 text-xs text-slate-400 dark:text-slate-500 font-medium\">\n          <div class=\"flex items-center gap-1.5\">\n            <i class=\"fa-solid fa-circle-check text-tealBrand-500\"><\/i> No server uploads\n          <\/div>\n          <div class=\"flex items-center gap-1.5\">\n            <i class=\"fa-solid fa-circle-check text-tealBrand-500\"><\/i> Multi-page safe rendering\n          <\/div>\n          <div class=\"flex items-center gap-1.5\">\n            <i class=\"fa-solid fa-circle-check text-tealBrand-500\"><\/i> Batch ZIP export\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- Workspace View (Active after PDF loaded) -->\n    <div id=\"workspaceSection\" class=\"hidden flex-col gap-6\">\n\n      <!-- File Header Bar -->\n      <div class=\"bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-4 sm:px-6 flex flex-wrap items-center justify-between gap-4 shadow-sm\">\n        <div class=\"flex items-center gap-3.5 min-w-0\">\n          <div class=\"w-12 h-12 rounded-xl bg-rose-100 dark:bg-rose-950\/70 text-rose-600 dark:text-rose-400 flex items-center justify-center text-xl shrink-0 font-bold\">\n            <i class=\"fa-solid fa-file-pdf\"><\/i>\n          <\/div>\n          <div class=\"truncate\">\n            <h3 id=\"docFileName\" class=\"text-base sm:text-lg font-bold text-slate-900 dark:text-white truncate\">document.pdf<\/h3>\n            <div class=\"flex items-center gap-3 text-xs text-slate-500 dark:text-slate-400 mt-0.5 font-medium\">\n              <span id=\"docFileSize\"><i class=\"fa-solid fa-hard-drive mr-1\"><\/i>0 KB<\/span>\n              <span>\u2022<\/span>\n              <span id=\"docPageCount\"><i class=\"fa-solid fa-copy mr-1\"><\/i>0 Pages Total<\/span>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <!-- Replace \/ New File button -->\n        <label for=\"changeFileInput\" class=\"cursor-pointer inline-flex items-center gap-2 px-3.5 py-2 text-xs font-semibold rounded-xl border border-slate-200 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-200 transition-colors\">\n          <i class=\"fa-solid fa-file-import\"><\/i> Choose Another PDF\n          <input type=\"file\" id=\"changeFileInput\" accept=\"application\/pdf\" class=\"hidden\">\n        <\/label>\n      <\/div>\n\n      <!-- Main Workspace Grid -->\n      <div class=\"grid grid-cols-1 lg:grid-cols-4 gap-6 items-start\">\n\n        <!-- Left Settings Sidebar -->\n        <aside class=\"lg:col-span-1 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm flex flex-col gap-5 sticky top-20\">\n          <div class=\"flex items-center justify-between border-b border-slate-100 dark:border-slate-800 pb-3\">\n            <h4 class=\"font-bold text-xs tracking-wider uppercase text-slate-400 dark:text-slate-500\">Configuration<\/h4>\n            <span class=\"text-[11px] font-semibold px-2 py-0.5 rounded-full bg-tealBrand-50 text-tealBrand-700 dark:bg-tealBrand-950 dark:text-tealBrand-300\">Live<\/span>\n          <\/div>\n\n          <!-- Format Choice -->\n          <div>\n            <label class=\"block text-xs font-bold uppercase text-slate-500 dark:text-slate-400 mb-2\">Image Format<\/label>\n            <div class=\"grid grid-cols-3 gap-2\">\n              <label class=\"cursor-pointer\">\n                <input type=\"radio\" name=\"imgFormat\" value=\"png\" class=\"peer sr-only\" checked=\"\">\n                <div class=\"p-2 text-center rounded-xl border border-slate-200 dark:border-slate-700 peer-checked:border-tealBrand-500 peer-checked:bg-tealBrand-50 dark:peer-checked:bg-tealBrand-950\/60 peer-checked:text-tealBrand-700 dark:peer-checked:text-tealBrand-300 text-xs font-bold transition-all\">\n                  PNG\n                <\/div>\n              <\/label>\n              <label class=\"cursor-pointer\">\n                <input type=\"radio\" name=\"imgFormat\" value=\"jpeg\" class=\"peer sr-only\">\n                <div class=\"p-2 text-center rounded-xl border border-slate-200 dark:border-slate-700 peer-checked:border-tealBrand-500 peer-checked:bg-tealBrand-50 dark:peer-checked:bg-tealBrand-950\/60 peer-checked:text-tealBrand-700 dark:peer-checked:text-tealBrand-300 text-xs font-bold transition-all\">\n                  JPEG\n                <\/div>\n              <\/label>\n              <label class=\"cursor-pointer\">\n                <input type=\"radio\" name=\"imgFormat\" value=\"webp\" class=\"peer sr-only\">\n                <div class=\"p-2 text-center rounded-xl border border-slate-200 dark:border-slate-700 peer-checked:border-tealBrand-500 peer-checked:bg-tealBrand-50 dark:peer-checked:bg-tealBrand-950\/60 peer-checked:text-tealBrand-700 dark:peer-checked:text-tealBrand-300 text-xs font-bold transition-all\">\n                  WebP\n                <\/div>\n              <\/label>\n            <\/div>\n          <\/div>\n\n          <!-- Quality Scale \/ DPI (Balanced to prevent mobile GPU crashes on huge PDFs) -->\n          <div>\n            <label class=\"block text-xs font-bold uppercase text-slate-500 dark:text-slate-400 mb-2\">Render Resolution<\/label>\n            <div class=\"space-y-2\">\n              <label class=\"flex items-center justify-between p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800\/50 has-[:checked]:border-tealBrand-500 has-[:checked]:bg-tealBrand-50\/50 dark:has-[:checked]:bg-tealBrand-950\/40\">\n                <div class=\"flex items-center gap-2\">\n                  <input type=\"radio\" name=\"qualityScale\" value=\"1.0\" class=\"accent-tealBrand-600\">\n                  <span class=\"text-xs font-semibold\">Standard<\/span>\n                <\/div>\n                <span class=\"text-[11px] text-slate-400\">1x (Fast)<\/span>\n              <\/label>\n              <label class=\"flex items-center justify-between p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800\/50 has-[:checked]:border-tealBrand-500 has-[:checked]:bg-tealBrand-50\/50 dark:has-[:checked]:bg-tealBrand-950\/40\">\n                <div class=\"flex items-center gap-2\">\n                  <input type=\"radio\" name=\"qualityScale\" value=\"1.5\" class=\"accent-tealBrand-600\" checked=\"\">\n                  <span class=\"text-xs font-semibold\">High Definition<\/span>\n                <\/div>\n                <span class=\"text-[11px] text-slate-400\">1.5x (Recommended)<\/span>\n              <\/label>\n              <label class=\"flex items-center justify-between p-2.5 rounded-xl border border-slate-200 dark:border-slate-800 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800\/50 has-[:checked]:border-tealBrand-500 has-[:checked]:bg-tealBrand-50\/50 dark:has-[:checked]:bg-tealBrand-950\/40\">\n                <div class=\"flex items-center gap-2\">\n                  <input type=\"radio\" name=\"qualityScale\" value=\"2.0\" class=\"accent-tealBrand-600\">\n                  <span class=\"text-xs font-semibold\">Ultra HD<\/span>\n                <\/div>\n                <span class=\"text-[11px] text-slate-400\">2x (Print)<\/span>\n              <\/label>\n            <\/div>\n          <\/div>\n\n          <!-- Page Selection Range -->\n          <div>\n            <label class=\"block text-xs font-bold uppercase text-slate-500 dark:text-slate-400 mb-1.5\">Page Selection<\/label>\n            <div class=\"space-y-2\">\n              <select id=\"pageRangeOption\" class=\"w-full text-xs font-semibold rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800 p-2.5 text-slate-700 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-tealBrand-500\">\n                <option value=\"all\">Convert All Pages<\/option>\n                <option value=\"custom\">Custom Page Range<\/option>\n              <\/select>\n              <input type=\"text\" id=\"customRangeInput\" placeholder=\"e.g. 1, 2-4, 6\" class=\"hidden w-full text-xs rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 p-2.5 text-slate-700 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-tealBrand-500\">\n            <\/div>\n          <\/div>\n\n          <!-- Rerender Trigger -->\n          <button id=\"reapplyBtn\" class=\"w-full py-2.5 px-3 rounded-xl font-bold text-xs bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-800 dark:text-slate-200 transition-colors flex items-center justify-center gap-2\">\n            <i class=\"fa-solid fa-arrows-rotate\"><\/i> Re-Render With Settings\n          <\/button>\n\n          <!-- Bulk ZIP Download Button -->\n          <div class=\"pt-3 border-t border-slate-100 dark:border-slate-800 flex flex-col gap-2\">\n            <button id=\"downloadAllZipBtn\" class=\"w-full py-3 px-4 rounded-xl font-bold text-sm bg-gradient-to-r from-tealBrand-600 to-tealBrand-700 hover:from-tealBrand-500 hover:to-tealBrand-600 text-white shadow-md shadow-tealBrand-600\/25 active:scale-[0.98] transition-all flex items-center justify-center gap-2\">\n              <i class=\"fa-solid fa-file-zipper\"><\/i>\n              <span>Download All as ZIP<\/span>\n            <\/button>\n            <button id=\"selectAllPagesBtn\" class=\"w-full py-2 px-3 rounded-xl font-semibold text-xs border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors\">\n              <i class=\"fa-regular fa-square-check mr-1.5\"><\/i> Select All For Export\n            <\/button>\n          <\/div>\n        <\/aside>\n\n        <!-- Gallery \/ Output Area -->\n        <div class=\"lg:col-span-3 flex flex-col gap-4\">\n\n          <!-- View Mode & Zoom Toolbar -->\n          <div class=\"bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-3 sm:px-4 flex flex-wrap items-center justify-between gap-3 shadow-sm\">\n            \n            <div class=\"flex items-center gap-2\">\n              <span class=\"text-xs font-semibold text-slate-500 dark:text-slate-400\">Layout:<\/span>\n              <div class=\"inline-flex rounded-xl p-1 bg-slate-100 dark:bg-slate-800\">\n                <button id=\"gridViewBtn\" class=\"p-1.5 px-3 rounded-lg text-xs font-bold bg-white dark:bg-slate-700 text-slate-900 dark:text-white shadow-xs transition-all\">\n                  <i class=\"fa-solid fa-table-cells mr-1\"><\/i> Grid\n                <\/button>\n                <button id=\"listViewBtn\" class=\"p-1.5 px-3 rounded-lg text-xs font-semibold text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition-all\">\n                  <i class=\"fa-solid fa-list mr-1\"><\/i> List\n                <\/button>\n              <\/div>\n            <\/div>\n\n            <!-- Dynamic Zoom Slider adjusting card scale for any screen size -->\n            <div class=\"flex items-center gap-2.5\">\n              <span class=\"text-xs font-semibold text-slate-500 dark:text-slate-400 hidden sm:inline\">Size:<\/span>\n              <i class=\"fa-solid fa-image text-slate-400 text-xs\"><\/i>\n              <input type=\"range\" id=\"cardSizeSlider\" min=\"180\" max=\"420\" value=\"280\" class=\"w-24 sm:w-36 accent-tealBrand-600 cursor-pointer\" title=\"Adjust preview card size\">\n              <i class=\"fa-solid fa-image text-slate-400 text-base\"><\/i>\n            <\/div>\n\n            <div class=\"text-xs text-slate-500 dark:text-slate-400 font-medium\">\n              Ready: <span id=\"visiblePageCount\" class=\"font-bold text-slate-800 dark:text-slate-100\">0<\/span> pages\n            <\/div>\n          <\/div>\n\n          <!-- Progress Bar during sequential conversion -->\n          <div id=\"progressBarContainer\" class=\"hidden bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-4 shadow-sm\">\n            <div class=\"flex justify-between items-center text-xs font-medium mb-1.5\">\n              <span id=\"progressBarLabel\" class=\"text-tealBrand-600 dark:text-tealBrand-400 font-bold flex items-center gap-2\">\n                <i class=\"fa-solid fa-circle-notch fa-spin\"><\/i> Rendering pages&#8230;\n              <\/span>\n              <span id=\"progressBarPercent\" class=\"font-mono text-slate-600 dark:text-slate-400 font-bold\">0%<\/span>\n            <\/div>\n            <div class=\"w-full h-2.5 bg-slate-100 dark:bg-slate-800 rounded-full overflow-hidden\">\n              <div id=\"progressBarFill\" class=\"h-full bg-gradient-to-r from-tealBrand-500 to-cyan-500 rounded-full transition-all duration-150\" style=\"width: 0%\"><\/div>\n            <\/div>\n          <\/div>\n\n          <!-- Rendered Pages Output Container -->\n          <div id=\"pagesContainer\" class=\"grid-mode\">\n            <!-- Dynamic page cards injected here -->\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/main>\n\n  <!-- Fullscreen Modal Viewer -->\n  <div id=\"viewerModal\" class=\"fixed inset-0 z-50 hidden bg-slate-950\/85 backdrop-blur-sm items-center justify-center p-3 sm:p-6\">\n    <div class=\"relative max-w-5xl w-full h-[92vh] bg-white dark:bg-slate-900 rounded-3xl shadow-2xl flex flex-col overflow-hidden border border-slate-200 dark:border-slate-800\">\n      \n      <!-- Modal Header -->\n      <div class=\"flex items-center justify-between px-5 py-3.5 border-b border-slate-200 dark:border-slate-800\">\n        <div class=\"flex items-center gap-2.5\">\n          <span class=\"w-7 h-7 rounded-lg bg-tealBrand-50 dark:bg-tealBrand-950 text-tealBrand-600 dark:text-tealBrand-400 flex items-center justify-center font-bold text-xs\">\n            <i class=\"fa-solid fa-magnifying-glass\"><\/i>\n          <\/span>\n          <h3 id=\"modalPageTitle\" class=\"font-bold text-slate-900 dark:text-white text-sm sm:text-base\">Page Preview<\/h3>\n        <\/div>\n\n        <div class=\"flex items-center gap-2\">\n          <button id=\"modalRotateBtn\" class=\"p-2 rounded-xl text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 text-xs font-semibold\" title=\"Rotate clockwise\">\n            <i class=\"fa-solid fa-rotate-right\"><\/i>\n          <\/button>\n          <button id=\"modalZoomOutBtn\" class=\"p-2 rounded-xl text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 text-xs font-semibold\" title=\"Zoom Out\">\n            <i class=\"fa-solid fa-minus\"><\/i>\n          <\/button>\n          <button id=\"modalZoomInBtn\" class=\"p-2 rounded-xl text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 text-xs font-semibold\" title=\"Zoom In\">\n            <i class=\"fa-solid fa-plus\"><\/i>\n          <\/button>\n          <div class=\"w-px h-5 bg-slate-200 dark:bg-slate-700 mx-1\"><\/div>\n          <button id=\"modalDownloadBtn\" class=\"px-3 py-1.5 rounded-xl bg-tealBrand-600 hover:bg-tealBrand-700 text-white text-xs font-bold flex items-center gap-1.5 transition-colors\">\n            <i class=\"fa-solid fa-download\"><\/i> Save\n          <\/button>\n          <button id=\"closeModalBtn\" class=\"p-2 rounded-xl text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 text-sm ml-1\">\n            <i class=\"fa-solid fa-xmark text-lg\"><\/i>\n          <\/button>\n        <\/div>\n      <\/div>\n\n      <!-- Preview Image Canvas View -->\n      <div class=\"flex-1 overflow-auto p-4 sm:p-8 flex items-center justify-center checker-pattern relative\">\n        <img decoding=\"async\" id=\"modalPreviewImage\" src=\"\" alt=\"Fullscreen page view\" class=\"max-h-full max-w-full rounded shadow-lg object-contain transition-transform duration-100\">\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <!-- Notification Toast Container -->\n  <div id=\"toastContainer\" class=\"fixed bottom-6 right-6 z-50 flex flex-col gap-2 pointer-events-none\"><\/div>\n\n  <!-- Footer -->\n  <footer class=\"mt-auto border-t border-slate-200 dark:border-slate-800 py-6 text-center text-xs text-slate-500\">\n    <div class=\"max-w-7xl mx-auto px-4 flex flex-col sm:flex-row items-center justify-between gap-2\">\n      <span>\u00a9 <span id=\"currentYear\"><\/span> <a href=\"https:\/\/srabons.de\" class=\"hover:text-tealBrand-500 font-bold\">srabons.de<\/a>. All rights reserved.<\/span>\n      <span class=\"text-slate-400\">Sequential multi-page rendering engine.<\/span>\n    <\/div>\n  <\/footer>\n\n  <script>\n    \/\/ Ensure PDF.js worker is correctly configured\n    if (typeof pdfjsLib !== 'undefined') {\n      pdfjsLib.GlobalWorkerOptions.workerSrc = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.worker.min.js';\n    }\n\n    \/\/ Application State\n    const appState = {\n      pdfDoc: null,\n      rawBytes: null,\n      fileName: 'document',\n      fileSize: 0,\n      totalPages: 0,\n      renderedPages: [], \/\/ [{ pageNum, dataUrl, width, height, format, selected }]\n      isRendering: false,\n      modalPageNum: 1,\n      modalZoom: 1.0,\n      modalRotation: 0\n    };\n\n    \/\/ DOM Elements\n    const dropzone = document.getElementById('dropzone');\n    const fileInput = document.getElementById('fileInput');\n    const changeFileInput = document.getElementById('changeFileInput');\n    const uploadSection = document.getElementById('uploadSection');\n    const workspaceSection = document.getElementById('workspaceSection');\n    const docFileName = document.getElementById('docFileName');\n    const docFileSize = document.getElementById('docFileSize');\n    const docPageCount = document.getElementById('docPageCount');\n    const pagesContainer = document.getElementById('pagesContainer');\n    const visiblePageCount = document.getElementById('visiblePageCount');\n    const progressBarContainer = document.getElementById('progressBarContainer');\n    const progressBarLabel = document.getElementById('progressBarLabel');\n    const progressBarFill = document.getElementById('progressBarFill');\n    const progressBarPercent = document.getElementById('progressBarPercent');\n    const clearAllBtn = document.getElementById('clearAllBtn');\n    const downloadAllZipBtn = document.getElementById('downloadAllZipBtn');\n    const selectAllPagesBtn = document.getElementById('selectAllPagesBtn');\n    const gridViewBtn = document.getElementById('gridViewBtn');\n    const listViewBtn = document.getElementById('listViewBtn');\n    const cardSizeSlider = document.getElementById('cardSizeSlider');\n    const pageRangeOption = document.getElementById('pageRangeOption');\n    const customRangeInput = document.getElementById('customRangeInput');\n    const reapplyBtn = document.getElementById('reapplyBtn');\n    const generateSampleBtn = document.getElementById('generateSampleBtn');\n    const themeToggleBtn = document.getElementById('themeToggleBtn');\n    const themeIcon = document.getElementById('themeIcon');\n\n    \/\/ Modal Elements\n    const viewerModal = document.getElementById('viewerModal');\n    const modalPageTitle = document.getElementById('modalPageTitle');\n    const modalPreviewImage = document.getElementById('modalPreviewImage');\n    const closeModalBtn = document.getElementById('closeModalBtn');\n    const modalRotateBtn = document.getElementById('modalRotateBtn');\n    const modalZoomInBtn = document.getElementById('modalZoomInBtn');\n    const modalZoomOutBtn = document.getElementById('modalZoomOutBtn');\n    const modalDownloadBtn = document.getElementById('modalDownloadBtn');\n\n    \/\/ Theme Management\n    function initTheme() {\n      const isDark = localStorage.getItem('theme') === 'dark' ||\n        (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);\n      if (isDark) {\n        document.documentElement.classList.add('dark');\n        themeIcon.classList.replace('fa-moon', 'fa-sun');\n      } else {\n        document.documentElement.classList.remove('dark');\n        themeIcon.classList.replace('fa-sun', 'fa-moon');\n      }\n    }\n\n    themeToggleBtn.addEventListener('click', () => {\n      const isDark = document.documentElement.classList.toggle('dark');\n      localStorage.setItem('theme', isDark ? 'dark' : 'light');\n      themeIcon.classList.toggle('fa-sun', isDark);\n      themeIcon.classList.toggle('fa-moon', !isDark);\n    });\n\n    document.getElementById('currentYear').textContent = new Date().getFullYear();\n\n    \/\/ Toast Notification System\n    function showToast(message, type = 'info') {\n      const container = document.getElementById('toastContainer');\n      const toast = document.createElement('div');\n      const icons = {\n        success: 'fa-circle-check text-emerald-500',\n        error: 'fa-circle-xmark text-rose-500',\n        info: 'fa-circle-info text-tealBrand-500',\n        warning: 'fa-triangle-exclamation text-amber-500'\n      };\n\n      toast.className = 'pointer-events-auto flex items-center gap-2.5 px-4 py-3 bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100 rounded-2xl shadow-xl border border-slate-200 dark:border-slate-700 text-xs font-semibold transform transition-all duration-300 translate-y-3 opacity-0';\n      toast.innerHTML = `<i class=\"fa-solid ${icons[type] || icons.info} text-sm\"><\/i> <span>${message}<\/span>`;\n      container.appendChild(toast);\n\n      requestAnimationFrame(() => {\n        toast.classList.remove('translate-y-3', 'opacity-0');\n      });\n\n      setTimeout(() => {\n        toast.classList.add('translate-y-3', 'opacity-0');\n        setTimeout(() => toast.remove(), 300);\n      }, 3500);\n    }\n\n    function formatBytes(bytes) {\n      if (!bytes || bytes === 0) return '0 Bytes';\n      const k = 1024;\n      const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n      const i = Math.floor(Math.log(bytes) \/ Math.log(k));\n      return parseFloat((bytes \/ Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n    }\n\n    \/\/ Card Size Slider (Responsive adjustment)\n    cardSizeSlider.addEventListener('input', (e) => {\n      const val = e.target.value;\n      pagesContainer.style.setProperty('--card-min-size', `${val}px`);\n    });\n\n    \/\/ View Switching\n    gridViewBtn.addEventListener('click', () => {\n      pagesContainer.className = 'grid-mode';\n      gridViewBtn.className = 'p-1.5 px-3 rounded-lg text-xs font-bold bg-white dark:bg-slate-700 text-slate-900 dark:text-white shadow-xs transition-all';\n      listViewBtn.className = 'p-1.5 px-3 rounded-lg text-xs font-semibold text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition-all';\n    });\n\n    listViewBtn.addEventListener('click', () => {\n      pagesContainer.className = 'list-mode';\n      listViewBtn.className = 'p-1.5 px-3 rounded-lg text-xs font-bold bg-white dark:bg-slate-700 text-slate-900 dark:text-white shadow-xs transition-all';\n      gridViewBtn.className = 'p-1.5 px-3 rounded-lg text-xs font-semibold text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition-all';\n    });\n\n    \/\/ Page Range Option toggle\n    pageRangeOption.addEventListener('change', (e) => {\n      if (e.target.value === 'custom') {\n        customRangeInput.classList.remove('hidden');\n        customRangeInput.focus();\n      } else {\n        customRangeInput.classList.add('hidden');\n      }\n    });\n\n    \/\/ Drag & Drop\n    ['dragenter', 'dragover'].forEach(evt => {\n      dropzone.addEventListener(evt, (e) => {\n        e.preventDefault();\n        dropzone.classList.add('border-tealBrand-500', 'bg-tealBrand-50\/20');\n      });\n    });\n\n    ['dragleave', 'drop'].forEach(evt => {\n      dropzone.addEventListener(evt, (e) => {\n        e.preventDefault();\n        dropzone.classList.remove('border-tealBrand-500', 'bg-tealBrand-50\/20');\n      });\n    });\n\n    dropzone.addEventListener('drop', (e) => {\n      if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n        const file = e.dataTransfer.files[0];\n        if (file.type === 'application\/pdf' || file.name.endsWith('.pdf')) {\n          loadPdfFromFile(file);\n        } else {\n          showToast('Please select a valid PDF file.', 'warning');\n        }\n      }\n    });\n\n    fileInput.addEventListener('change', (e) => {\n      if (e.target.files.length > 0) loadPdfFromFile(e.target.files[0]);\n    });\n\n    changeFileInput.addEventListener('change', (e) => {\n      if (e.target.files.length > 0) loadPdfFromFile(e.target.files[0]);\n    });\n\n    \/\/ Load PDF from File object\n    function loadPdfFromFile(file) {\n      appState.fileName = file.name.replace(\/\\.pdf$\/i, '');\n      appState.fileSize = file.size;\n\n      docFileName.textContent = file.name;\n      docFileSize.innerHTML = `<i class=\"fa-solid fa-hard-drive mr-1\"><\/i>${formatBytes(file.size)}`;\n\n      const reader = new FileReader();\n      reader.onload = async function() {\n        appState.rawBytes = new Uint8Array(this.result);\n        await initPdfDocument(appState.rawBytes);\n      };\n      reader.readAsArrayBuffer(file);\n    }\n\n    \/\/ Initialize PDF.js Document\n    async function initPdfDocument(data) {\n      try {\n        showToast('Reading PDF document...', 'info');\n        const loadingTask = pdfjsLib.getDocument({\n          data: data,\n          cMapUrl: 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/cmaps\/',\n          cMapPacked: true,\n        });\n\n        appState.pdfDoc = await loadingTask.promise;\n        appState.totalPages = appState.pdfDoc.numPages;\n\n        docPageCount.innerHTML = `<i class=\"fa-solid fa-copy mr-1\"><\/i>${appState.totalPages} Pages Total`;\n\n        uploadSection.classList.add('hidden');\n        workspaceSection.classList.remove('hidden');\n        clearAllBtn.classList.remove('hidden');\n\n        await startSequentialConversion();\n      } catch (err) {\n        console.error('PDF Load Error:', err);\n        showToast('Could not open PDF: ' + (err.message || 'Corrupted file'), 'error');\n      }\n    }\n\n    \/\/ Helper: Parse Page Range (e.g. \"1, 2-4, 7\")\n    function parseRange(str, max) {\n      if (!str || !str.trim()) {\n        return Array.from({ length: max }, (_, i) => i + 1);\n      }\n      const pages = new Set();\n      const tokens = str.split(',');\n      for (let token of tokens) {\n        token = token.trim();\n        if (token.includes('-')) {\n          const parts = token.split('-');\n          let s = parseInt(parts[0], 10);\n          let e = parseInt(parts[1], 10);\n          if (!isNaN(s) && !isNaN(e)) {\n            s = Math.max(1, Math.min(s, max));\n            e = Math.max(1, Math.min(e, max));\n            for (let i = Math.min(s, e); i <= Math.max(s, e); i++) {\n              pages.add(i);\n            }\n          }\n        } else {\n          const num = parseInt(token, 10);\n          if (!isNaN(num) &#038;&#038; num >= 1 && num <= max) {\n            pages.add(num);\n          }\n        }\n      }\n      const result = Array.from(pages).sort((a, b) => a - b);\n      return result.length > 0 ? result : Array.from({ length: max }, (_, i) => i + 1);\n    }\n\n    \/\/ Core Solution: STRICTLY SEQUENTIAL Multi-Page Rendering\n    async function startSequentialConversion() {\n      if (!appState.pdfDoc) return;\n      if (appState.isRendering) {\n        showToast('Conversion is currently running, please wait...', 'warning');\n        return;\n      }\n\n      appState.isRendering = true;\n      appState.renderedPages = [];\n      pagesContainer.innerHTML = '';\n\n      let targetPages = [];\n      if (pageRangeOption.value === 'custom') {\n        targetPages = parseRange(customRangeInput.value, appState.totalPages);\n      } else {\n        targetPages = Array.from({ length: appState.totalPages }, (_, i) => i + 1);\n      }\n\n      visiblePageCount.textContent = targetPages.length;\n\n      const format = document.querySelector('input[name=\"imgFormat\"]:checked').value;\n      const scale = parseFloat(document.querySelector('input[name=\"qualityScale\"]:checked').value);\n      const mimeType = format === 'jpeg' ? 'image\/jpeg' : (format === 'webp' ? 'image\/webp' : 'image\/png');\n\n      progressBarContainer.classList.remove('hidden');\n      updateProgress(0, targetPages.length, `Starting page 1 of ${targetPages.length}...`);\n\n      let processedCount = 0;\n\n      for (let i = 0; i < targetPages.length; i++) {\n        const pageNum = targetPages[i];\n\n        updateProgress(\n          processedCount,\n          targetPages.length,\n          `Processing Page ${pageNum} (${processedCount + 1}\/${targetPages.length})...`\n        );\n\n        try {\n          \/\/ 1. Fetch individual page from PDF document\n          const page = await appState.pdfDoc.getPage(pageNum);\n\n          \/\/ 2. Obtain viewport using desired scale\n          const viewport = page.getViewport({ scale: scale });\n\n          \/\/ 3. Create fresh, isolated offscreen canvas to avoid state conflicts\n          const canvas = document.createElement('canvas');\n          canvas.width = Math.floor(viewport.width);\n          canvas.height = Math.floor(viewport.height);\n\n          const ctx = canvas.getContext('2d', { alpha: format === 'png' });\n\n          \/\/ If JPEG or non-transparent PNG, paint solid white canvas background\n          if (format === 'jpeg' || format === 'webp') {\n            ctx.fillStyle = '#ffffff';\n            ctx.fillRect(0, 0, canvas.width, canvas.height);\n          }\n\n          \/\/ 4. Render context explicitly bound to this fresh canvas\n          const renderContext = {\n            canvasContext: ctx,\n            viewport: viewport,\n          };\n\n          \/\/ 5. CRITICAL: Strictly await the renderTask promise before proceeding to subsequent pages!\n          const renderTask = page.render(renderContext);\n          await renderTask.promise;\n\n          \/\/ 6. Extract data safely as dataURL\n          const dataUrl = canvas.toDataURL(mimeType, 0.92);\n\n          const pageObj = {\n            pageNum: pageNum,\n            dataUrl: dataUrl,\n            width: canvas.width,\n            height: canvas.height,\n            format: format,\n            selected: true\n          };\n\n          appState.renderedPages.push(pageObj);\n          appendCardToGallery(pageObj);\n\n          \/\/ 7. Explicit cleanup: zero canvas dimensions to free mobile\/browser memory immediately\n          canvas.width = 0;\n          canvas.height = 0;\n\n        } catch (pageErr) {\n          console.error(`Error rendering page ${pageNum}:`, pageErr);\n          appendErrorCard(pageNum, pageErr.message);\n        }\n\n        processedCount++;\n        updateProgress(\n          processedCount,\n          targetPages.length,\n          `Rendered Page ${pageNum} of ${targetPages.length}`\n        );\n      }\n\n      updateProgress(targetPages.length, targetPages.length, 'All pages converted successfully!');\n      setTimeout(() => {\n        progressBarContainer.classList.add('hidden');\n        appState.isRendering = false;\n      }, 600);\n\n      showToast(`Finished rendering ${appState.renderedPages.length} pages.`, 'success');\n    }\n\n    function updateProgress(curr, total, label) {\n      const pct = total > 0 ? Math.round((curr \/ total) * 100) : 0;\n      progressBarLabel.innerHTML = `<i class=\"fa-solid fa-circle-notch fa-spin\"><\/i> ${label}`;\n      progressBarPercent.textContent = `${pct}%`;\n      progressBarFill.style.width = `${pct}%`;\n    }\n\n    \/\/ Append Individual Page Card\n    function appendCardToGallery(page) {\n      const card = document.createElement('div');\n      card.id = `card-page-${page.pageNum}`;\n      card.className = 'page-card group bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl overflow-hidden shadow-xs hover:shadow-md transition-all flex flex-col';\n\n      card.innerHTML = `\n        <div class=\"p-3 bg-slate-50 dark:bg-slate-800\/60 border-b border-slate-100 dark:border-slate-800 flex items-center justify-between text-xs\">\n          <div class=\"flex items-center gap-2\">\n            <input type=\"checkbox\" checked class=\"page-checkbox accent-tealBrand-600 rounded cursor-pointer\" data-page=\"${page.pageNum}\">\n            <span class=\"font-bold text-slate-800 dark:text-slate-200\">Page ${page.pageNum}<\/span>\n          <\/div>\n          <span class=\"text-[10px] text-slate-400 font-mono\">${page.width} &times; ${page.height}px<\/span>\n        <\/div>\n\n        <div class=\"p-3 flex-1 flex items-center justify-center checker-pattern relative overflow-hidden group cursor-pointer\" onclick=\"openFullscreenModal(${page.pageNum})\">\n          <img decoding=\"async\" src=\"${page.dataUrl}\" alt=\"Page ${page.pageNum}\" class=\"max-h-64 object-contain rounded shadow-xs group-hover:scale-[1.03] transition-transform duration-200\" \/>\n          <div class=\"absolute inset-0 bg-slate-950\/40 opacity-0 group-hover:opacity-100 flex items-center justify-center gap-2 transition-opacity\">\n            <span class=\"px-3 py-1.5 rounded-xl bg-white\/95 text-slate-900 text-xs font-bold shadow flex items-center gap-1.5\">\n              <i class=\"fa-solid fa-expand text-[11px]\"><\/i> Full View\n            <\/span>\n          <\/div>\n        <\/div>\n\n        <div class=\"p-3 bg-white dark:bg-slate-900 border-t border-slate-100 dark:border-slate-800 flex items-center gap-2\">\n          <button onclick=\"downloadSinglePage(${page.pageNum})\" class=\"flex-1 py-1.5 px-2 rounded-xl bg-slate-100 hover:bg-tealBrand-50 hover:text-tealBrand-700 dark:bg-slate-800 dark:hover:bg-slate-700 dark:hover:text-tealBrand-300 text-slate-700 dark:text-slate-200 text-xs font-bold flex items-center justify-center gap-1.5 transition-colors\">\n            <i class=\"fa-solid fa-download text-[11px]\"><\/i> Save ${page.format.toUpperCase()}\n          <\/button>\n          <button onclick=\"copyImageClipboard(${page.pageNum})\" class=\"p-2 rounded-xl text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-800 dark:hover:text-slate-200 text-xs\" title=\"Copy image to clipboard\">\n            <i class=\"fa-regular fa-copy\"><\/i>\n          <\/button>\n        <\/div>\n      `;\n\n      card.querySelector('.page-checkbox').addEventListener('change', (e) => {\n        const found = appState.renderedPages.find(p => p.pageNum === page.pageNum);\n        if (found) found.selected = e.target.checked;\n      });\n\n      pagesContainer.appendChild(card);\n    }\n\n    function appendErrorCard(pageNum, errMsg) {\n      const card = document.createElement('div');\n      card.className = 'p-4 rounded-2xl border border-rose-200 dark:border-rose-900 bg-rose-50\/50 dark:bg-rose-950\/20 text-rose-700 dark:text-rose-400 text-xs flex flex-col gap-2 justify-center items-center text-center';\n      card.innerHTML = `\n        <i class=\"fa-solid fa-triangle-exclamation text-xl\"><\/i>\n        <span class=\"font-bold\">Page ${pageNum} Error<\/span>\n        <span class=\"text-[11px] opacity-80\">${errMsg || 'Could not render page'}<\/span>\n      `;\n      pagesContainer.appendChild(card);\n    }\n\n    \/\/ Reapply Settings button\n    reapplyBtn.addEventListener('click', () => {\n      startSequentialConversion();\n    });\n\n    \/\/ Single Page Download\n    window.downloadSinglePage = function(pageNum) {\n      const page = appState.renderedPages.find(p => p.pageNum === pageNum);\n      if (!page) return;\n\n      const ext = page.format || 'png';\n      const a = document.createElement('a');\n      a.href = page.dataUrl;\n      a.download = `${appState.fileName}_page_${page.pageNum}.${ext}`;\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n\n      showToast(`Page ${page.pageNum} saved!`, 'success');\n    };\n\n    \/\/ Copy to Clipboard\n    window.copyImageClipboard = async function(pageNum) {\n      const page = appState.renderedPages.find(p => p.pageNum === pageNum);\n      if (!page) return;\n\n      try {\n        const res = await fetch(page.dataUrl);\n        const blob = await res.blob();\n        \n        if (window.ClipboardItem) {\n          \/\/ If JPEG\/WebP, convert to PNG Blob for maximum OS clipboard compatibility\n          if (blob.type !== 'image\/png') {\n            const img = new Image();\n            img.src = page.dataUrl;\n            await img.decode();\n            const tempCanvas = document.createElement('canvas');\n            tempCanvas.width = img.width;\n            tempCanvas.height = img.height;\n            const ctx = tempCanvas.getContext('2d');\n            ctx.drawImage(img, 0, 0);\n            tempCanvas.toBlob(async (pngBlob) => {\n              await navigator.clipboard.write([new ClipboardItem({ 'image\/png': pngBlob })]);\n              showToast(`Page ${pageNum} copied to clipboard!`, 'success');\n            }, 'image\/png');\n          } else {\n            await navigator.clipboard.write([new ClipboardItem({ 'image\/png': blob })]);\n            showToast(`Page ${pageNum} copied to clipboard!`, 'success');\n          }\n        } else {\n          showToast('Clipboard image copy is not supported in this browser.', 'warning');\n        }\n      } catch (err) {\n        console.warn('Clipboard failed:', err);\n        showToast('Clipboard access denied. Please use the Save button.', 'warning');\n      }\n    };\n\n    \/\/ Select\/Deselect All\n    let allSelectedState = true;\n    selectAllPagesBtn.addEventListener('click', () => {\n      allSelectedState = !allSelectedState;\n      appState.renderedPages.forEach(p => p.selected = allSelectedState);\n      document.querySelectorAll('.page-checkbox').forEach(cb => cb.checked = allSelectedState);\n      selectAllPagesBtn.innerHTML = allSelectedState \n        ? '<i class=\"fa-regular fa-square-check mr-1.5\"><\/i> Deselect All' \n        : '<i class=\"fa-regular fa-square mr-1.5\"><\/i> Select All For Export';\n    });\n\n    \/\/ Batch Download All as ZIP\n    downloadAllZipBtn.addEventListener('click', async () => {\n      const selected = appState.renderedPages.filter(p => p.selected);\n      if (selected.length === 0) {\n        showToast('No pages selected to export.', 'warning');\n        return;\n      }\n\n      progressBarContainer.classList.remove('hidden');\n      updateProgress(0, selected.length, 'Creating ZIP archive...');\n\n      try {\n        const zip = new JSZip();\n        const folder = zip.folder(`${appState.fileName}_images`);\n\n        let count = 0;\n        for (const item of selected) {\n          const base64Data = item.dataUrl.split(',')[1];\n          const ext = item.format || 'png';\n          const filename = `page_${String(item.pageNum).padStart(3, '0')}.${ext}`;\n          folder.file(filename, base64Data, { base64: true });\n          count++;\n          updateProgress(count, selected.length, `Archiving page ${count} of ${selected.length}...`);\n        }\n\n        updateProgress(selected.length, selected.length, 'Compressing archive...');\n\n        const zipBlob = await zip.generateAsync({ type: 'blob' }, (meta) => {\n          updateProgress(meta.percent, 100, `Compressing: ${Math.round(meta.percent)}%`);\n        });\n\n        const url = URL.createObjectURL(zipBlob);\n        const a = document.createElement('a');\n        a.href = url;\n        a.download = `${appState.fileName}_all_pages.zip`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n\n        showToast(`Downloaded ZIP archive with ${selected.length} images!`, 'success');\n      } catch (err) {\n        console.error('ZIP Error:', err);\n        showToast('Error creating ZIP file: ' + err.message, 'error');\n      } finally {\n        setTimeout(() => progressBarContainer.classList.add('hidden'), 500);\n      }\n    });\n\n    \/\/ Fullscreen Preview Modal\n    window.openFullscreenModal = function(pageNum) {\n      const page = appState.renderedPages.find(p => p.pageNum === pageNum);\n      if (!page) return;\n\n      appState.modalPageNum = pageNum;\n      appState.modalZoom = 1.0;\n      appState.modalRotation = 0;\n\n      modalPageTitle.textContent = `Page ${pageNum} Preview (${page.width} \u00d7 ${page.height}px)`;\n      modalPreviewImage.src = page.dataUrl;\n      applyModalTransform();\n\n      viewerModal.classList.remove('hidden');\n      viewerModal.classList.add('flex');\n    };\n\n    function applyModalTransform() {\n      modalPreviewImage.style.transform = `scale(${appState.modalZoom}) rotate(${appState.modalRotation}deg)`;\n    }\n\n    closeModalBtn.addEventListener('click', () => {\n      viewerModal.classList.add('hidden');\n      viewerModal.classList.remove('flex');\n    });\n\n    viewerModal.addEventListener('click', (e) => {\n      if (e.target === viewerModal) {\n        viewerModal.classList.add('hidden');\n        viewerModal.classList.remove('flex');\n      }\n    });\n\n    modalRotateBtn.addEventListener('click', () => {\n      appState.modalRotation = (appState.modalRotation + 90) % 360;\n      applyModalTransform();\n    });\n\n    modalZoomInBtn.addEventListener('click', () => {\n      if (appState.modalZoom < 3.0) {\n        appState.modalZoom += 0.25;\n        applyModalTransform();\n      }\n    });\n\n    modalZoomOutBtn.addEventListener('click', () => {\n      if (appState.modalZoom > 0.5) {\n        appState.modalZoom -= 0.25;\n        applyModalTransform();\n      }\n    });\n\n    modalDownloadBtn.addEventListener('click', () => {\n      downloadSinglePage(appState.modalPageNum);\n    });\n\n    \/\/ Clear Workspace\n    clearAllBtn.addEventListener('click', () => {\n      appState.pdfDoc = null;\n      appState.rawBytes = null;\n      appState.renderedPages = [];\n      pagesContainer.innerHTML = '';\n      workspaceSection.classList.add('hidden');\n      uploadSection.classList.remove('hidden');\n      clearAllBtn.classList.add('hidden');\n      fileInput.value = '';\n      changeFileInput.value = '';\n      showToast('Workspace reset.', 'info');\n    });\n\n    \/\/ Built-in 3-Page Test PDF Generator (Pure client-side raw minimal PDF builder)\n    \/\/ Allows testing multi-page rendering (Page 1, 2, 3) immediately with zero dependencies\n    generateSampleBtn.addEventListener('click', async () => {\n      showToast('Generating sample 3-page test PDF...', 'info');\n\n      \/\/ Construct a valid minimal 3-page PDF binary in memory\n      const samplePdfSource = `%PDF-1.4\n1 0 obj\n<< \/Type \/Catalog \/Pages 2 0 R >>\nendobj\n2 0 obj\n<< \/Type \/Pages \/Kids [3 0 R 4 0 R 5 0 R] \/Count 3 >>\nendobj\n3 0 obj\n<< \/Type \/Page \/Parent 2 0 R \/MediaBox [0 0 612 792] \/Contents 6 0 R \/Resources << \/Font << \/F1 9 0 R >> >> >>\nendobj\n4 0 obj\n<< \/Type \/Page \/Parent 2 0 R \/MediaBox [0 0 612 792] \/Contents 7 0 R \/Resources << \/Font << \/F1 9 0 R >> >> >>\nendobj\n5 0 obj\n<< \/Type \/Page \/Parent 2 0 R \/MediaBox [0 0 612 792] \/Contents 8 0 R \/Resources << \/Font << \/F1 9 0 R >> >> >>\nendobj\n6 0 obj\n<< \/Length 176 >>\nstream\n0.08 0.58 0.53 rg\n0 0 612 792 re f\n1 1 1 rg\nBT\n\/F1 36 Tf\n60 680 Td\n(srabons.de Test PDF) Tj\n\/F1 20 Tf\n0 -60 Td\n(Page 1 of 3 - Verified First Page) Tj\n\/F1 14 Tf\n0 -40 Td\n(Sequential processing ensures all pages follow smoothly.) Tj\nET\nendstream\nendobj\n7 0 obj\n<< \/Length 174 >>\nstream\n0.1 0.3 0.6 rg\n0 0 612 792 re f\n1 1 1 rg\nBT\n\/F1 36 Tf\n60 680 Td\n(srabons.de Test PDF) Tj\n\/F1 20 Tf\n0 -60 Td\n(Page 2 of 3 - Verified Second Page) Tj\n\/F1 14 Tf\n0 -40 Td\n(Page 2 renders immediately with no stalls or race conditions.) Tj\nET\nendstream\nendobj\n8 0 obj\n<< \/Length 175 >>\nstream\n0.4 0.1 0.5 rg\n0 0 612 792 re f\n1 1 1 rg\nBT\n\/F1 36 Tf\n60 680 Td\n(srabons.de Test PDF) Tj\n\/F1 20 Tf\n0 -60 Td\n(Page 3 of 3 - Verified Third Page) Tj\n\/F1 14 Tf\n0 -40 Td\n(All pages processed safely in sequential order.) Tj\nET\nendstream\nendobj\n9 0 obj\n<< \/Type \/Font \/Subtype \/Type1 \/BaseFont \/Helvetica >>\nendobj\nxref\n0 10\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n0000000133 00000 n \n0000000257 00000 n \n0000000381 00000 n \n0000000505 00000 n \n0000000732 00000 n \n0000000957 00000 n \n0000001183 00000 n \ntrailer\n<< \/Size 10 \/Root 1 0 R >>\nstartxref\n1254\n%%EOF`;\n\n      const encoder = new TextEncoder();\n      const sampleBytes = encoder.encode(samplePdfSource);\n\n      appState.fileName = 'srabons_demo_sample';\n      appState.fileSize = sampleBytes.length;\n      docFileName.textContent = 'srabons_demo_sample.pdf';\n      docFileSize.innerHTML = `<i class=\"fa-solid fa-hard-drive mr-1\"><\/i>${formatBytes(sampleBytes.length)}`;\n\n      await initPdfDocument(sampleBytes);\n    });\n\n    \/\/ Start with theme check\n    initTheme();\n  <\/script>\n","protected":false},"excerpt":{"rendered":"<p>srabons.de | PDF to Image Converter srabons.de PDF to Image Converter Local Client-Side Execution Try Sample PDF Reset Drop your PDF document here Instantly convert all pages into sharp PNG, JPEG, or WebP images. Every page renders reliably in sequence directly on your device. Choose PDF File No server uploads Multi-page safe rendering Batch ZIP [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_kb_optimizer_status":1,"footnotes":""},"class_list":["post-1027","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/pages\/1027","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/comments?post=1027"}],"version-history":[{"count":3,"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/pages\/1027\/revisions"}],"predecessor-version":[{"id":1031,"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/pages\/1027\/revisions\/1031"}],"wp:attachment":[{"href":"https:\/\/srabons.de\/en\/wp-json\/wp\/v2\/media?parent=1027"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}