{"id":4600,"date":"2025-01-01T21:09:57","date_gmt":"2025-01-01T15:39:57","guid":{"rendered":"https:\/\/rajmedical.co.in\/?p=4600"},"modified":"2026-06-21T21:09:58","modified_gmt":"2026-06-21T15:39:58","slug":"image-to-pdf-converter","status":"publish","type":"post","link":"https:\/\/rajmedical.co.in\/hi\/image-to-pdf-converter\/","title":{"rendered":"Image to PDF Converter"},"content":{"rendered":"<!doctype html>\r\n<html lang=\"en\">\r\n<head>\r\n<meta charset=\"utf-8\" \/>\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\r\n<title>Contact \u2014 image \u21c4 PDF converter<\/title>\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Big+Shoulders+Display:wght@600;700;800&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap\" rel=\"stylesheet\">\r\n<style>\r\n\r\n  :root{\r\n    --bg: #1c1b19;\r\n    --panel: #242220;\r\n    --panel-2: #2b2925;\r\n    --border: #3a3733;\r\n    --paper: #f2ede4;\r\n    --paper-dim: #c9c2b4;\r\n    --steel: #8fa3a8;\r\n    --steel-dim: #6c7a7e;\r\n    --safelight: #ff6a2b;\r\n    --safelight-dim: #c9531f;\r\n    --good: #6fcf7a;\r\n    --warn: #ffb84d;\r\n    --radius: 4px;\r\n  }\r\n  *{ box-sizing: border-box; }\r\n  html,body{ margin:0; padding:0; }\r\n  .wrap{\r\n    background: var(--bg);\r\n    color: var(--paper);\r\n    font-family: 'IBM Plex Sans', sans-serif;\r\n    -webkit-font-smoothing: antialiased;\r\n    min-height: 100vh;\r\n  }\r\n  .mono{ font-family: 'IBM Plex Mono', monospace; }\r\n  a{ color: inherit; }\r\n  button{ font-family: inherit; }\r\n  ::selection{ background: var(--safelight); color: #1c1b19; }\r\n\r\n  :focus-visible{ outline: 2px solid var(--safelight); outline-offset: 2px; }\r\n\r\n  .wrap{\r\n    max-width: 980px;\r\n    margin: 0 auto;\r\n    padding: 56px 24px 80px;\r\n  }\r\n\r\n  \/* ---------- Header ---------- *\/\r\n  header{ margin-bottom: 40px; }\r\n  .eyebrow{\r\n    font-size: 11px;\r\n    letter-spacing: 0.16em;\r\n    text-transform: uppercase;\r\n    color: var(--steel);\r\n    margin: 0 0 10px;\r\n  }\r\n  .eyebrow .dot{ color: var(--safelight); }\r\n  h1.wordmark{\r\n    font-family: 'Big Shoulders Display', sans-serif;\r\n    font-weight: 800;\r\n    font-size: clamp(56px, 11vw, 96px);\r\n    line-height: 0.86;\r\n    letter-spacing: -0.01em;\r\n    margin: 0 0 16px;\r\n    color: var(--paper);\r\n    opacity: 0;\r\n    transform: translateY(10px);\r\n    animation: rise 0.6s ease forwards;\r\n  }\r\n  h1.wordmark span{ color: var(--safelight); }\r\n  @keyframes rise{ to{ opacity:1; transform: translateY(0); } }\r\n  .tagline{\r\n    font-size: 16px;\r\n    line-height: 1.55;\r\n    color: var(--paper-dim);\r\n    max-width: 52ch;\r\n    margin: 0;\r\n  }\r\n\r\n  \/* ---------- Dropzone ---------- *\/\r\n  .dropzone{\r\n    position: relative;\r\n    border: 2px dashed var(--border);\r\n    border-radius: var(--radius);\r\n    padding: 64px 24px;\r\n    text-align: center;\r\n    cursor: pointer;\r\n    transition: border-color 0.15s ease, background-color 0.15s ease;\r\n    margin-bottom: 28px;\r\n  }\r\n  .dropzone:hover{ border-color: #545049; }\r\n  .dropzone.active{\r\n    border-color: var(--safelight);\r\n    background: rgba(255,106,43,0.06);\r\n  }\r\n  .dropzone .corner{\r\n    position: absolute;\r\n    width: 18px; height: 18px;\r\n    border: 0 solid var(--safelight);\r\n    opacity: 0.85;\r\n  }\r\n  .dropzone .corner.tl{ top:10px; left:10px; border-top-width:2px; border-left-width:2px; }\r\n  .dropzone .corner.tr{ top:10px; right:10px; border-top-width:2px; border-right-width:2px; }\r\n  .dropzone .corner.bl{ bottom:10px; left:10px; border-bottom-width:2px; border-left-width:2px; }\r\n  .dropzone .corner.br{ bottom:10px; right:10px; border-bottom-width:2px; border-right-width:2px; }\r\n  .dropzone svg{ color: var(--steel); margin-bottom: 14px; }\r\n  .dropzone .dz-title{ font-size: 19px; font-weight: 500; margin: 0 0 8px; color: var(--paper); }\r\n  .dropzone .dz-sub{ font-size: 12px; color: var(--steel); letter-spacing: 0.02em; }\r\n  .dropzone input[type=file]{ display:none; }\r\n\r\n  \/* ---------- Global stats \/ controls bar ---------- *\/\r\n  .statsbar{\r\n    display:none;\r\n    background: var(--panel);\r\n    border: 1px solid var(--border);\r\n    border-radius: var(--radius);\r\n    padding: 14px 18px;\r\n    margin-bottom: 20px;\r\n    align-items: center;\r\n    justify-content: space-between;\r\n    gap: 16px;\r\n    flex-wrap: wrap;\r\n  }\r\n  .statsbar.show{ display:flex; }\r\n  .statsbar .totals{ font-size: 13px; color: var(--paper-dim); }\r\n  .statsbar .totals b{ color: var(--paper); font-weight: 600; }\r\n  .statsbar .totals .pct{ color: var(--good); font-weight: 600; }\r\n  .statsbar .actions{ display:flex; gap:8px; flex-wrap: wrap; align-items:center; }\r\n\r\n  select.mini, .num-input{\r\n    background: var(--panel-2);\r\n    border: 1px solid var(--border);\r\n    color: var(--paper);\r\n    font-family: 'IBM Plex Mono', monospace;\r\n    font-size: 12px;\r\n    padding: 6px 8px;\r\n    border-radius: var(--radius);\r\n  }\r\n\r\n  .btn{\r\n    font-family: 'IBM Plex Mono', monospace;\r\n    font-size: 11px;\r\n    letter-spacing: 0.06em;\r\n    text-transform: uppercase;\r\n    padding: 8px 14px;\r\n    border-radius: var(--radius);\r\n    border: 1px solid var(--border);\r\n    background: transparent;\r\n    color: var(--paper-dim);\r\n    cursor: pointer;\r\n    transition: all 0.12s ease;\r\n  }\r\n  .btn:hover{ border-color: var(--steel); color: var(--paper); }\r\n  .btn.primary{\r\n    background: var(--safelight);\r\n    border-color: var(--safelight);\r\n    color: #1c1503;\r\n    font-weight: 600;\r\n  }\r\n  .btn.primary:hover{ background: #ff7d45; }\r\n  .btn:disabled{ opacity:0.35; cursor: not-allowed; }\r\n\r\n  \/* ---------- Cards ---------- *\/\r\n  .cards{ display:flex; flex-direction: column; gap: 16px; }\r\n  .card{\r\n    background: var(--panel);\r\n    border: 1px solid var(--border);\r\n    border-radius: var(--radius);\r\n    padding: 18px;\r\n    position: relative;\r\n    opacity: 0;\r\n    transform: translateY(6px);\r\n    animation: cardin 0.25s ease forwards;\r\n  }\r\n  @keyframes cardin{ to{ opacity:1; transform: translateY(0); } }\r\n  .card.skeleton .card-grid{ opacity: 0.4; }\r\n  .card.error{ border-color: var(--safelight-dim); }\r\n  .card .remove{\r\n    position:absolute; top:12px; right:12px;\r\n    background:none; border:none; color: var(--steel-dim);\r\n    font-size: 18px; line-height:1; cursor:pointer; padding: 4px;\r\n  }\r\n  .card .remove:hover{ color: var(--safelight); }\r\n\r\n  .card-grid{\r\n    display:grid;\r\n    grid-template-columns: 150px 1fr;\r\n    gap: 20px;\r\n  }\r\n  @media (max-width: 640px){\r\n    .card-grid{ grid-template-columns: 1fr; }\r\n  }\r\n\r\n  .thumb-wrap{\r\n    width:100%;\r\n    aspect-ratio: 1\/1;\r\n    border-radius: 2px;\r\n    border: 1px solid var(--border);\r\n    overflow:hidden;\r\n    background-image:\r\n      linear-gradient(45deg, #2b2925 25%, transparent 25%),\r\n      linear-gradient(-45deg, #2b2925 25%, transparent 25%),\r\n      linear-gradient(45deg, transparent 75%, #2b2925 75%),\r\n      linear-gradient(-45deg, transparent 75%, #2b2925 75%);\r\n    background-size: 16px 16px;\r\n    background-position: 0 0, 0 8px, 8px -8px, -8px 0px;\r\n    background-color: #1f1e1b;\r\n  }\r\n  .thumb-wrap img{ width:100%; height:100%; object-fit: contain; display:block; }\r\n\r\n  .card-head{ display:flex; align-items:flex-start; justify-content:space-between; gap: 10px; margin-bottom: 14px; padding-right: 20px; }\r\n  .card-name{ font-size: 13px; color: var(--paper); word-break: break-all; margin: 0 0 6px; }\r\n  .pill-row{ display:flex; gap:8px; flex-wrap: wrap; align-items:center; }\r\n  .pill{\r\n    font-size: 10px;\r\n    letter-spacing: 0.05em;\r\n    text-transform: uppercase;\r\n    border: 1px solid var(--steel-dim);\r\n    color: var(--steel);\r\n    padding: 2px 6px;\r\n    border-radius: 2px;\r\n  }\r\n  .meta-text{ font-size: 11px; color: var(--steel); }\r\n\r\n  \/* Quality control *\/\r\n  .ctrl-block{ margin-bottom: 16px; }\r\n  .ctrl-label{\r\n    font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;\r\n    color: var(--steel); display:flex; justify-content: space-between; margin-bottom: 8px;\r\n  }\r\n  .ctrl-label .val{ color: var(--paper); }\r\n  input[type=range]{\r\n    -webkit-appearance: none;\r\n    width: 100%;\r\n    height: 4px;\r\n    background: var(--border);\r\n    border-radius: 2px;\r\n    margin: 0;\r\n  }\r\n  input[type=range]::-webkit-slider-thumb{\r\n    -webkit-appearance: none;\r\n    width: 15px; height: 15px;\r\n    border-radius: 50%;\r\n    background: var(--safelight);\r\n    cursor: pointer;\r\n    border: 2px solid #1c1b19;\r\n    box-shadow: 0 0 0 1px var(--safelight);\r\n  }\r\n  input[type=range]::-moz-range-thumb{\r\n    width: 15px; height: 15px;\r\n    border-radius: 50%;\r\n    background: var(--safelight);\r\n    cursor: pointer;\r\n    border: 2px solid #1c1b19;\r\n  }\r\n  .stops-row{\r\n    display:flex; justify-content: space-between;\r\n    margin-top: 6px;\r\n    font-size: 10px;\r\n    color: var(--steel-dim);\r\n    font-family: 'IBM Plex Mono', monospace;\r\n  }\r\n  .stops-row span{ flex: 1; text-align:center; }\r\n  .stops-row span:first-child{ text-align:left; }\r\n  .stops-row span:last-child{ text-align:right; }\r\n  .stops-row span.active{ color: var(--safelight); font-weight: 600; }\r\n\r\n  \/* Format chips *\/\r\n  .chip-row{ display:flex; gap:8px; flex-wrap: wrap; }\r\n  .chip{\r\n    flex: 1 1 90px;\r\n    position: relative;\r\n    border: 1px solid var(--border);\r\n    background: var(--panel-2);\r\n    border-radius: var(--radius);\r\n    padding: 8px 10px;\r\n    text-align:left;\r\n    cursor: pointer;\r\n    transition: border-color 0.12s ease, background-color 0.12s ease;\r\n  }\r\n  .chip:hover:not(:disabled){ border-color: var(--steel); }\r\n  .chip.active{ border-color: var(--safelight); background: rgba(255,106,43,0.08); }\r\n  .chip:disabled{ opacity: 0.32; cursor: not-allowed; }\r\n  .chip .fmt{ display:block; font-family:'IBM Plex Mono', monospace; font-size: 11px; font-weight:600; letter-spacing:0.04em; color: var(--paper); text-transform: uppercase; }\r\n  .chip .sz{ display:block; font-family:'IBM Plex Mono', monospace; font-size: 11px; color: var(--steel); margin-top:3px; }\r\n  .chip.active .fmt{ color: var(--safelight); }\r\n  .chip .badge{\r\n    position:absolute; top:-7px; right:6px;\r\n    font-size: 8px; letter-spacing: 0.05em; text-transform: uppercase;\r\n    background: var(--good); color: #0e1f10; padding: 1px 4px; border-radius: 2px; font-weight:700;\r\n  }\r\n\r\n  \/* Resize row *\/\r\n  .resize-row{ display:flex; align-items:center; gap:10px; margin-top: 14px; font-size: 12px; color: var(--paper-dim); flex-wrap: wrap; }\r\n  .resize-row label.cb{ display:flex; align-items:center; gap:7px; cursor:pointer; }\r\n  input[type=checkbox]{\r\n    appearance: none; width:15px; height:15px; border:1px solid var(--steel-dim); border-radius:2px;\r\n    background: var(--panel-2); cursor:pointer; position: relative; flex-shrink:0;\r\n  }\r\n  input[type=checkbox]:checked{ background: var(--safelight); border-color: var(--safelight); }\r\n  input[type=checkbox]:checked::after{\r\n    content:''; position:absolute; left:4px; top:1px; width:4px; height:8px;\r\n    border: solid #1c1b19; border-width: 0 2px 2px 0; transform: rotate(45deg);\r\n  }\r\n  .resize-row .num-input{ width: 80px; }\r\n  .resize-fields{ display:flex; align-items:center; gap:8px; }\r\n  .resize-fields[hidden]{ display:none; }\r\n\r\n  \/* Result row *\/\r\n  .result-row{\r\n    margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border);\r\n    display:flex; align-items:center; justify-content: space-between; gap: 14px; flex-wrap: wrap;\r\n  }\r\n  .result-figures{ display:flex; align-items:baseline; gap: 12px; flex-wrap: wrap; }\r\n  .fig{ }\r\n  .fig .flabel{ font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--steel); display:block; margin-bottom: 3px; }\r\n  .fig .fval{ font-family: 'IBM Plex Mono', monospace; font-size: 14px; color: var(--steel); }\r\n  .fig.out .fval{ font-family: 'Big Shoulders Display', sans-serif; font-size: 26px; font-weight: 700; color: var(--paper); }\r\n  .arrow{ color: var(--steel-dim); font-size: 16px; }\r\n  .pct-badge{\r\n    font-family: 'IBM Plex Mono', monospace; font-size: 12px; font-weight: 600;\r\n    padding: 3px 8px; border-radius: 3px;\r\n  }\r\n  .pct-badge.down{ color: var(--good); background: rgba(111,207,122,0.12); }\r\n  .pct-badge.up{ color: var(--warn); background: rgba(255,184,77,0.12); }\r\n\r\n  .err-msg{ font-size: 13px; color: var(--warn); margin: 4px 0 0; }\r\n\r\n  \/* ---------- Footer ---------- *\/\r\n  footer{ margin-top: 56px; text-align:center; }\r\n  footer p{ font-size: 11px; color: var(--steel-dim); letter-spacing: 0.02em; line-height:1.6; }\r\n\r\n  @media (prefers-reduced-motion: reduce){\r\n    *{ animation: none !important; transition: none !important; }\r\n  }\r\n\r\n  \/* ---------- Tabs ---------- *\/\r\n  .tabs{ display:flex; gap:8px; margin-bottom: 24px; border-bottom: 1px solid var(--border); }\r\n  .tab{\r\n    font-family: 'IBM Plex Mono', monospace;\r\n    font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase;\r\n    background: none; border: none; color: var(--steel);\r\n    padding: 12px 4px 14px; margin-right: 22px; cursor: pointer;\r\n    border-bottom: 2px solid transparent; transform: translateY(1px);\r\n  }\r\n  .tab .n{ color: var(--steel-dim); margin-right: 6px; }\r\n  .tab:hover{ color: var(--paper-dim); }\r\n  .tab.active{ color: var(--paper); border-bottom-color: var(--safelight); }\r\n  .tab.active .n{ color: var(--safelight); }\r\n  .mode-panel{ display:none; }\r\n  .mode-panel.active{ display:block; }\r\n\r\n  \/* ---------- Settings panel ---------- *\/\r\n  .settings-panel{\r\n    background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);\r\n    padding: 18px; margin-bottom: 20px;\r\n  }\r\n  .settings-title{ font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--steel); margin: 0 0 14px; }\r\n  .settings-grid{ display:grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }\r\n  @media (max-width: 760px){ .settings-grid{ grid-template-columns: repeat(2,1fr); } }\r\n  .field label{ display:block; font-size: 10px; letter-spacing:0.08em; text-transform: uppercase; color: var(--steel); margin-bottom: 7px; }\r\n  .field select, .field input[type=text]{\r\n    width: 100%; background: var(--panel-2); border: 1px solid var(--border); color: var(--paper);\r\n    font-family: 'IBM Plex Mono', monospace; font-size: 12px; padding: 8px 9px; border-radius: var(--radius);\r\n  }\r\n  .field-note{ font-size: 10px; color: var(--steel-dim); margin-top: 6px; }\r\n\r\n  \/* ---------- Frame grid (image -> pdf source list) ---------- *\/\r\n  .frame-grid{ display:grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 12px; margin-bottom: 20px; }\r\n  .frame{\r\n    background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden;\r\n    opacity:0; transform: translateY(6px); animation: cardin 0.25s ease forwards;\r\n  }\r\n  .frame-thumb{\r\n    position:relative; aspect-ratio: 1\/1;\r\n    background-image:\r\n      linear-gradient(45deg, #2b2925 25%, transparent 25%),\r\n      linear-gradient(-45deg, #2b2925 25%, transparent 25%),\r\n      linear-gradient(45deg, transparent 75%, #2b2925 75%),\r\n      linear-gradient(-45deg, transparent 75%, #2b2925 75%);\r\n    background-size: 14px 14px;\r\n    background-position: 0 0, 0 7px, 7px -7px, -7px 0px;\r\n    background-color: #1f1e1b;\r\n  }\r\n  .frame-thumb img{ width:100%; height:100%; object-fit: contain; display:block; }\r\n  .frame-num{\r\n    position:absolute; top:6px; left:6px;\r\n    font-family:'IBM Plex Mono', monospace; font-size: 10px; font-weight:700;\r\n    background: var(--safelight); color:#1c1503; padding: 1px 5px; border-radius: 2px; letter-spacing: 0.04em;\r\n  }\r\n  .frame-meta{ padding: 7px 8px; font-size: 10px; color: var(--steel); font-family:'IBM Plex Mono', monospace; line-height:1.5; }\r\n  .frame-meta .fname{ color: var(--paper-dim); font-family:'IBM Plex Sans', sans-serif; font-size: 11px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:2px; }\r\n  .frame-ctrls{ display:flex; border-top: 1px solid var(--border); }\r\n  .frame-ctrls button{\r\n    flex:1; background:none; border:none; color: var(--steel); padding: 6px 0; font-size: 12px; cursor:pointer;\r\n    border-right: 1px solid var(--border);\r\n  }\r\n  .frame-ctrls button:last-child{ border-right:none; }\r\n  .frame-ctrls button:hover:not(:disabled){ color: var(--safelight); background: var(--panel-2); }\r\n  .frame-ctrls button:disabled{ opacity: 0.3; cursor: default; }\r\n\r\n  \/* ---------- Build result panel (image -> pdf) ---------- *\/\r\n  .build-panel{\r\n    background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);\r\n    padding: 18px; display:flex; align-items:center; justify-content: space-between; gap:16px; flex-wrap: wrap;\r\n  }\r\n  .build-status{ font-family:'IBM Plex Mono', monospace; font-size: 12px; color: var(--steel); }\r\n  .build-status b{ color: var(--paper); }\r\n  .build-status .pct{ color: var(--good); }\r\n\r\n  \/* ---------- PDF source cards (pdf -> image) ---------- *\/\r\n  .pdf-card{\r\n    background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);\r\n    padding: 18px; margin-bottom: 18px;\r\n    opacity:0; transform: translateY(6px); animation: cardin 0.25s ease forwards;\r\n  }\r\n  .pdf-card-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; margin-bottom:16px; }\r\n  .pdf-card-head .pdf-name{ font-size: 14px; color: var(--paper); margin: 0 0 6px; word-break: break-all; }\r\n  .pdf-controls{ display:grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 16px; }\r\n  @media (max-width: 700px){ .pdf-controls{ grid-template-columns: 1fr; } }\r\n  .range-field input[type=text]{\r\n    width:100%; background: var(--panel-2); border:1px solid var(--border); color: var(--paper);\r\n    font-family:'IBM Plex Mono', monospace; font-size:12px; padding: 8px 9px; border-radius: var(--radius);\r\n  }\r\n  .progress-line{ font-family:'IBM Plex Mono', monospace; font-size: 11px; color: var(--steel); margin: 4px 0 0; min-height: 14px; }\r\n  .page-grid{ display:grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 12px; margin-top: 16px; }\r\n  .page-tile{\r\n    background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius); overflow:hidden;\r\n    opacity:0; transform: translateY(6px); animation: cardin 0.25s ease forwards;\r\n  }\r\n  .page-tile .frame-thumb{ background-color:#1f1e1b; }\r\n  .page-tile .page-dl{\r\n    display:block; width:100%; text-align:center; background:none; border:none; border-top:1px solid var(--border);\r\n    color: var(--steel); font-size: 11px; padding: 6px 0; cursor:pointer; font-family:'IBM Plex Mono', monospace;\r\n  }\r\n  .page-tile .page-dl:hover{ color: var(--safelight); background: var(--panel); }\r\n  .extract-row{ display:flex; align-items:center; gap:12px; flex-wrap: wrap; }\r\n<\/style>\r\n<\/head>\r\n<body>\r\n<div class=\"wrap\">\r\n\r\n  <header>\r\n    <p class=\"eyebrow\"><span class=\"dot\">\u25cf<\/span> client-side photo lab<\/p>\r\n    <h1 class=\"wordmark\">CONTA<span>CT<\/span><\/h1>\r\n    <p class=\"tagline\">Lay your images down on a contact sheet and pull out a PDF \u2014 or split a PDF back into individual prints. Everything happens here; nothing is saved in our server.<\/p>\r\n  <\/header>\r\n\r\n  <nav class=\"tabs\" role=\"tablist\">\r\n    <button class=\"tab active\" id=\"tabImg2Pdf\" role=\"tab\" aria-selected=\"true\"><span class=\"n\">01<\/span>Images \u2192 PDF<\/button>\r\n    <button class=\"tab\" id=\"tabPdf2Img\" role=\"tab\" aria-selected=\"false\"><span class=\"n\">02<\/span>PDF \u2192 Images<\/button>\r\n  <\/nav>\r\n\r\n  <!-- ============ MODE 1: IMAGES -> PDF ============ -->\r\n  <section class=\"mode-panel active\" id=\"panelImg2Pdf\">\r\n\r\n    <section class=\"dropzone\" id=\"dzImages\" tabindex=\"0\" role=\"button\" aria-label=\"Upload images\">\r\n      <span class=\"corner tl\"><\/span><span class=\"corner tr\"><\/span><span class=\"corner bl\"><\/span><span class=\"corner br\"><\/span>\r\n      <svg width=\"34\" height=\"34\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\">\r\n        <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"\/>\r\n        <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"\/>\r\n        <path d=\"M21 15l-5-5L5 21\"\/>\r\n      <\/svg>\r\n      <p class=\"dz-title\">Drop images here to build a sheet, or click to browse<\/p>\r\n      <p class=\"dz-sub mono\">JPEG \u00b7 PNG \u00b7 WEBP \u00b7 GIF \u00b7 BMP \u00b7 SVG \u2014 combined into one PDF, in the order shown below<\/p>\r\n      <input type=\"file\" id=\"imgInput\" accept=\"image\/*\" multiple \/>\r\n    <\/section>\r\n\r\n    <div id=\"img2pdfBody\" style=\"display:none;\">\r\n      <div class=\"settings-panel\">\r\n        <p class=\"settings-title\">Sheet settings<\/p>\r\n        <div class=\"settings-grid\">\r\n          <div class=\"field\">\r\n            <label for=\"pageSize\">Page size<\/label>\r\n            <select id=\"pageSize\">\r\n              <option value=\"a4\">A4<\/option>\r\n              <option value=\"letter\">Letter<\/option>\r\n              <option value=\"legal\">Legal<\/option>\r\n              <option value=\"fit\">Fit to image<\/option>\r\n            <\/select>\r\n          <\/div>\r\n          <div class=\"field\" id=\"orientationField\">\r\n            <label for=\"orientation\">Orientation<\/label>\r\n            <select id=\"orientation\">\r\n              <option value=\"auto\">Auto (per image)<\/option>\r\n              <option value=\"portrait\">Portrait<\/option>\r\n              <option value=\"landscape\">Landscape<\/option>\r\n            <\/select>\r\n          <\/div>\r\n          <div class=\"field\" id=\"marginField\">\r\n            <label for=\"margin\">Margin<\/label>\r\n            <select id=\"margin\">\r\n              <option value=\"0\">None<\/option>\r\n              <option value=\"18\">Small<\/option>\r\n              <option value=\"36\" selected>Normal<\/option>\r\n            <\/select>\r\n          <\/div>\r\n          <div class=\"field\">\r\n            <label>Embed as<\/label>\r\n            <div class=\"chip-row\" id=\"embedChips\">\r\n              <button class=\"chip active\" data-embed=\"jpeg\"><span class=\"fmt\">JPEG<\/span><span class=\"sz mono\">compressed<\/span><\/button>\r\n              <button class=\"chip\" data-embed=\"png\"><span class=\"fmt\">PNG<\/span><span class=\"sz mono\">lossless<\/span><\/button>\r\n            <\/div>\r\n          <\/div>\r\n        <\/div>\r\n\r\n        <div class=\"ctrl-block\" id=\"embedQualityBlock\" style=\"margin-top:16px;\">\r\n          <div class=\"ctrl-label\"><span>Compression<\/span><span class=\"val mono\" id=\"embedQLabel\">f\/5.6 \u00b7 75% \u00b7 Balanced<\/span><\/div>\r\n          <input type=\"range\" min=\"0\" max=\"5\" step=\"1\" value=\"2\" id=\"embedQuality\" \/>\r\n          <div class=\"stops-row\" id=\"embedStops\"><\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"frame-grid\" id=\"frameGrid\"><\/div>\r\n\r\n      <div class=\"build-panel\">\r\n        <div class=\"build-status mono\" id=\"buildStatus\">Building sheet\u2026<\/div>\r\n        <button class=\"btn primary\" id=\"downloadPdfBtn\" disabled>Download PDF<\/button>\r\n      <\/div>\r\n    <\/div>\r\n  <\/section>\r\n\r\n  <!-- ============ MODE 2: PDF -> IMAGES ============ -->\r\n  <section class=\"mode-panel\" id=\"panelPdf2Img\">\r\n\r\n    <section class=\"dropzone\" id=\"dzPdf\" tabindex=\"0\" role=\"button\" aria-label=\"Upload PDF\">\r\n      <span class=\"corner tl\"><\/span><span class=\"corner tr\"><\/span><span class=\"corner bl\"><\/span><span class=\"corner br\"><\/span>\r\n      <svg width=\"34\" height=\"34\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\">\r\n        <path d=\"M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z\"\/>\r\n        <path d=\"M14 2v6h6\"\/>\r\n      <\/svg>\r\n      <p class=\"dz-title\">Drop a PDF here to pull its pages out, or click to browse<\/p>\r\n      <p class=\"dz-sub mono\">Pages become individual PNG, JPEG or WEBP frames<\/p>\r\n      <input type=\"file\" id=\"pdfInput\" accept=\"application\/pdf\" multiple \/>\r\n    <\/section>\r\n\r\n    <div id=\"pdfCards\"><\/div>\r\n  <\/section>\r\n\r\n  <footer>\r\n    <p><!-- Conversion runs on-device using Canvas, pdf.js and jsPDF.<br>  --> No file ever saves in our server.<\/p>\r\n  <\/footer>\r\n\r\n<\/div>\r\n\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jspdf\/2.5.1\/jspdf.umd.min.js\"><\/script>\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.min.js\"><\/script>\r\n<script>\r\n(function(){\r\n  \"use strict\";\r\n\r\n  \/\/ ---------- Shared config ----------\r\n  var QUALITY_STOPS = [\r\n    { q: 95, label: 'f\/2.8', desc: 'Best quality' },\r\n    { q: 85, label: 'f\/4',   desc: 'High quality' },\r\n    { q: 75, label: 'f\/5.6', desc: 'Balanced' },\r\n    { q: 60, label: 'f\/8',   desc: 'Standard' },\r\n    { q: 40, label: 'f\/11',  desc: 'Aggressive' },\r\n    { q: 20, label: 'f\/16',  desc: 'Max compression' }\r\n  ];\r\n  var RES_STOPS = [\r\n    { scale: 1, label: '1\u00d7', dpi: '72 dpi' },\r\n    { scale: 2, label: '2\u00d7', dpi: '144 dpi' },\r\n    { scale: 3, label: '3\u00d7', dpi: '216 dpi' },\r\n    { scale: 4, label: '4\u00d7', dpi: '288 dpi' }\r\n  ];\r\n  var MIME = { jpeg: 'image\/jpeg', png: 'image\/png', webp: 'image\/webp' };\r\n  var EXT  = { jpeg: 'jpg', png: 'png', webp: 'webp' };\r\n  var PAGE_SIZES = { a4: { w: 595.28, h: 841.89 }, letter: { w: 612, h: 792 }, legal: { w: 612, h: 1008 } };\r\n  var SUPPORTED = { jpeg: true, png: true, webp: false };\r\n\r\n  \/\/ ---------- Utilities ----------\r\n  function formatBytes(bytes){\r\n    if (bytes == null) return '\u2014';\r\n    if (bytes < 1024) return bytes + ' B';\r\n    var units = ['KB','MB','GB'];\r\n    var val = bytes, i = -1;\r\n    do { val \/= 1024; i++; } while (val >= 1024 && i < units.length - 1);\r\n    return val.toFixed(val < 10 ? 2 : 1) + ' ' + units[i];\r\n  }\r\n  function uid(){ return 'x' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36); }\r\n  function baseName(filename){ var i = filename.lastIndexOf('.'); return i > 0 ? filename.slice(0, i) : filename; }\r\n  function escapeHtml(s){ var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }\r\n  function pad2(n){ return n < 10 ? '0' + n : '' + n; }\r\n  function triggerDownload(url, filename){\r\n    var a = document.createElement('a');\r\n    a.href = url; a.download = filename;\r\n    document.body.appendChild(a); a.click(); a.remove();\r\n  }\r\n  function detectFormatSupport(mime){\r\n    return new Promise(function(resolve){\r\n      try{\r\n        var c = document.createElement('canvas'); c.width = 2; c.height = 2;\r\n        c.toBlob(function(blob){ resolve(!!blob && blob.type === mime); }, mime);\r\n      } catch(e){ resolve(false); }\r\n    });\r\n  }\r\n\r\n  \/\/ ===================================================================\r\n  \/\/ MODE 1: IMAGES -> PDF\r\n  \/\/ ===================================================================\r\n  var frames = [];\r\n  var img2pdf = {\r\n    settings: { pageSize: 'a4', orientation: 'auto', margin: 36, embed: 'jpeg', qIndex: 2 },\r\n    pdfURL: null,\r\n    debounceTimer: null\r\n  };\r\n\r\n  function addImageFrame(file){\r\n    var id = uid();\r\n    var frame = { id: id, file: file, name: file.name, img: null, naturalWidth: 0, naturalHeight: 0, objectURL: URL.createObjectURL(file) };\r\n    frames.push(frame);\r\n    document.getElementById('img2pdfBody').style.display = '';\r\n    renderFrameGrid();\r\n\r\n    var im = new Image();\r\n    im.onload = function(){\r\n      frame.img = im;\r\n      frame.naturalWidth = im.naturalWidth;\r\n      frame.naturalHeight = im.naturalHeight;\r\n      renderFrameGrid();\r\n      scheduleRebuildPdf(150);\r\n    };\r\n    im.onerror = function(){\r\n      frames = frames.filter(function(f){ return f.id !== id; });\r\n      renderFrameGrid();\r\n    };\r\n    im.src = frame.objectURL;\r\n  }\r\n\r\n  function renderFrameGrid(){\r\n    var grid = document.getElementById('frameGrid');\r\n    grid.innerHTML = frames.map(function(f, idx){\r\n      var dims = f.naturalWidth ? (f.naturalWidth + '\u00d7' + f.naturalHeight) : 'reading\u2026';\r\n      return (\r\n        '<div class=\"frame\" data-id=\"' + f.id + '\">' +\r\n          '<div class=\"frame-thumb\"><span class=\"frame-num mono\">' + pad2(idx + 1) + '<\/span><img decoding=\"async\" src=\"' + f.objectURL + '\" alt=\"\" \/><\/div>' +\r\n          '<div class=\"frame-meta\"><div class=\"fname\">' + escapeHtml(f.name) + '<\/div>' + dims + '<\/div>' +\r\n          '<div class=\"frame-ctrls\">' +\r\n            '<button data-action=\"left\" ' + (idx === 0 ? 'disabled' : '') + '>\u2039<\/button>' +\r\n            '<button data-action=\"right\" ' + (idx === frames.length - 1 ? 'disabled' : '') + '>\u203a<\/button>' +\r\n            '<button data-action=\"remove\">\u00d7<\/button>' +\r\n          '<\/div>' +\r\n        '<\/div>'\r\n      );\r\n    }).join('');\r\n  }\r\n\r\n  function moveFrame(id, dir){\r\n    var i = frames.findIndex(function(f){ return f.id === id; });\r\n    if (i < 0) return;\r\n    var j = i + dir;\r\n    if (j < 0 || j >= frames.length) return;\r\n    var tmp = frames[i]; frames[i] = frames[j]; frames[j] = tmp;\r\n    renderFrameGrid();\r\n    scheduleRebuildPdf(0);\r\n  }\r\n\r\n  function removeFrame(id){\r\n    var f = frames.find(function(x){ return x.id === id; });\r\n    if (f && f.objectURL) URL.revokeObjectURL(f.objectURL);\r\n    frames = frames.filter(function(x){ return x.id !== id; });\r\n    renderFrameGrid();\r\n    if (frames.length === 0){\r\n      document.getElementById('img2pdfBody').style.display = 'none';\r\n      document.getElementById('downloadPdfBtn').disabled = true;\r\n      if (img2pdf.pdfURL){ URL.revokeObjectURL(img2pdf.pdfURL); img2pdf.pdfURL = null; }\r\n      document.getElementById('buildStatus').textContent = 'No images yet';\r\n      return;\r\n    }\r\n    scheduleRebuildPdf(0);\r\n  }\r\n\r\n  function scheduleRebuildPdf(delay){\r\n    clearTimeout(img2pdf.debounceTimer);\r\n    document.getElementById('buildStatus').textContent = 'Building sheet\u2026';\r\n    img2pdf.debounceTimer = setTimeout(buildPdf, delay);\r\n  }\r\n\r\n  function getPageDims(frame){\r\n    var s = img2pdf.settings;\r\n    if (s.pageSize === 'fit'){\r\n      return { w: frame.naturalWidth, h: frame.naturalHeight, unit: 'px', margin: 0 };\r\n    }\r\n    var base = PAGE_SIZES[s.pageSize];\r\n    var orient = s.orientation;\r\n    if (orient === 'auto') orient = (frame.naturalWidth > frame.naturalHeight) ? 'landscape' : 'portrait';\r\n    var w, h;\r\n    if (orient === 'landscape'){ w = Math.max(base.w, base.h); h = Math.min(base.w, base.h); }\r\n    else { w = Math.min(base.w, base.h); h = Math.max(base.w, base.h); }\r\n    return { w: w, h: h, unit: 'pt', margin: parseInt(s.margin, 10) || 0 };\r\n  }\r\n\r\n  function rasterizeForPdf(frame){\r\n    var canvas = document.createElement('canvas');\r\n    canvas.width = frame.naturalWidth; canvas.height = frame.naturalHeight;\r\n    var ctx = canvas.getContext('2d');\r\n    var fmt = img2pdf.settings.embed;\r\n    if (fmt === 'jpeg'){ ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); }\r\n    ctx.drawImage(frame.img, 0, 0);\r\n    if (fmt === 'jpeg'){\r\n      var q = QUALITY_STOPS[img2pdf.settings.qIndex].q \/ 100;\r\n      return canvas.toDataURL('image\/jpeg', q);\r\n    }\r\n    return canvas.toDataURL('image\/png');\r\n  }\r\n\r\n  function buildPdf(){\r\n    var statusEl = document.getElementById('buildStatus');\r\n    var dlBtn = document.getElementById('downloadPdfBtn');\r\n    if (frames.length === 0 || frames.some(function(f){ return !f.img; })){\r\n      if (frames.length === 0) statusEl.textContent = 'No images yet';\r\n      return;\r\n    }\r\n    if (typeof window.jspdf === 'undefined'){\r\n      statusEl.textContent = 'PDF library failed to load \u2014 check your connection.';\r\n      return;\r\n    }\r\n    try{\r\n      var doc = null;\r\n      var fmt = img2pdf.settings.embed;\r\n      frames.forEach(function(frame, idx){\r\n        var dims = getPageDims(frame);\r\n        var dataURL = rasterizeForPdf(frame);\r\n        var availW = dims.w - dims.margin * 2;\r\n        var availH = dims.h - dims.margin * 2;\r\n        var scale = Math.min(availW \/ frame.naturalWidth, availH \/ frame.naturalHeight);\r\n        var drawW = frame.naturalWidth * scale;\r\n        var drawH = frame.naturalHeight * scale;\r\n        var x = (dims.w - drawW) \/ 2;\r\n        var y = (dims.h - drawH) \/ 2;\r\n        var orientArg = dims.w > dims.h ? 'l' : 'p';\r\n        if (idx === 0){\r\n          doc = new window.jspdf.jsPDF({ orientation: orientArg, unit: dims.unit, format: [dims.w, dims.h] });\r\n        } else {\r\n          doc.addPage([dims.w, dims.h], orientArg);\r\n        }\r\n        doc.addImage(dataURL, fmt === 'jpeg' ? 'JPEG' : 'PNG', x, y, drawW, drawH);\r\n      });\r\n      var blob = doc.output('blob');\r\n      if (img2pdf.pdfURL) URL.revokeObjectURL(img2pdf.pdfURL);\r\n      img2pdf.pdfURL = URL.createObjectURL(blob);\r\n      statusEl.innerHTML = '<b>' + frames.length + (frames.length === 1 ? ' page' : ' pages') + '<\/b> \u00b7 ' + formatBytes(blob.size);\r\n      dlBtn.disabled = false;\r\n    } catch(e){\r\n      statusEl.textContent = 'Could not build the PDF: ' + e.message;\r\n    }\r\n  }\r\n\r\n  function wireImg2Pdf(){\r\n    var dz = document.getElementById('dzImages');\r\n    var input = document.getElementById('imgInput');\r\n    dz.addEventListener('click', function(){ input.click(); });\r\n    dz.addEventListener('keydown', function(e){ if (e.key === 'Enter' || e.key === ' '){ e.preventDefault(); input.click(); } });\r\n    input.addEventListener('change', function(){\r\n      Array.from(input.files).filter(function(f){ return f.type.indexOf('image\/') === 0; }).forEach(addImageFrame);\r\n      input.value = '';\r\n    });\r\n    ['dragenter','dragover'].forEach(function(evt){ dz.addEventListener(evt, function(e){ e.preventDefault(); dz.classList.add('active'); }); });\r\n    ['dragleave','drop'].forEach(function(evt){ dz.addEventListener(evt, function(e){ e.preventDefault(); dz.classList.remove('active'); }); });\r\n    dz.addEventListener('drop', function(e){\r\n      if (e.dataTransfer && e.dataTransfer.files){\r\n        Array.from(e.dataTransfer.files).filter(function(f){ return f.type.indexOf('image\/') === 0; }).forEach(addImageFrame);\r\n      }\r\n    });\r\n\r\n    document.getElementById('frameGrid').addEventListener('click', function(e){\r\n      var btn = e.target.closest('button[data-action]');\r\n      if (!btn) return;\r\n      var frameEl = e.target.closest('.frame');\r\n      var id = frameEl.dataset.id;\r\n      var action = btn.dataset.action;\r\n      if (action === 'left') moveFrame(id, -1);\r\n      else if (action === 'right') moveFrame(id, 1);\r\n      else if (action === 'remove') removeFrame(id);\r\n    });\r\n\r\n    document.getElementById('pageSize').addEventListener('change', function(e){\r\n      img2pdf.settings.pageSize = e.target.value;\r\n      var isFit = img2pdf.settings.pageSize === 'fit';\r\n      document.getElementById('orientationField').style.opacity = isFit ? 0.4 : 1;\r\n      document.getElementById('orientation').disabled = isFit;\r\n      document.getElementById('marginField').style.opacity = isFit ? 0.4 : 1;\r\n      document.getElementById('margin').disabled = isFit;\r\n      scheduleRebuildPdf(0);\r\n    });\r\n    document.getElementById('orientation').addEventListener('change', function(e){\r\n      img2pdf.settings.orientation = e.target.value;\r\n      scheduleRebuildPdf(0);\r\n    });\r\n    document.getElementById('margin').addEventListener('change', function(e){\r\n      img2pdf.settings.margin = e.target.value;\r\n      scheduleRebuildPdf(0);\r\n    });\r\n\r\n    var embedStops = document.getElementById('embedStops');\r\n    embedStops.innerHTML = QUALITY_STOPS.map(function(s, i){ return '<span data-i=\"' + i + '\">' + s.label + '<\/span>'; }).join('');\r\n    function updateEmbedQLabel(){\r\n      var s = QUALITY_STOPS[img2pdf.settings.qIndex];\r\n      document.getElementById('embedQLabel').textContent = s.label + ' \u00b7 ' + s.q + '% \u00b7 ' + s.desc;\r\n      embedStops.querySelectorAll('span').forEach(function(sp){ sp.classList.toggle('active', parseInt(sp.dataset.i, 10) === img2pdf.settings.qIndex); });\r\n    }\r\n    updateEmbedQLabel();\r\n    document.getElementById('embedQuality').addEventListener('input', function(e){\r\n      img2pdf.settings.qIndex = parseInt(e.target.value, 10);\r\n      updateEmbedQLabel();\r\n      scheduleRebuildPdf(300);\r\n    });\r\n\r\n    document.querySelectorAll('#embedChips .chip').forEach(function(chip){\r\n      chip.addEventListener('click', function(){\r\n        document.querySelectorAll('#embedChips .chip').forEach(function(c){ c.classList.remove('active'); });\r\n        chip.classList.add('active');\r\n        img2pdf.settings.embed = chip.dataset.embed;\r\n        var qBlock = document.getElementById('embedQualityBlock');\r\n        qBlock.style.opacity = img2pdf.settings.embed === 'png' ? 0.4 : 1;\r\n        document.getElementById('embedQuality').disabled = img2pdf.settings.embed === 'png';\r\n        scheduleRebuildPdf(0);\r\n      });\r\n    });\r\n\r\n    document.getElementById('downloadPdfBtn').addEventListener('click', function(){\r\n      if (!img2pdf.pdfURL) return;\r\n      triggerDownload(img2pdf.pdfURL, 'contact-sheet.pdf');\r\n    });\r\n  }\r\n\r\n  \/\/ ===================================================================\r\n  \/\/ MODE 2: PDF -> IMAGES\r\n  \/\/ ===================================================================\r\n  var pdfCards = new Map();\r\n\r\n  function parsePageRange(text, maxPages){\r\n    text = (text || '').trim();\r\n    if (!text) {\r\n      var all = [];\r\n      for (var i = 1; i <= maxPages; i++) all.push(i);\r\n      return all;\r\n    }\r\n    var set = {};\r\n    text.split(',').forEach(function(tok){\r\n      tok = tok.trim();\r\n      if (!tok) return;\r\n      if (tok.indexOf('-') > -1){\r\n        var parts = tok.split('-');\r\n        var a = parseInt(parts[0], 10), b = parseInt(parts[1], 10);\r\n        if (isNaN(a) || isNaN(b)) return;\r\n        if (a > b){ var t = a; a = b; b = t; }\r\n        for (var n = Math.max(1, a); n <= Math.min(maxPages, b); n++) set[n] = true;\r\n      } else {\r\n        var v = parseInt(tok, 10);\r\n        if (!isNaN(v) && v >= 1 && v <= maxPages) set[v] = true;\r\n      }\r\n    });\r\n    return Object.keys(set).map(Number).sort(function(a, b){ return a - b; });\r\n  }\r\n\r\n  function renderPdfPageToBlob(pdfDoc, pageNum, scale, format, quality){\r\n    return pdfDoc.getPage(pageNum).then(function(page){\r\n      var viewport = page.getViewport({ scale: scale });\r\n      var canvas = document.createElement('canvas');\r\n      canvas.width = Math.ceil(viewport.width);\r\n      canvas.height = Math.ceil(viewport.height);\r\n      var ctx = canvas.getContext('2d');\r\n      if (format === 'jpeg'){ ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); }\r\n      return page.render({ canvasContext: ctx, viewport: viewport }).promise.then(function(){\r\n        return new Promise(function(resolve, reject){\r\n          var mime = MIME[format];\r\n          var q = format === 'png' ? undefined : (quality \/ 100);\r\n          canvas.toBlob(function(blob){ if (blob) resolve(blob); else reject(new Error('encode failed')); }, mime, q);\r\n        });\r\n      });\r\n    });\r\n  }\r\n\r\n  function addPdfFile(file){\r\n    var id = uid();\r\n    var card = {\r\n      id: id, file: file, name: file.name, base: baseName(file.name), size: file.size,\r\n      pdfDoc: null, numPages: 0,\r\n      settings: { format: 'jpeg', qIndex: 2, resIndex: 1, range: '' },\r\n      pageBlobs: {}, el: null, errored: false, extracting: false,\r\n      previewTimer: null\r\n    };\r\n    pdfCards.set(id, card);\r\n\r\n    var el = document.createElement('div');\r\n    el.className = 'pdf-card';\r\n    el.dataset.id = id;\r\n    el.innerHTML =\r\n      '<button class=\"remove\" aria-label=\"Remove\" data-action=\"remove\" style=\"position:static; float:right;\">\u00d7<\/button>' +\r\n      '<p class=\"pdf-name\">' + escapeHtml(file.name) + '<\/p>' +\r\n      '<p class=\"meta-text mono\">Reading PDF\u2026<\/p>';\r\n    document.getElementById('pdfCards').appendChild(el);\r\n    card.el = el;\r\n    el.querySelector('[data-action=\"remove\"]').addEventListener('click', function(){ removePdfCard(id); });\r\n\r\n    file.arrayBuffer().then(function(buf){\r\n      if (typeof pdfjsLib === 'undefined') throw new Error('pdf.js failed to load');\r\n      return pdfjsLib.getDocument({ data: buf }).promise;\r\n    }).then(function(pdfDoc){\r\n      card.pdfDoc = pdfDoc;\r\n      card.numPages = pdfDoc.numPages;\r\n      buildPdfCardBody(card);\r\n      computePagePreviewSizes(card);\r\n    }).catch(function(err){\r\n      card.errored = true;\r\n      el.innerHTML =\r\n        '<button class=\"remove\" aria-label=\"Remove\" data-action=\"remove\" style=\"position:static; float:right;\">\u00d7<\/button>' +\r\n        '<p class=\"pdf-name\">' + escapeHtml(file.name) + '<\/p>' +\r\n        '<p class=\"err-msg\">Couldn\\'t open this PDF \u2014 it may be encrypted, corrupted, or not a valid PDF file.<\/p>';\r\n      el.querySelector('[data-action=\"remove\"]').addEventListener('click', function(){ removePdfCard(id); });\r\n    });\r\n  }\r\n\r\n  function buildPdfCardBody(card){\r\n    var el = card.el;\r\n    var chips = ['jpeg','png','webp'].map(function(fmt){\r\n      var supported = SUPPORTED[fmt];\r\n      return '<button class=\"chip' + (fmt === card.settings.format ? ' active' : '') + '\" data-fmt=\"' + fmt + '\" ' + (supported ? '' : 'disabled title=\"Not supported by this browser\"') + '>' +\r\n        '<span class=\"fmt\">' + fmt + '<\/span><span class=\"sz mono\">\u2026<\/span><\/button>';\r\n    }).join('');\r\n\r\n    var qStops = QUALITY_STOPS.map(function(s, i){ return '<span data-i=\"' + i + '\">' + s.label + '<\/span>'; }).join('');\r\n    var rStops = RES_STOPS.map(function(s, i){ return '<span data-i=\"' + i + '\">' + s.label + '<br>' + s.dpi + '<\/span>'; }).join('');\r\n\r\n    el.innerHTML =\r\n      '<div class=\"pdf-card-head\">' +\r\n        '<div>' +\r\n          '<p class=\"pdf-name\">' + escapeHtml(card.name) + '<\/p>' +\r\n          '<div class=\"pill-row\">' +\r\n            '<span class=\"pill\">PDF<\/span>' +\r\n            '<span class=\"meta-text mono\">' + card.numPages + (card.numPages === 1 ? ' page' : ' pages') + '<\/span>' +\r\n            '<span class=\"meta-text mono\">' + formatBytes(card.size) + '<\/span>' +\r\n          '<\/div>' +\r\n        '<\/div>' +\r\n        '<button class=\"remove\" aria-label=\"Remove\" data-action=\"remove\" style=\"position:static;\">\u00d7<\/button>' +\r\n      '<\/div>' +\r\n\r\n      '<div class=\"pdf-controls\">' +\r\n        '<div>' +\r\n          '<div class=\"ctrl-block\"><div class=\"ctrl-label\"><span>Save as<\/span><\/div>' +\r\n            '<div class=\"chip-row\" data-role=\"fmt-chips\">' + chips + '<\/div>' +\r\n          '<\/div>' +\r\n          '<div class=\"ctrl-block\" data-role=\"quality-block\">' +\r\n            '<div class=\"ctrl-label\"><span>Compression<\/span><span class=\"val mono\" data-role=\"qlabel\"><\/span><\/div>' +\r\n            '<input type=\"range\" min=\"0\" max=\"5\" step=\"1\" value=\"' + card.settings.qIndex + '\" data-role=\"quality\" \/>' +\r\n            '<div class=\"stops-row\">' + qStops + '<\/div>' +\r\n          '<\/div>' +\r\n        '<\/div>' +\r\n        '<div>' +\r\n          '<div class=\"ctrl-block\">' +\r\n            '<div class=\"ctrl-label\"><span>Resolution<\/span><span class=\"val mono\" data-role=\"rlabel\"><\/span><\/div>' +\r\n            '<input type=\"range\" min=\"0\" max=\"3\" step=\"1\" value=\"' + card.settings.resIndex + '\" data-role=\"resolution\" \/>' +\r\n            '<div class=\"stops-row\">' + rStops + '<\/div>' +\r\n          '<\/div>' +\r\n          '<div class=\"ctrl-block range-field\">' +\r\n            '<div class=\"ctrl-label\"><span>Pages<\/span><\/div>' +\r\n            '<input type=\"text\" data-role=\"range\" placeholder=\"All pages \u2014 e.g. 1-3, 5, 8-10\" \/>' +\r\n          '<\/div>' +\r\n        '<\/div>' +\r\n      '<\/div>' +\r\n\r\n      '<div class=\"extract-row\">' +\r\n        '<button class=\"btn primary\" data-role=\"extract\">Extract pages<\/button>' +\r\n        '<button class=\"btn\" data-role=\"dlzip\" style=\"display:none;\">Download all (.zip)<\/button>' +\r\n      '<\/div>' +\r\n      '<p class=\"progress-line\" data-role=\"progress\"><\/p>' +\r\n      '<div class=\"page-grid\" data-role=\"page-grid\"><\/div>';\r\n\r\n    el.querySelector('[data-action=\"remove\"]').addEventListener('click', function(){ removePdfCard(card.id); });\r\n\r\n    function updateQLabel(){\r\n      var s = QUALITY_STOPS[card.settings.qIndex];\r\n      el.querySelector('[data-role=\"qlabel\"]').textContent = s.label + ' \u00b7 ' + s.q + '% \u00b7 ' + s.desc;\r\n    }\r\n    function updateRLabel(){\r\n      var s = RES_STOPS[card.settings.resIndex];\r\n      el.querySelector('[data-role=\"rlabel\"]').textContent = s.label + ' \u00b7 ' + s.dpi;\r\n    }\r\n    updateQLabel(); updateRLabel();\r\n    el.querySelector('[data-role=\"quality-block\"]').style.opacity = card.settings.format === 'png' ? 0.4 : 1;\r\n    el.querySelector('[data-role=\"quality\"]').disabled = card.settings.format === 'png';\r\n\r\n    el.querySelectorAll('[data-role=\"fmt-chips\"] .chip').forEach(function(chip){\r\n      chip.addEventListener('click', function(){\r\n        if (chip.disabled) return;\r\n        el.querySelectorAll('[data-role=\"fmt-chips\"] .chip').forEach(function(c){ c.classList.remove('active'); });\r\n        chip.classList.add('active');\r\n        card.settings.format = chip.dataset.fmt;\r\n        el.querySelector('[data-role=\"quality-block\"]').style.opacity = card.settings.format === 'png' ? 0.4 : 1;\r\n        el.querySelector('[data-role=\"quality\"]').disabled = card.settings.format === 'png';\r\n        schedulePreview(card, 0);\r\n      });\r\n    });\r\n\r\n    el.querySelector('[data-role=\"quality\"]').addEventListener('input', function(e){\r\n      card.settings.qIndex = parseInt(e.target.value, 10);\r\n      updateQLabel();\r\n      schedulePreview(card, 300);\r\n    });\r\n    el.querySelector('[data-role=\"resolution\"]').addEventListener('input', function(e){\r\n      card.settings.resIndex = parseInt(e.target.value, 10);\r\n      updateRLabel();\r\n      schedulePreview(card, 300);\r\n    });\r\n    el.querySelector('[data-role=\"range\"]').addEventListener('input', function(e){\r\n      card.settings.range = e.target.value;\r\n    });\r\n\r\n    el.querySelector('[data-role=\"extract\"]').addEventListener('click', function(){ extractPages(card); });\r\n    el.querySelector('[data-role=\"dlzip\"]').addEventListener('click', function(){ downloadCardZip(card); });\r\n  }\r\n\r\n  function schedulePreview(card, delay){\r\n    clearTimeout(card.previewTimer);\r\n    card.previewTimer = setTimeout(function(){ computePagePreviewSizes(card); }, delay);\r\n  }\r\n\r\n  function computePagePreviewSizes(card){\r\n    if (!card.pdfDoc) return;\r\n    var el = card.el;\r\n    var scale = RES_STOPS[card.settings.resIndex].scale;\r\n    var q = QUALITY_STOPS[card.settings.qIndex].q;\r\n    var fmts = ['jpeg','png','webp'].filter(function(f){ return SUPPORTED[f]; });\r\n    Promise.all(fmts.map(function(fmt){\r\n      return renderPdfPageToBlob(card.pdfDoc, 1, scale, fmt, q)\r\n        .then(function(blob){ return { fmt: fmt, blob: blob }; })\r\n        .catch(function(){ return { fmt: fmt, blob: null }; });\r\n    })).then(function(results){\r\n      var smallestFmt = null, smallestSize = Infinity;\r\n      results.forEach(function(r){ if (r.blob && r.blob.size < smallestSize){ smallestSize = r.blob.size; smallestFmt = r.fmt; } });\r\n      results.forEach(function(r){\r\n        var chip = el.querySelector('[data-role=\"fmt-chips\"] .chip[data-fmt=\"' + r.fmt + '\"]');\r\n        if (!chip) return;\r\n        var sz = chip.querySelector('.sz');\r\n        if (sz) sz.textContent = r.blob ? ('~' + formatBytes(r.blob.size) + '\/pg') : 'failed';\r\n        var oldBadge = chip.querySelector('.badge');\r\n        if (oldBadge) oldBadge.remove();\r\n        if (r.fmt === smallestFmt){\r\n          var badge = document.createElement('span');\r\n          badge.className = 'badge'; badge.textContent = 'smallest';\r\n          chip.appendChild(badge);\r\n        }\r\n      });\r\n    });\r\n  }\r\n\r\n  function extractPages(card){\r\n    if (card.extracting || !card.pdfDoc) return;\r\n    var el = card.el;\r\n    var pages = parsePageRange(card.settings.range, card.numPages);\r\n    var progressEl = el.querySelector('[data-role=\"progress\"]');\r\n    var gridEl = el.querySelector('[data-role=\"page-grid\"]');\r\n    var extractBtn = el.querySelector('[data-role=\"extract\"]');\r\n    var zipBtn = el.querySelector('[data-role=\"dlzip\"]');\r\n    if (pages.length === 0){\r\n      progressEl.textContent = 'No valid pages in that range.';\r\n      return;\r\n    }\r\n    card.extracting = true;\r\n    extractBtn.disabled = true;\r\n    zipBtn.style.display = 'none';\r\n    gridEl.innerHTML = '';\r\n    card.pageBlobs = {};\r\n\r\n    var scale = RES_STOPS[card.settings.resIndex].scale;\r\n    var fmt = card.settings.format;\r\n    var q = QUALITY_STOPS[card.settings.qIndex].q;\r\n    var digits = String(card.numPages).length;\r\n\r\n    var i = 0;\r\n    function next(){\r\n      if (i >= pages.length){\r\n        progressEl.textContent = 'Done \u2014 ' + pages.length + (pages.length === 1 ? ' page' : ' pages') + ' extracted.';\r\n        card.extracting = false;\r\n        extractBtn.disabled = false;\r\n        zipBtn.style.display = '';\r\n        return;\r\n      }\r\n      var pageNum = pages[i];\r\n      progressEl.textContent = 'Rendering page ' + pageNum + '\u2026 (' + (i + 1) + ' of ' + pages.length + ')';\r\n      renderPdfPageToBlob(card.pdfDoc, pageNum, scale, fmt, q).then(function(blob){\r\n        card.pageBlobs[pageNum] = blob;\r\n        var url = URL.createObjectURL(blob);\r\n        var tile = document.createElement('div');\r\n        tile.className = 'page-tile';\r\n        tile.innerHTML =\r\n          '<div class=\"frame-thumb\"><span class=\"frame-num mono\">' + pad2(pageNum) + '<\/span><img decoding=\"async\" src=\"' + url + '\" alt=\"\" \/><\/div>' +\r\n          '<div class=\"frame-meta\">' + formatBytes(blob.size) + '<\/div>' +\r\n          '<button class=\"page-dl mono\">Download<\/button>';\r\n        tile.querySelector('.page-dl').addEventListener('click', function(){\r\n          triggerDownload(url, card.base + '-p' + String(pageNum).padStart(digits, '0') + '.' + EXT[fmt]);\r\n        });\r\n        gridEl.appendChild(tile);\r\n        i++;\r\n        next();\r\n      }).catch(function(){\r\n        progressEl.textContent = 'Failed to render page ' + pageNum + '.';\r\n        i++;\r\n        next();\r\n      });\r\n    }\r\n    next();\r\n  }\r\n\r\n  function downloadCardZip(card){\r\n    if (typeof JSZip === 'undefined'){ alert('The zip library failed to load.'); return; }\r\n    var fmt = card.settings.format;\r\n    var digits = String(card.numPages).length;\r\n    var zip = new JSZip();\r\n    var any = false;\r\n    Object.keys(card.pageBlobs).forEach(function(pageNum){\r\n      var blob = card.pageBlobs[pageNum];\r\n      if (!blob) return;\r\n      zip.file(card.base + '-p' + String(pageNum).padStart(digits, '0') + '.' + EXT[fmt], blob);\r\n      any = true;\r\n    });\r\n    if (!any) return;\r\n    zip.generateAsync({ type: 'blob' }).then(function(content){\r\n      var url = URL.createObjectURL(content);\r\n      triggerDownload(url, card.base + '-pages.zip');\r\n      setTimeout(function(){ URL.revokeObjectURL(url); }, 4000);\r\n    });\r\n  }\r\n\r\n  function removePdfCard(id){\r\n    var card = pdfCards.get(id);\r\n    if (!card) return;\r\n    if (card.el && card.el.parentNode) card.el.parentNode.removeChild(card.el);\r\n    pdfCards.delete(id);\r\n  }\r\n\r\n  function wirePdf2Img(){\r\n    var dz = document.getElementById('dzPdf');\r\n    var input = document.getElementById('pdfInput');\r\n    dz.addEventListener('click', function(){ input.click(); });\r\n    dz.addEventListener('keydown', function(e){ if (e.key === 'Enter' || e.key === ' '){ e.preventDefault(); input.click(); } });\r\n    input.addEventListener('change', function(){\r\n      Array.from(input.files).filter(function(f){ return f.type === 'application\/pdf' || \/\\.pdf$\/i.test(f.name); }).forEach(addPdfFile);\r\n      input.value = '';\r\n    });\r\n    ['dragenter','dragover'].forEach(function(evt){ dz.addEventListener(evt, function(e){ e.preventDefault(); dz.classList.add('active'); }); });\r\n    ['dragleave','drop'].forEach(function(evt){ dz.addEventListener(evt, function(e){ e.preventDefault(); dz.classList.remove('active'); }); });\r\n    dz.addEventListener('drop', function(e){\r\n      if (e.dataTransfer && e.dataTransfer.files){\r\n        Array.from(e.dataTransfer.files).filter(function(f){ return f.type === 'application\/pdf' || \/\\.pdf$\/i.test(f.name); }).forEach(addPdfFile);\r\n      }\r\n    });\r\n  }\r\n\r\n  \/\/ ===================================================================\r\n  \/\/ Tabs + init\r\n  \/\/ ===================================================================\r\n  function wireTabs(){\r\n    var tabImg = document.getElementById('tabImg2Pdf');\r\n    var tabPdf = document.getElementById('tabPdf2Img');\r\n    var panelImg = document.getElementById('panelImg2Pdf');\r\n    var panelPdf = document.getElementById('panelPdf2Img');\r\n    tabImg.addEventListener('click', function(){\r\n      tabImg.classList.add('active'); tabImg.setAttribute('aria-selected','true');\r\n      tabPdf.classList.remove('active'); tabPdf.setAttribute('aria-selected','false');\r\n      panelImg.classList.add('active'); panelPdf.classList.remove('active');\r\n    });\r\n    tabPdf.addEventListener('click', function(){\r\n      tabPdf.classList.add('active'); tabPdf.setAttribute('aria-selected','true');\r\n      tabImg.classList.remove('active'); tabImg.setAttribute('aria-selected','false');\r\n      panelPdf.classList.add('active'); panelImg.classList.remove('active');\r\n    });\r\n  }\r\n\r\n  function init(){\r\n    if (typeof pdfjsLib !== 'undefined'){\r\n      pdfjsLib.GlobalWorkerOptions.workerSrc = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/2.16.105\/pdf.worker.min.js';\r\n    }\r\n    detectFormatSupport('image\/webp').then(function(ok){ SUPPORTED.webp = ok; });\r\n    wireTabs();\r\n    wireImg2Pdf();\r\n    wirePdf2Img();\r\n    document.getElementById('buildStatus').textContent = 'No images yet';\r\n  }\r\n\r\n  document.addEventListener('DOMContentLoaded', init);\r\n})();\r\n\r\n<\/script>\r\n<\/body>\r\n<\/html>\r\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_eb_attr":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[76],"tags":[],"class_list":["post-4600","post","type-post","status-publish","format-standard","hentry","category-tools"],"_links":{"self":[{"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/posts\/4600","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/comments?post=4600"}],"version-history":[{"count":13,"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/posts\/4600\/revisions"}],"predecessor-version":[{"id":4604,"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/posts\/4600\/revisions\/4604"}],"wp:attachment":[{"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/media?parent=4600"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/categories?post=4600"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rajmedical.co.in\/hi\/wp-json\/wp\/v2\/tags?post=4600"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}