1import{i as W}from"./header-DTc-a7mb.js";import{l as J,r as Z,y as p,u as V}from"./theme-BwSXwi2g.js";import{c as T,H as O,n as P}from"./gifenc.esm-CnQ3XFIa.js";const c=J(),j="jpg-to-gif",K=c.nav_short&&c.nav_short[j]||"JPG to GIF",S=c.seo&&c.seo[j],Y=S?S.h2a:"Convert JPG images to GIF free. No upload required.",Q=c.select_images||"Select Images",X=c.drop_hint||"or drop images anywhere",R=c.download||"Download",ee=c.download_zip||"Download ZIP",q=K.split(" "),te=q[0],ne=q.slice(1).join(" "),oe=600;document.body.style.cssText="margin:0;padding:0;min-height:100vh;background:var(--bg-page);";const A=document.createElement("style");A.textContent=` 2 @keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}} 3 #app>div{animation:fadeUp 0.4s ease both} 4 .upload-label{display:inline-flex;align-items:center;gap:8px;background:var(--accent);color:var(--text-on-accent);font-family:'DM Sans',sans-serif;font-weight:600;font-size:14px;padding:10px 20px;border-radius:8px;cursor:pointer;transition:background 0.15s;} 5 .upload-label:hover{background:var(--accent-hover);} 6 .mode-tab{flex:1;padding:10px;border:1.5px solid var(--border-light);border-radius:8px;background:var(--bg-card);font-size:13px;font-weight:600;font-family:'DM Sans',sans-serif;color:var(--text-secondary);cursor:pointer;transition:all 0.15s;text-align:center;} 7 .mode-tab.active{border-color:var(--accent);background:var(--accent-bg);color:var(--accent);} 8 #fileGrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:10px;margin-bottom:16px;} 9 .file-card{background:var(--bg-card);border-radius:10px;border:1.5px solid var(--border);overflow:hidden;position:relative;} 10 .file-card img{width:100%;height:90px;object-fit:cover;display:block;} 11 .file-card .card-name{font-size:10px;color:var(--text-secondary);padding:4px 6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:'DM Sans',sans-serif;} 12 .file-card .rm{position:absolute;top:4px;right:4px;width:18px;height:18px;border-radius:50%;background:rgba(0,0,0,0.45);border:none;color:#fff;font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;} 13 .main-btn{width:100%;padding:13px;border:none;border-radius:10px;background:var(--accent);color:var(--text-on-accent);font-size:15px;font-family:'Fraunces',serif;font-weight:700;cursor:pointer;transition:all 0.18s;margin-bottom:10px;} 14 .main-btn:hover{background:var(--accent-hover);} 15 .main-btn:disabled{background:var(--btn-disabled);cursor:not-allowed;opacity:0.7;} 16 .dark-btn{background:var(--btn-dark);color:var(--text-on-dark-btn);} 17 .dark-btn:hover{background:var(--btn-dark-hover) !important;} 18 .count-badge{display:inline-block;background:var(--accent-bg);color:var(--accent);font-size:12px;font-weight:700;padding:3px 10px;border-radius:20px;font-family:'DM Sans',sans-serif;margin-left:10px;} 19 .option-row{display:flex;align-items:center;justify-content:space-between;background:var(--bg-card);border:1.5px solid var(--border);border-radius:10px;padding:10px 14px;margin-bottom:8px;font-family:'DM Sans',sans-serif;} 20 .option-label{font-size:13px;font-weight:600;color:var(--text-primary);} 21 .option-val{font-size:13px;color:var(--accent);font-weight:700;min-width:40px;text-align:right;} 22 input[type=range]{accent-color:var(--accent);} 23 .toggle-wrap{display:flex;align-items:center;gap:8px;} 24 .toggle{width:36px;height:20px;background:var(--border-light);border-radius:20px;position:relative;cursor:pointer;transition:background 0.2s;border:none;outline:none;} 25 .toggle.on{background:var(--accent);} 26 .toggle::after{content:'';position:absolute;width:14px;height:14px;background:#fff;border-radius:50%;top:3px;left:3px;transition:left 0.2s;} 27 .toggle.on::after{left:19px;} 28 #animatedPreview{display:none;text-align:center;margin-bottom:12px;} 29 #animatedPreview img{max-width:100%;max-height:200px;border-radius:8px;border:1.5px solid var(--border);} 30 .seo-section{max-width:700px;margin:0 auto;padding:0 16px 60px;font-family:'DM Sans',sans-serif;} 31 .seo-section h2{font-family:'Fraunces',serif;font-size:17px;font-weight:700;color:var(--text-primary);margin:32px 0 10px;}
32 .seo-section h3{font-family:'Fraunces',serif;font-size:15px;font-weight:700;color:var(--text-primary);margin:24px 0 8px;} 33 .seo-section ol{padding-left:20px;margin:0 0 12px;} 34 .seo-section ol li{font-size:13px;color:var(--text-secondary);line-height:1.6;margin-bottom:6px;} 35 .seo-section p{font-size:13px;color:var(--text-secondary);line-height:1.6;margin:0 0 12px;} 36 37 38 39 40 .seo-links{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px;} 41 .seo-link{padding:7px 14px;background:var(--bg-card);border:1.5px solid var(--border-light);border-radius:8px;font-size:13px;font-weight:600;color:var(--text-primary);text-decoration:none;font-family:'DM Sans',sans-serif;transition:all 0.15s;} 42 .seo-link:hover{border-color:var(--accent);color:var(--accent);} 43 .seo-section .faq-item { background:var(--bg-card); border-radius:12px; padding:18px 20px; margin-bottom:10px; box-shadow:0 1px 4px rgba(0,0,0,0.06); } 44 .seo-section .faq-item h4 { font-family:'Fraunces',serif; font-size:15px; font-weight:700; color:var(--text-primary); margin:0 0 6px; } 45 .seo-section .faq-item p { margin:0; } 46`;document.head.appendChild(A);Z("jpg-to-gif");document.querySelector("#app").innerHTML=` 47 <div style="max-width:900px;margin:32px auto;padding:0 16px 60px;font-family:'DM Sans',sans-serif;"> 48 <div style="margin-bottom:20px;"> 49 <h1 style="font-family:'Fraunces',serif;font-size:clamp(24px,4vw,36px);font-weight:400;color:var(--text-primary);margin:0 0 6px;line-height:1;letter-spacing:-0.02em;">${te} <em style="font-style:italic;color:var(--accent);">${ne}</em></h1> 50 <p style="font-size:13px;color:var(--text-tertiary);margin:0;">${Y}</p> 51 </div> 52 <div style="margin-bottom:16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;"> 53 <label class="upload-label" for="fileInput"><span style="font-size:18px;">+</span> ${Q}</label> 54 <span style="font-size:12px;color:var(--text-muted);">${X} â up to 25 files</span> 55 <span class="count-badge" id="countBadge" style="display:none;"></span> 56 </div> 57 <input type="file" id="fileInput" accept="image/jpeg,image/jpg" multiple style="display:none;" /> 58 59 <div id="fileGrid"></div> 60 61 <!-- Mode toggle â only shown for 2+ files --> 62 <div id="modeWrap" style="display:none;margin-bottom:14px;"> 63 <div style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:8px;font-family:'DM Sans',sans-serif;">GIF Mode</div> 64 <div style="display:flex;gap:8px;"> 65 <button class="mode-tab active" id="tabStatic">ð¼ Static GIF</button> 66 <button class="mode-tab" id="tabAnimated">ð¬ Animated GIF</button> 67 </div> 68 </div> 69 70 <!-- Animated options â only shown in animated mode --> 71 <div id="animOpts" style="display:none;margin-bottom:14px;"> 72 <div class="option-row"> 73 <span class="option-label">â± Seconds per frame</span> 74 <div style="display:flex;align-items:center;gap:10px;"> 75 <input type="range" id="fpsRange" min="0.1" max="3" step="0.1" value="0.5" style="width:120px;"> 76 <span class="option-val" id="fpsVal">0.5s</span> 77 </div> 78 </div> 79 <div class="option-row"> 80 <span class="option-label">ð Loop</span> 81 <div class="toggle-wrap"> 82 <button class="toggle on" id="loopToggle"></button> 83 <span style="font-size:13px;color:var(--text-secondary);font-family:'DM Sans',sans-serif;" id="loopLabel">On</span> 84 </div> 85 </div> 86 </div> 87 88 <!-- Animated preview --> 89 <div id="animatedPreview"> 90 <p style="font-size:12px;color:var(--text-muted);margin:0 0 6px;font-family:'DM Sans',sans-serif;">Preview</p> 91 <img id="previewGif" src="" alt="animated preview" /> 92 </div> 93 94 <button class="main-btn" id="convertBtn" disabled>Convert to GIF</button> 95 <a id="downloadLink" class="main-btn dark-btn" style="display:none;text-align:center;text-decoration:none;"></a> 96 <button class="main-btn dark-btn" id="zipBtn" style="display:none;">⬠${ee}</button> 97 <p id="statusNote" style="font-size:12px;color:var(--text-muted);text-align:center;margin:4px 0 0;font-family:'DM Sans',sans-serif;min-height:16px;"></p> 98 <div id="nextSteps" style="display:none;margin-top:20px;"> 99 <div style="font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:10px;font-family:'DM Sans',sans-serif;">What's Next?</div> 100 <div style="display:flex;gap:10px;flex-wrap:wrap;" id="nextStepsButtons"></div> 101 </div> 102 </div> 103`;W();const k=document.getElementById("fileInput"),M=document.getElementById("fileGrid"),D=document.getElementById("countBadge"),ae=
103document.getElementById("modeWrap"),L=document.getElementById("tabStatic"),E=document.getElementById("tabAnimated"),$=document.getElementById("animOpts"),I=document.getElementById("fpsRange"),ie=document.getElementById("fpsVal"),F=document.getElementById("loopToggle"),re=document.getElementById("loopLabel"),x=document.getElementById("convertBtn"),m=document.getElementById("downloadLink"),g=document.getElementById("zipBtn"),d=document.getElementById("statusNote"),z=document.getElementById("animatedPreview"),se=document.getElementById("previewGif");let l=[],h=!1,v=!0;L.addEventListener("click",()=>{h=!1,L.classList.add("active"),E.classList.remove("active"),$.style.display="none",z.style.display="none",u()});E.addEventListener("click",()=>{h=!0,E.classList.add("active"),L.classList.remove("active"),$.style.display="block",u()});I.addEventListener("input",()=>{ie.textContent=parseFloat(I.value).toFixed(1)+"s",u()});F.addEventListener("click",()=>{v=!v,F.classList.toggle("on",v),re.textContent=v?"On":"Off",u()});function u(){m.style.display="none",g.style.display="none",z.style.display="none",d.textContent=""}function U(){D.style.display=l.length?"inline-block":"none",D.textContent=`${l.length} image${l.length!==1?"s":""}`,x.disabled=l.length===0,ae.style.display=l.length>=2?"block":"none",l.length<2&&h&&(h=!1,L.classList.add("active"),E.classList.remove("active"),$.style.display="none"),u()}function _(){M.innerHTML="",l.forEach((i,n)=>{const a=document.createElement("div");a.className="file-card";const e=document.createElement("img"),o=URL.createObjectURL(i);e.onload=()=>URL.revokeObjectURL(o),e.src=o;const t=document.createElement("div");t.className="card-name",t.textContent=i.name;const r=document.createElement("button");r.className="rm",r.textContent="Ã",r.addEventListener("click",()=>{l.splice(n,1),_(),U()}),a.append(e,t,r),M.appendChild(a)}),U()}function N(i){const n=Array.from(i).filter(a=>a.type==="image/jpeg"||a.name.toLowerCase().match(/\.jpe?g$/)).slice(0,25-l.length);l.push(...n),_()}k.addEventListener("change",()=>{k.files.length&&N(k.files),k.value=""});document.addEventListener("dragover",i=>i.preventDefault());document.addEventListener("drop",i=>{i.preventDefault(),i.dataTransfer.files.length&&N(i.dataTransfer.files)});function B(i,n=oe){return new Promise((a,e)=>{const o=new Image,t=URL.createObjectURL(i);o.onerror=e,o.onload=()=>{let r=o.naturalWidth,s=o.naturalHeight;(r>n||s>n)&&(r>s?(s=Math.round(s/r*n),r=n):(r=Math.round(r/s*n),s=n));const f=document.createElement("canvas");f.width=r,f.height=s;const b=f.getContext("2d");b.fillStyle="#ffffff",b.fillRect(0,0,r,s),b.drawImage(o,0,0,r,s),URL.revokeObjectURL(t),a(f)},o.src=t})}function G(i){const n=i.width,a=i.height,e=i.getContext("2d"),o=new Uint8Array(e.getImageData(0,0,n,a).data.buffer),t=O(o,256),r=P(o,t),s=T();return s.writeFrame(r,n,a,{palette:t,repeat:0}),s.finish(),new Blob([s.bytes()],{type:"image/gif"})}function le(i,n,a){const e=i[0].width,o=i[0].height,t=T();for(const r of i){let s=r;if(r.width!==e||r.height!==o){const w=document.createElement("canvas");w.width=e,w.height=o;const C=w.getContext("2d");C.fillStyle="#ffffff",C.fillRect(0,0,e,o),C.drawImage(r,0,0,e,o),s=w}const f=new Uint8Array(s.getContext("2d").getImageData(0,0,e,o).data.buffer),b=O(f,256),H=P(f,b);t.writeFrame(H,e,o,{palette:b,delay:n,repeat:a?0:-1})}return t.finish(),new Blob([t.bytes()],{type:"image/gif"})}x.addEventListener("click",async()=>{if(l.length){x.disabled=!0,x.textContent="Processingâ¦",d.textContent="",u();try{const i=Math.round(parseFloat(I.value)*100);if(h&&l.length>=2){d.textContent="Building framesâ¦";const n=[];for(let o=0;o<l.length;o++)d.textContent=`Loading frame ${o+1} of ${l.length}â¦`,n.push(await B(l[o]));d.textContent="Encoding animated GIFâ¦";const a=le(n,i,v),e=URL.createObjectURL(a);y=[{blob:a,name:"animated.gif",type:"image/gif"}],se.src=e,z.style.display="block",m.href=e,m.onclick=()=>setTimeout(()=>URL.revokeObjectURL(e),1e4),m.download="animated.gif",m.textContent=`⬠${R} animated.gif (${Math.round(a.size/1024)} KB)`,m.style.display="block",d.textContent=""}else{if(l.length===1){d.textContent="Convertingâ¦";const n=await B(l[0]),a=G(n),e=URL.createObjectURL(a),o=l[0].name.replace(/\.[^.]+$/,"");y=[{blob:a,name:`${o}.gif`,type:"image/gif"}],m.href=e,m.onclick=()=>setTimeout(()=>URL.revokeObjectURL(e),1e4),m.download=`${o}.gif`,m.textContent=`⬠${R} (${Math.round(a.size/1024)} KB)`,m.style.display="block",d.textContent=""}else{const n=[];for(let t=0;t<l.length;
103t++){d.textContent=`Converting ${t+1} of ${l.length}â¦`;const r=await B(l[t]),s=G(r),f=l[t].name.replace(/\.[^.]+$/,"");n.push({name:`${f}.gif`,blob:s})}window.JSZip||await new Promise((t,r)=>{const s=document.createElement("script");s.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js",s.onload=t,s.onerror=r,document.head.appendChild(s)}),y=n.map(t=>({blob:t.blob,name:t.name,type:"image/gif"}));const a=new window.JSZip;for(const{name:t,blob:r}of n)a.file(t,await r.arrayBuffer());const e=await a.generateAsync({type:"blob",compression:"STORE"}),o=URL.createObjectURL(e);g._url=o,g.style.display="block",d.textContent="",g.addEventListener("click",()=>{const t=document.createElement("a");t.href=g._url,t.download="converted-gif.zip",t.click(),window.showReviewPrompt&&window.showReviewPrompt(),setTimeout(()=>URL.revokeObjectURL(g._url),1e4),window.rcShowSaveButton?.(g.parentElement,e,"converted-gif.zip","jpg-to-gif")},{once:!0})}d.textContent=d.textContent||"Done!"}pe()}catch(i){alert("Error: "+(i?.message||i))}x.disabled=!1,x.textContent="Convert to GIF"}});function de(){return new Promise((i,n)=>{const a=indexedDB.open("relahconvert",1);a.onupgradeneeded=e=>e.target.result.createObjectStore("pending",{keyPath:"id"}),a.onsuccess=e=>i(e.target.result),a.onerror=()=>n(new Error("IDB open failed"))})}async function ce(i){const a=(await de()).transaction("pending","readwrite"),e=a.objectStore("pending");return e.clear(),i.forEach((o,t)=>e.put({id:t,blob:o.blob,name:o.name,type:o.type})),new Promise((o,t)=>{a.oncomplete=o,a.onerror=t})}let y=[];function pe(){const i=[{label:"Compress",href:p("compress")},{label:"Resize",href:p("resize")},{label:"Crop",href:p("crop")},{label:"Rotate",href:p("rotate")},{label:"Flip",href:p("flip")},{label:"Black & White",href:p("grayscale")},{label:"Watermark",href:p("watermark")},{label:"Convert to JPG",href:p("png-to-jpg")},{label:"Convert to PNG",href:p("jpg-to-png")},{label:"Convert to WebP",href:p("jpg-to-webp")}],n=document.getElementById("nextStepsButtons");n.innerHTML="",i.forEach(a=>{const e=document.createElement("button");e.textContent=a.label,e.style.cssText="padding:8px 16px;border-radius:8px;border:1.5px solid var(--border-light);font-size:13px;font-weight:500;color:var(--text-primary);background:var(--bg-card);cursor:pointer;font-family:'DM Sans',sans-serif;transition:all 0.15s;",e.onmouseover=()=>{e.style.borderColor="var(--accent)",e.style.color="var(--accent)"},e.onmouseout=()=>{e.style.borderColor="var(--border-light)",e.style.color="var(--text-primary)"},e.addEventListener("click",async()=>{if(y.length)try{await ce(y),sessionStorage.setItem("pendingFromIDB","1")}catch{}window.location.href=a.href}),n.appendChild(e)}),document.getElementById("nextSteps").style.display="block"}(function(){const n=c.seo&&c.seo[j];if(!n)return;const a=c.seo_faq_title||"Frequently Asked Questions",e=c.seo_also_try||"Also Try",o=document.createElement("div");o.className="seo-section",V(n.faqs),o.innerHTML=`<h2>${n.h2a}</h2><ol>${n.steps.map(t=>`<li>${t}</li>`).join("")}</ol><h2>${n.h2b}</h2>${n.body}<h3>${n.h3why}</h3><p>${n.why}</p><h3>${a}</h3>${n.faqs.map(t=>`<div class="faq-item"><h4>${t.q}</h4><p>${t.a}</p></div>`).join("")}<h3>${e}</h3><div class="seo-links">${n.links.map(t=>`<a class="seo-link" href="${p(t.href.slice(1))}">${t.label}</a>`).join("")}</div>`,document.querySelector("#app").appendChild(o)})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.