PromptBase
Upgrade
Close icon
General
Home
Marketplace
Create
Hire
Login
Chat
Sell
Explore

Prompt Details

Model
(gemini-3.1-pro)
Token size
245
Example input
[App Name]: GIF Studio [Theme Style, e.g., Dark Mode, Neumorphism, Clean Minimalist]: Dark Mode [Color Palette, e.g., deep blue backgrounds with neon green accents]: deep backgrounds (#090c12, #11151d) with vibrant purple accents (#7c3aed, #8b5cf6) [Layout Requirements, e.g., Left sidebar navigation, top header, main central canvas, bottom timeline panel]: Left sidebar, top header, main central canvas area, bottom timeline panel, and a right-side inspector panel [Specific Features, e.g., Play/pause buttons, draggable layers, interactive charts, data tables]: Play/pause buttons, draggable layers, trending text animations, frame-by-frame timeline, and GIF export
Example output
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>GIF Studio — Next Level Text Editor</title> <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Anton&family=Archivo+Black&family=Bebas+Neue&family=Bungee&family=Caveat:wght@400;700&family=Comfortaa:wght@400;600;700&family=Dancing+Script:wght@400;600;700&family=DM+Sans:wght@400;500;700&family=Inter:wght@400;500;600;700;800&family=Lato:wght@400;700;900&family=Lobster&family=Merriweather:wght@400;700;900&family=Montserrat:wght@400;600;700;800&family=Noto+Nastaliq+Urdu:wght@400;700&family=Nunito:wght@400;600;700;800&family=Oswald:wght@400;600;700&family=Pacifico&family=Permanent+Marker&family=Playfair+Display:wght@400;700&family=Poppins:wght@400;600;700;800&family=Quicksand:wght@400;600;700&family=Raleway:wght@400;600;700;800&family=Roboto:wght@400;700&family=Rubik:wght@400;700&family=Space+Grotesk:wght@400;600;700&display=swap" rel="stylesheet"> <style> /* Custom Dark Scrollbar for entire page */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: #0e131a; } ::-webkit-scrollbar-thumb { background: #303848; border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: #7c3aed; } * { scrollbar-width: thin; scrollbar-color: #303848 #0e131a; box-sizing:border-box; } html,body{margin:0;width:100%;height:100%;overflow:hidden} body{background:#090c12;color:#f4f6fb;font-family:Inter,system-ui,sans-serif} button,input,select,textarea{font:inherit} button{cursor:pointer;border:0} :root{--panel:#11151d;--panel2:#171c25;--line:#29313e;--muted:#8e97a8;--accent:#8b5cf6;--accent2:#a78bfa} .app{height:100vh;display:grid;grid-template-columns:220px minmax(0,1fr) 320px;grid-template-rows:56px minmax(0,1fr) 154px;overflow:hidden} .top{grid-column:1/4;grid-row:1;display:flex;align-items:center;gap:9px;padding:8px 14px;border-bottom:1px solid var(--line);background:#0e131a;z-index:20} .workspace{display:contents} .sidebar{grid-column:1;grid-row:2/4;background:var(--panel);min-height:0;overflow-y:auto;overflow-x:hidden;padding:10px;border-right:1px solid var(--line);z-index:5} .inspector{grid-column:3;grid-row:2/4;background:var(--panel);min-height:0;overflow-y:auto;overflow-x:hidden;padding:10px;border-left:1px solid var(--line);scroll-behavior:smooth;z-index:5} .canvasarea{grid-column:2;grid-row:2;min-width:0;min-height:0;position:relative;display:flex;align-items:center;justify-content:center;padding:18px;background:radial-gradient(circle at center,#171c29 0,#0c1017 56%,#080b10 100%);overflow:hidden;z-index:1} .timeline{grid-column:2;grid-row:3;background:#0e131a;border-top:1px solid var(--line);padding:7px 10px;overflow:auto;min-height:0;z-index:10} .logo{font-weight:800;font-size:15px;white-space:nowrap}.logo span{color:#a78bfa}.spacer{flex:1} .btn{background:#191f29;color:#e8edf5;border:1px solid #303846;padding:8px 11px;border-radius:9px;transition:.15s;white-space:nowrap}.btn:hover{background:#242b37} .primary{background:#7c3aed;border-color:#8b5cf6;color:#fff}.primary:hover{background:#6d28d9} .live{font-size:10px;color:#7ee8a2;display:flex;align-items:center;gap:5px;white-space:nowrap}.live i{width:6px;height:6px;border-radius:50%;background:#5ee58a;box-shadow:0 0 8px #5ee58a} .badge{font-size:10px;background:#202734;color:#cbd3e1;padding:4px 7px;border-radius:6px} .section{margin-bottom:12px}.section h3{font-size:10px;text-transform:uppercase;color:#8d97a8;letter-spacing:1px;margin:3px 0 7px} .toolgrid{display:grid;grid-template-columns:1fr 1fr;gap:5px} .sidebar .section:nth-child(2) .toolgrid{grid-template-columns:repeat(3,1fr)} .sidebar .section:nth-child(3) .toolgrid{grid-template-columns:repeat(3,1fr)} .sidebar .section:nth-child(4) .toolgrid{grid-template-columns:repeat(4,1fr)} .tool{min-height:32px;padding:6px 4px;background:#171d26;color:#e7ebf2;border:1px solid #2a3240;border-radius:8px;font-size:10px;line-height:1.15}.tool:hover,.tool.active{border-color:#8b5cf6;background:#211b35} .animGrid{display:grid;grid-template-columns:repeat(3,1fr);gap:5px;max-height:165px;overflow-y:auto;background:#11151d;border:1px solid #2b3441;padding:5px;border-radius:7px;margin-bottom:8px} .animBtn{font-size:9px;padding:6px 2px;border-radius:5px;background:#171d26;color:#dce2eb;border:1px solid #2a3340;text-align:center;line-height:1.1} .animBtn:hover{background:#211b35;border-color:#8b5cf6} .animBtn.active{border-color:#a78bfa;background:#281e45;color:#fff;box-shadow:0 0 0 1px #8b5cf633} .stage{width:100px;height:100px;aspect-ratio:auto;max-width:calc(100% - 36px);max-height:calc(100% - 36px);background-color:#1b1f27;background-image:linear-gradient(45deg,#252b35 25%,transparent 25%),linear-gradient(-45deg,#252b35 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#252b35 75%),linear-gradient(-45deg,transparent 75%,#252b35 75%);background-size:28px 28px;background-position:0 0,0 14px,14px -14px,-14px 0;border:1px solid #394252;border-radius:14px;overflow:hidden;position:relative;box-shadow:0 18px 55px #000a} .stage.liveStage{box-shadow:0 0 0 1px #8b5cf655,0 18px 55px #000a} #canvas{width:100%;height:100%;display:block;cursor:grab}#canvas:active{cursor:grabbing} .empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;color:#858e9f;pointer-events:auto} .empty strong{display:block;color:#dfe5ef;font-size:17px;margin-bottom:4px} .uploadDeviceBtn{pointer-events:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;min-width:190px;padding:18px 24px;border:1px solid #3a4658;border-radius:14px;background:rgba(17,21,29,.92);color:#e8edf5;box-shadow:0 12px 35px #0007;transition:.16s} .uploadDeviceBtn:hover{border-color:#8b5cf6;background:#211b35;transform:translateY(-1px)} .uploadDeviceBtn .uploadIcon{width:34px;height:34px;display:grid;place-items:center;border-radius:10px;background:#2a1c48;color:#c4b5fd;font-size:18px} .uploadDeviceBtn strong{display:block;color:#f5f7fb;font-size:14px;margin:0} .uploadDeviceBtn small{color:#8e97a8;font-size:9px} .canvasHud{position:absolute;left:22px;top:18px;display:flex;gap:6px;pointer-events:none} .frameCanvasTools{position:absolute;right:14px;top:14px;display:none;gap:5px;z-index:20;pointer-events:auto}.frameCanvasTools.show{display:flex}.frameCanvasTools button{border:1px solid #58657a;background:#151a23;color:#fff;border-radius:8px;padding:7px 10px;font-size:12px;font-weight:700;cursor:pointer;box-shadow:0 5px 18px #0008}.frameCanvasTools button:hover{background:#252d3a}.frameCanvasTools .mainAdd{border-color:#8b5cf6;background:#6d42d8} .hud{background:#0d1219dd;border:1px solid #303948;color:#bfc8d6;border-radius:7px;padding:5px 7px;font-size:9px} .hud.liveHud{color:#8df0a8} .field{margin:7px 0}.field label{display:flex;justify-content:space-between;font-size:10px;color:#aeb7c6;margin-bottom:4px} .field input,.field select,.field textarea{width:100%;background:#181e27;border:1px solid #2b3441;color:#edf1f7;border-radius:7px;padding:7px} .field textarea{resize:vertical;min-height:60px}.range{accent-color:#8b5cf6}.value{color:#d9ccff}.row{display:flex;gap:6px}.row>*{flex:1}.muted{color:#7f8898;font-size:9px} .timelinehead{display:flex;align-items:center;gap:6px;margin-bottom:6px;min-width:max-content}.timelinehead strong{font-size:12px} .frames{display:flex;gap:7px;min-height:104px;padding-bottom:5px;align-items:flex-start} .frame{width:104px;flex:0 0 104px;background:#161b23;border:1px solid #2b3441;border-radius:8px;padding:4px;cursor:pointer} .frame.active{border-color:#8b5cf6;box-shadow:0 0 0 1px #8b5cf655}.thumb{width:100%;height:66px;object-fit:cover;border-radius:5px;background:#0b0f15}.frame .num{font-size:9px;color:#9ca5b4;margin-top:2px}.frame input{width:100%;margin-top:3px;background:#0e131a;border:1px solid #29313d;color:#ddd;border-radius:5px;padding:2px;font-size:9px} .textPanel{position:static;width:100%;max-height:none;overflow:visible;background:#0f141cf7;border:1px solid #394252;border-radius:14px;padding:12px;box-shadow:0 18px 55px #000c;z-index:30;display:none}.textPanel.show{display:block} .fontPicker{position:relative}.fontPickerBtn{width:100%;display:flex;align-items:center;justify-content:space-between;background:#181e27;border:1px solid #2b3441;color:#edf1f7;border-radius:7px;padding:8px 10px;cursor:pointer}.fontPickerBtn:hover{border-color:#8b5cf6}.fontPickerPanel{display:none;position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:80;background:#111720;border:1px solid #3a4352;border-radius:10px;padding:8px;box-shadow:0 18px 45px #000d;max-height:320px;overflow:auto}.fontPickerPanel.open{display:block}.fontSearch{width:100%;background:#0b1017;border:1px solid #303847;color:#fff;border-radius:7px;padding:8px;margin-bottom:7px;outline:none}.fontGrid{display:grid;grid-template-columns:1fr 1fr;gap:5px}.fontChoice{min-height:42px;text-align:left;background:#171d26;border:1px solid #2a3340;color:#f2f4f8;border-radius:7px;padding:6px 8px;cursor:pointer;line-height:1.05}.fontChoice:hover{border-color:#8b5cf6;background:#211b35}.fontChoice.active{border-color:#a78bfa;background:#2a2050;box-shadow:0 0 0 1px #8b5cf633}.fontChoice small{display:block;font-family:Inter,sans-serif;font-size:8px;color:#7f8998;margin-top:3px}.fontDone{width:100%;margin-top:7px;background:#6d42d8;color:#fff;border:0;border-radius:7px;padding:8px;font-weight:700;cursor:pointer}.fontLivePreview{margin-top:5px;font-size:12px;color:#aeb7c6;min-height:17px} .panelTitle{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.panelTitle b{font-size:11px;letter-spacing:.8px}.close{background:#1b212b;color:#adb6c5;border:1px solid #303847;border-radius:7px;width:26px;height:26px} .smallGrid{display:grid;grid-template-columns:1fr 1fr;gap:6px}.colorRow{display:grid;grid-template-columns:1fr 1fr;gap:6px}.colorRow input[type=color]{height:32px;padding:2px} .effectGrid{display:grid;grid-template-columns:repeat(4,1fr);gap:5px} .effectBtn,.frameStyleBtn{font-size:9px;padding:7px 3px;border-radius:7px;background:#171d26;color:#dce2eb;border:1px solid #2a3340}.effectBtn.active,.frameStyleBtn.active{border-color:#a78bfa;background:#281e45;box-shadow:0 0 0 1px #8b5cf633} .tabbar{display:flex;gap:5px;margin-bottom:8px}.tab{flex:1;background:#171d26;color:#9fa8b8;border:1px solid #2a3340;padding:7px 5px;border-radius:7px;font-size:9px}.tab.active{background:#2a2050;color:#fff;border-color:#8b5cf6}.hint{font-size:9px;color:#788394;line-height:1.35;margin-top:6px}.scopeBtn{flex:1;background:#171d26;color:#9fa8b8;border:1px solid #2a3340;padding:7px 5px;border-radius:7px;font-size:9px;cursor:pointer}.scopeBtn.active{background:#2a2050;color:#fff;border-color:#8b5cf6}.scopeBtn:hover{border-color:#8b5cf6} .frameStyles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}.framePreview{height:30px;border:1px dashed #505a6b;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:8px;color:#cbd3df} .styleSale{background:linear-gradient(135deg,#ef4444,#f97316);border:0;border-radius:4px;color:#fff;font-weight:800}.styleRibbon{background:#7c3aed;color:#fff;border:0;transform:skew(-8deg)}.styleArrow{background:#f59e0b;color:#111;border:0;clip-path:polygon(0 0,82% 0,100% 50%,82% 100%,0 100%,12% 50%)}.styleBadge{background:#0ea5e9;color:#fff;border:0;border-radius:999px}.styleHighlight{background:#fde047;color:#111;border:0}.styleBox{background:#1e293b;color:#fff;border:1px solid #94a3b8}.styleSpeech{background:#fff;color:#111;border:0;border-radius:8px;position:relative}.styleCircle{background:#22c55e;color:#fff;border:0;border-radius:50%}.stylePill{background:#ec4899;color:#fff;border:0;border-radius:999px}.styleTicket{background:#f8fafc;color:#111;border:1px dashed #64748b}.styleTag{background:#f59e0b;color:#111;border:0;clip-path:polygon(0 0,82% 0,100% 50%,82% 100%,0 100%)}.styleBurst{background:#ef4444;color:#fff;border:0;clip-path:polygon(50% 0,61% 17%,80% 6%,83% 26%,100% 32%,85% 50%,100% 68%,82% 74%,80% 94%,61% 83%,50% 100%,39% 83%,20% 94%,17% 74%,0 68%,15% 50%,0 32%,18% 26%,20% 6%,39% 17%)}.styleNeon{background:#111827;color:#67e8f9;border:1px solid #22d3ee;box-shadow:0 0 8px #22d3ee}.styleTape{background:#fde68a;color:#111;border:0;transform:rotate(-3deg)} @media(max-width:1050px){ .app{grid-template-columns:205px minmax(0,1fr) 300px} .top .live{display:none}.textPanel{width:300px} } @media(max-width:820px){ .app{grid-template-columns:175px minmax(0,1fr);grid-template-rows:54px minmax(0,1fr) 145px} .inspector{display:none} .sidebar{grid-row:2;} .timeline{grid-column:1/3;grid-row:3;} .textPanel{width:285px} } .imageToolsSection .imageToolGrid{grid-template-columns:repeat(2,1fr)} .imageToolsSection .imageToolGrid .tool{min-height:38px} .imageToolStatus{margin-top:8px;padding:8px 9px;border:1px solid #272e39;border-radius:8px;background:#0f1218;color:#9ca3af;font-size:11px;line-height:1.35} .imageToolStatus.ok{color:#b8f7d1;border-color:#235c42} .imageToolStatus.active{color:#d9d1ff;border-color:#6f56d9;box-shadow:0 0 0 1px #6f56d933} .smallHint{font-size:10px;line-height:1.35;color:#747d8b;margin-top:7px} </style> </head> <body> <div class="app"> <header class="top"><div class="logo">GIF <span>STUDIO</span></div><button class="btn" id="newBtn">New</button><button class="btn" id="demoBtn">Demo</button><button class="btn" id="undoBtn">↶ Undo</button><button class="btn" id="redoBtn">↷ Redo</button><div class="spacer"></div><span class="live"><i></i> Live Preview</span><span class="badge" id="fpsBadge">12 FPS</span><button class="btn primary" id="previewBtn">▶ Preview</button><button class="btn primary" id="exportBtn">Export GIF</button></header> <section class="workspace"> <aside class="sidebar"> <div class="section"><h3>Import Main Background</h3><div class="toolgrid"><button class="tool" id="addImages">🖼 BG Images</button><button class="tool" id="addTextBtn">T Add Text</button><button class="tool" id="bgImgBtn">🖼 Solid BG Img</button><button class="tool" id="bgColBtn">🎨 BG Color</button></div></div> <div class="section"><h3>Main Frame Animation</h3><div class="toolgrid"> <button class="tool active" data-anim="none">None</button><button class="tool" data-anim="fade">Fade</button> <button class="tool" data-anim="zoom">Zoom In</button><button class="tool" data-anim="zoomOut">Zoom Out</button> <button class="tool" data-anim="slideUp">Slide Up</button><button class="tool" data-anim="slideDown">Slide Down</button> <button class="tool" data-anim="slide">Slide Left</button><button class="tool" data-anim="bounce">Bounce</button> <button class="tool" data-anim="rotate">Rotate</button><button class="tool" data-anim="float">Float</button> <button class="tool" data-anim="pulse">Pulse</button><button class="tool" data-anim="glitch">Glitch</button> <button class="tool" data-anim="swing">Swing</button><button class="tool" data-anim="blur">Blur</button> <button class="tool" data-anim="flip">Flip</button><button class="tool" data-anim="jello">Jello</button> </div></div> <div class="section"><h3>Particles</h3><div class="field"><label>Size <span id="partSizeVal">1x</span></label><input id="particleSize" class="range" type="range" min=".5" max="3" step=".1" value="1"></div> <div class="toolgrid"> <button class="tool active" data-particle="none">None</button> <button class="tool" data-particle="magicStars">✨ Roshan Stars</button> <button class="tool" data-particle="fallingStars">🌠 Girte Stars</button> <button class="tool" data-particle="fireworks">🎆 Aatish Bazi</button> <button class="tool" data-particle="spinningStars">💫 Ghoomte Stars</button> <button class="tool" data-particle="hearts">♥ Hearts</button> <button class="tool" data-particle="snow">❄ Snow</button> <button class="tool" data-particle="confetti">✺ Confetti</button> </div></div> <div class="section imageToolsSection"> <h3>Image Tools</h3> <div class="toolgrid imageToolGrid"> <button class="tool" id="selectImageTool">🎯 Select Image</button> <button class="tool" id="aiObjectTool">🧠 AI Select Object</button> <button class="tool" id="copyImageTool">📋 Copy</button> <button class="tool" id="pasteImageTool">📥 Paste</button> <button class="tool" id="removeBgTool">✂ Remove Background</button> <button class="tool" id="resetBgTool">↺ Reset Image</button> </div> <div class="imageToolStatus" id="imageToolStatus">Select an image/frame to use Image Tools</div> <div class="smallHint">🧠 AI Select Object: image ke main foreground object ko isolate karke draggable sticker banata hai. Ye original background delete nahi karega.</div> <div class="field"><label>Background Removal Tolerance <span id="bgTolVal">42</span></label><input id="bgTol" class="range" type="range" min="5" max="120" step="1" value="42"></div> <div class="field"><label>Edge Softness <span id="bgSoftVal">8</span></label><input id="bgSoft" class="range" type="range" min="0" max="30" step="1" value="8"></div> <div class="smallHint">Auto mode removes the background color sampled from the image corners. Copy/Paste works with the system clipboard when the browser allows it.</div> </div> <input id="imageInput" type="file" accept="image/*" multiple hidden><input id="frameImageInput" type="file" accept="image/*" multiple hidden><input id="bgInput" type="file" accept="image/*" hidden><input id="stickerInput" type="file" accept="image/png,image/webp,image/gif,image/jpeg" multiple hidden> </aside> <main class="canvasarea"><div class="stage" id="dropStage"><canvas id="canvas" width="960" height="540"></canvas><div class="empty" id="empty"> <button class="uploadDeviceBtn" id="emptyUploadBtn" type="button"> <span class="uploadIcon">⬆</span> <strong>Upload from Device</strong> <small>PNG, JPG, WEBP or GIF</small> </button> </div><div class="canvasHud"><span class="hud liveHud">● LIVE</span><span class="hud" id="canvasFrameInfo">Frame 0 / 0</span></div><div class="frameCanvasTools" id="frameCanvasTools"><button class="mainAdd" id="canvasAddFrameImage">+ Add Image</button><button id="canvasStepUp" title="Bring selected image forward">⬆</button><button id="canvasStepBack" title="Send selected image backward">⬇</button></div></div> <div class="textPanel" id="textEditorPanel"> <div class="panelTitle"><b>TEXT EDITOR — NEXT LEVEL</b><button class="close" id="closeTextPanel">×</button></div> <div class="tabbar"><button class="tab active" data-tab="basic">Text</button><button class="tab" data-tab="anim">Animation</button><button class="tab" data-tab="frame">Text Frame</button><button class="tab" data-tab="effects">Effects</button></div> <div class="tabContent" id="tabBasic"> <div class="field"><label>Text</label><textarea id="te_text" placeholder="Enter text..."></textarea></div> <div class="smallGrid"><div class="field"><label>Font</label><div class="fontPicker" id="fontPicker"><button type="button" class="fontPickerBtn" id="fontPickerBtn"><span id="fontPickerLabel" style="font-family:Inter">Inter</span><span>▾</span></button><div class="fontPickerPanel" id="fontPickerPanel"><input class="fontSearch" id="fontSearch" placeholder="Search font..."><div class="fontGrid" id="fontGrid"></div><div class="fontLivePreview" id="fontLivePreview">Live preview: Inter</div><button type="button" class="fontDone" id="fontDone">Done — Keep Font</button></div></div><select id="te_font" hidden><option>Inter</option><option>Roboto</option><option>Poppins</option><option>Montserrat</option><option>Anton</option><option>Archivo Black</option><option>Bebas Neue</option><option>Bungee</option><option>Oswald</option><option>Rubik</option><option>Pacifico</option><option>Lobster</option><option>Playfair Display</option><option>Noto Nastaliq Urdu</option><option>Abril Fatface</option><option>Caveat</option><option>Comfortaa</option><option>Dancing Script</option><option>DM Sans</option><option>Lato</option><option>Merriweather</option><option>Nunito</option><option>Permanent Marker</option><option>Quicksand</option><option>Raleway</option><option>Space Grotesk</option></select></div><div class="field"><label>Size <span id="te_sizeVal">80</span></label><input id="te_size" type="range" min="16" max="260" value="80"></div></div> <div class="field"><label>Apply Text To</label><div class="row" id="textScope"><button type="button" class="scopeBtn active" data-text-scope="frame">1 Frame</button><button type="button" class="scopeBtn" data-text-scope="all">All Frames</button></div><div class="hint">1 Frame = sirf selected frame. All Frames = ye text aur iski settings tamam frames par.</div></div> <div class="smallGrid"><div class="field"><label>Fill Opacity <span id="te_fillOpVal">100%</span></label><input id="te_fillOp" type="range" min="0" max="1" step=".05" value="1"></div><div class="field"><label>Stroke <span id="te_strokeWVal">8</span></label><input id="te_strokeW" type="range" min="0" max="45" value="8"></div></div> <div class="colorRow"><div class="field"><label>Fill</label><input type="color" id="te_fill" value="#ffffff"></div><div class="field"><label>Stroke</label><input type="color" id="te_stroke" value="#000000"></div></div> <div class="smallGrid"><div class="field"><label>Stroke Opacity</label><input id="te_strokeOp" type="range" min="0" max="1" step=".05" value="1"></div><div class="field"><label>Letter Spacing <span id="te_spacingVal">0</span></label><input id="te_spacing" type="range" min="-8" max="20" value="0"></div></div> </div> <div class="tabContent" id="tabAnim" style="display:none"> <div class="field"><label>Trending Text Animation</label></div> <div class="animGrid" id="te_animGrid"> <button class="animBtn active" data-anim="none">None</button><button class="animBtn" data-anim="dynamicPop">Dynamic Pop</button><button class="animBtn" data-anim="springDrop">Spring Drop</button><button class="animBtn" data-anim="shimmer">Shimmer ✨</button><button class="animBtn" data-anim="neonPulse">Neon Pulse</button><button class="animBtn" data-anim="typewriter">Typewriter</button><button class="animBtn" data-anim="wordReveal">Word Reveal</button><button class="animBtn" data-anim="charPop">Char Pop</button><button class="animBtn" data-anim="pop">Pop</button><button class="animBtn" data-anim="elastic">Elastic</button><button class="animBtn" data-anim="sale">SALE Burst</button><button class="animBtn" data-anim="stamp">Stamp</button><button class="animBtn" data-anim="slideUp">Slide Up</button><button class="animBtn" data-anim="slideDown">Slide Down</button><button class="animBtn" data-anim="slideLeft">Slide Left</button><button class="animBtn" data-anim="slideRight">Slide Right</button><button class="animBtn" data-anim="rollLeft">Roll Left</button><button class="animBtn" data-anim="rollRight">Roll Right</button><button class="animBtn" data-anim="rollUp">Roll Up</button><button class="animBtn" data-anim="rollDown">Roll Down</button><button class="animBtn" data-anim="swingLeft">Swing Left</button><button class="animBtn" data-anim="swingRight">Swing Right</button><button class="animBtn" data-anim="swingUp">Swing Up</button><button class="animBtn" data-anim="swingDown">Swing Down</button><button class="animBtn" data-anim="centerTwist">Center Twist</button><button class="animBtn" data-anim="wordTwist">All Word Twist</button><button class="animBtn" data-anim="wordRoll">Word Roll</button><button class="animBtn" data-anim="letterTwist">Letter Twist</button><button class="animBtn" data-anim="arrowSwipe">Arrow Swipe</button><button class="animBtn" data-anim="bounceIn">Bounce In</button><button class="animBtn" data-anim="zoomIn">Zoom In</button><button class="animBtn" data-anim="rotateIn">Rotate In</button><button class="animBtn" data-anim="flipIn">Flip In</button><button class="animBtn" data-anim="blurIn">Blur In</button><button class="animBtn" data-anim="shake">Shake</button><button class="animBtn" data-anim="swing">Swing</button><button class="animBtn" data-anim="wave">Wave</button><button class="animBtn" data-anim="glitch">Glitch</button><button class="animBtn" data-anim="neonFlicker">Neon Flicker</button><button class="animBtn" data-anim="zoomPulse">Zoom Pulse</button><button class="animBtn" data-anim="rubber">Rubber</button><button class="animBtn" data-anim="whip">Whip</button><button class="animBtn" data-anim="dropBounce">Drop Bounce</button><button class="animBtn" data-anim="swingIn">Swing In</button><button class="animBtn" data-anim="flipY">Flip Y</button><button class="animBtn" data-anim="trackingIn">Tracking In</button><button class="animBtn" data-anim="revealUp">Reveal Up</button> </div> <div class="field"><label>Speed <span id="te_speedVal">1x</span></label><input id="te_speed" type="range" min=".25" max="4" step=".25" value="1"></div> <div class="field"><label>Delay <span id="te_delayVal">0%</span></label><input id="te_delay" type="range" min="0" max=".7" step=".05" value="0"></div> <div class="hint">Animation har selected text layer ke sath save hoti hai. Frame change karne par doosre frame ka text animation automatically apply hota hai.</div> </div> <div class="tabContent" id="tabFrame" style="display:none"> <div class="field"><label>Text Frame Style</label></div><div class="frameStyles" id="frameStyles"><button class="frameStyleBtn active" data-style="none"><span class="framePreview">None</span></button><button class="frameStyleBtn" data-style="sale"><span class="framePreview styleSale">SALE</span></button><button class="frameStyleBtn" data-style="ribbon"><span class="framePreview styleRibbon">TEXT</span></button><button class="frameStyleBtn" data-style="arrow"><span class="framePreview styleArrow">→</span></button><button class="frameStyleBtn" data-style="badge"><span class="framePreview styleBadge">NEW</span></button><button class="frameStyleBtn" data-style="highlight"><span class="framePreview styleHighlight">TEXT</span></button><button class="frameStyleBtn" data-style="box"><span class="framePreview styleBox">TEXT</span></button><button class="frameStyleBtn" data-style="speech"><span class="framePreview styleSpeech">HI</span></button><button class="frameStyleBtn" data-style="circle"><span class="framePreview styleCircle">GO</span></button><button class="frameStyleBtn" data-style="pill"><span class="framePreview stylePill">SALE</span></button><button class="frameStyleBtn" data-style="ticket"><span class="framePreview styleTicket">TICKET</span></button><button class="frameStyleBtn" data-style="tag"><span class="framePreview styleTag">PRICE</span></button><button class="frameStyleBtn" data-style="burst"><span class="framePreview styleBurst">WOW</span></button><button class="frameStyleBtn" data-style="neonBox"><span class="framePreview styleNeon">LIVE</span></button><button class="frameStyleBtn" data-style="tape"><span class="framePreview styleTape">NEW</span></button></div> <div class="field" style="margin-top:12px;"><label>Frame Animation</label></div> <div class="animGrid" id="te_frameAnimGrid" style="grid-template-columns:repeat(3, 1fr);"> <button class="animBtn active" data-anim="none">None</button><button class="animBtn" data-anim="pop">Pop</button><button class="animBtn" data-anim="slide">Slide</button><button class="animBtn" data-anim="stamp">Stamp</button><button class="animBtn" data-anim="wipe">Wipe</button><button class="animBtn" data-anim="pulse">Pulse</button><button class="animBtn" data-anim="shake">Shake</button><button class="animBtn" data-anim="swing">Swing</button><button class="animBtn" data-anim="arrowIn">Arrow In</button><button class="animBtn" data-anim="zoom">Zoom</button><button class="animBtn" data-anim="drop">Drop</button><button class="animBtn" data-anim="flip">Flip</button><button class="animBtn" data-anim="expand">Expand</button><button class="animBtn" data-anim="elastic">Elastic</button><button class="animBtn" data-anim="shimmer">Shimmer</button><button class="animBtn" data-anim="rotate">Rotate</button><button class="animBtn" data-anim="wipeY">Wipe Y</button> </div> <div class="smallGrid"><div class="field"><label>Frame Color</label><input type="color" id="te_frameColor" value="#ef4444"></div><div class="field"><label>Padding <span id="te_paddingVal">18</span></label><input id="te_padding" type="range" min="4" max="60" value="18"></div></div> <div class="field"><label>Frame Opacity <span id="te_frameOpVal">100%</span></label><input id="te_frameOp" type="range" min="0" max="1" step=".05" value="1"></div> </div> <div class="tabContent" id="tabEffects" style="display:none"><div class="field"><label>Text Effect</label></div><div class="effectGrid" id="effectGrid"><button class="effectBtn active" data-effect="none">None</button><button class="effectBtn" data-effect="shadow">Shadow</button><button class="effectBtn" data-effect="glow">Glow</button><button class="effectBtn" data-effect="neon">Neon</button><button class="effectBtn" data-effect="outline">Outline</button><button class="effectBtn" data-effect="3d">3D</button><button class="effectBtn" data-effect="gradient">Gradient</button><button class="effectBtn" data-effect="reflect">Reflect</button><button class="effectBtn" data-effect="fire">Fire</button><button class="effectBtn" data-effect="chrome">Chrome</button><button class="effectBtn" data-effect="glitch">Glitch</button><button class="effectBtn" data-effect="rgb">RGB Split</button><button class="effectBtn" data-effect="emboss">Emboss</button><button class="effectBtn" data-effect="hollow">Hollow</button></div></div> <button id="te_deleteBtn" class="btn" style="width:100%;margin-top:9px;background:#5b1d25;border-color:#7b2b36">Delete Text</button> </div></main> <aside class="inspector"> <div class="section"> <h3>Add Extra Layers</h3> <div class="row"> <button class="btn" id="addStickerBtn" style="background:#281e45; border-color:#8b5cf6; color:#d9ccff;">🧩 Add Sticker</button> <button class="btn" id="addFrameImageBtn" style="background:#152033; border-color:#3b82f6; color:#bfdbfe;">🖼 Inner Image</button> </div> </div> <div class="section" id="stickerInspector" style="display:none; padding:10px; background:#16102b; border:1px solid #382470; border-radius:8px; margin-bottom:12px;"> <h3 style="color:#d9ccff; margin-top:0;">★ Selected Sticker</h3> <div class="field"><label>CapCut Pro Animations</label></div> <div class="animGrid" id="stickerAnimGrid"> <button class="animBtn active" data-anim="none">None</button><button class="animBtn" data-anim="dynamicPop">Dynamic Pop</button><button class="animBtn" data-anim="springDrop">Spring Drop</button><button class="animBtn" data-anim="flyIn">Fly In</button><button class="animBtn" data-anim="zoomFade">Zoom Fade</button><button class="animBtn" data-anim="pendulum">Pendulum</button><button class="animBtn" data-anim="tiktokWobble">Tiktok Wobble</button><button class="animBtn" data-anim="floatSpin">Float & Spin</button><button class="animBtn" data-anim="tremble">Tremble</button><button class="animBtn" data-anim="heartbeat">Heartbeat</button><button class="animBtn" data-anim="pop">Basic Pop</button><button class="animBtn" data-anim="bounce">Bounce</button><button class="animBtn" data-anim="zoom">Zoom In</button><button class="animBtn" data-anim="zoomOut">Zoom Out</button><button class="animBtn" data-anim="slideUp">Slide Up</button><button class="animBtn" data-anim="slideDown">Slide Down</button><button class="animBtn" data-anim="slideLeft">Slide Left</button><button class="animBtn" data-anim="slideRight">Slide Right</button><button class="animBtn" data-anim="spin">Spin</button><button class="animBtn" data-anim="spinFast">Spin Fast</button><button class="animBtn" data-anim="orbit">Orbit</button><button class="animBtn" data-anim="flip">Flip</button><button class="animBtn" data-anim="float">Float</button><button class="animBtn" data-anim="swing">Swing</button><button class="animBtn" data-anim="shake">Shake</button><button class="animBtn" data-anim="pulse">Pulse</button><button class="animBtn" data-anim="rubber">Rubber</button><button class="animBtn" data-anim="elastic">Elastic</button><button class="animBtn" data-anim="jello">Jello</button><button class="animBtn" data-anim="glitch">Glitch</button><button class="animBtn" data-anim="drop">Drop</button> </div> <div class="field"><label>Animation Time / Speed <span id="stickerSpeedVal">1x</span></label><input id="stickerSpeed" class="range" type="range" min=".1" max="4" step=".1" value="1"></div> <div class="field"><label>Sticker Size <span id="stickerScaleVal">100%</span></label><input id="stickerScale" class="range" type="range" min=".1" max="4" step=".05" value="1"></div> <div class="field"><label>Rotation <span id="stickerRotationVal">0°</span></label><input id="stickerRotation" class="range" type="range" min="-180" max="180" step="1" value="0"></div> <div class="field" style="margin-top:12px"><label style="color:#a78bfa">Layer Ordering</label></div> <div class="row"> <button class="btn" id="stickerStepUp" style="background:#2d2159; border-color:#4c3896; color:#e2d9ff;">⬆ Step Up</button> <button class="btn" id="stickerStepBack" style="background:#2d2159; border-color:#4c3896; color:#e2d9ff;">⬇ Step Back</button> </div> <div class="row" style="margin-top:6px"> <button class="btn" id="deleteSticker" style="background:#5b1d25; border-color:#7b2b36; color:#ffccd2;">🗑 Delete Sticker</button> <button class="btn" id="duplicateSticker" style="background:#2d2159; border-color:#4c3896; color:#e2d9ff;">Duplicate</button> </div> </div> <div class="section" id="frameImageInspector" style="display:none; padding:10px; background:#101a2b; border:1px solid #1e3a8a; border-radius:8px; margin-bottom:12px;"> <h3 style="color:#bfdbfe; margin-top:0;">★ Selected Inner Image</h3> <div class="field"><label>CapCut Animations (Inner Image)</label></div> <div class="animGrid" id="innerAnimGrid"> <button class="animBtn active" data-anim="none">None</button><button class="animBtn" data-anim="dynamicPop">Dynamic Pop</button><button class="animBtn" data-anim="springDrop">Spring Drop</button><button class="animBtn" data-anim="flyIn">Fly In</button><button class="animBtn" data-anim="zoomFade">Zoom Fade</button><button class="animBtn" data-anim="pendulum">Pendulum</button><button class="animBtn" data-anim="tiktokWobble">Tiktok Wobble</button><button class="animBtn" data-anim="floatSpin">Float & Spin</button><button class="animBtn" data-anim="tremble">Tremble</button><button class="animBtn" data-anim="heartbeat">Heartbeat</button><button class="animBtn" data-anim="pop">Basic Pop</button><button class="animBtn" data-anim="bounce">Bounce</button><button class="animBtn" data-anim="zoom">Zoom In</button><button class="animBtn" data-anim="zoomOut">Zoom Out</button><button class="animBtn" data-anim="slideUp">Slide Up</button><button class="animBtn" data-anim="slideDown">Slide Down</button><button class="animBtn" data-anim="slideLeft">Slide Left</button><button class="animBtn" data-anim="slideRight">Slide Right</button><button class="animBtn" data-anim="spin">Spin</button><button class="animBtn" data-anim="spinFast">Spin Fast</button><button class="animBtn" data-anim="orbit">Orbit</button><button class="animBtn" data-anim="flip">Flip</button><button class="animBtn" data-anim="float">Float</button><button class="animBtn" data-anim="swing">Swing</button><button class="animBtn" data-anim="shake">Shake</button><button class="animBtn" data-anim="pulse">Pulse</button><button class="animBtn" data-anim="rubber">Rubber</button><button class="animBtn" data-anim="elastic">Elastic</button><button class="animBtn" data-anim="jello">Jello</button><button class="animBtn" data-anim="glitch">Glitch</button><button class="animBtn" data-anim="drop">Drop</button> </div> <div class="field" style="margin-top:12px"><label style="color:#60a5fa">Layer Options & Ordering</label></div> <div class="row"> <button class="btn" id="frameStepUp" style="background:#172a4f; border-color:#254687; color:#dbeafe;">⬆ Step Up</button> <button class="btn" id="frameStepBack" style="background:#172a4f; border-color:#254687; color:#dbeafe;">⬇ Step Back</button> </div> <div class="row" style="margin-top:6px"> <button class="btn" id="frameDeleteImage" style="background:#5b1d25; border-color:#7b2b36; color:#ffccd2;">🗑 Delete</button> <button class="btn" id="frameCenterImage" style="background:#172a4f; border-color:#254687; color:#dbeafe;">◎ Center</button> </div> </div> <div class="section" id="frameTransitionSection"> <h3>Frame-to-Frame Transition</h3> <div class="smallHint" style="margin-bottom:7px">Choose how this frame enters after the previous frame. Each button is a different transition.</div> <div class="animGrid" id="frameTransitionGrid" style="grid-template-columns:repeat(2,1fr);max-height:240px;"> <button class="animBtn active" data-frame-transition="none">No Transition</button> <button class="animBtn" data-frame-transition="crossDissolve">Cross Dissolve</button> <button class="animBtn" data-frame-transition="dipBlack">Dip to Black</button> <button class="animBtn" data-frame-transition="dipWhite">Dip to White</button> <button class="animBtn" data-frame-transition="filmDissolve">Film Dissolve</button> <button class="animBtn" data-frame-transition="pushLeft">Push Left</button> <button class="animBtn" data-frame-transition="pushRight">Push Right</button> <button class="animBtn" data-frame-transition="pushUp">Push Up</button> <button class="animBtn" data-frame-transition="pushDown">Push Down</button> <button class="animBtn" data-frame-transition="slideLeft">Slide Left</button> <button class="animBtn" data-frame-transition="slideRight">Slide Right</button> <button class="animBtn" data-frame-transition="slideUp">Slide Up</button> <button class="animBtn" data-frame-transition="slideDown">Slide Down</button> <button class="animBtn" data-frame-transition="zoom">Zoom</button> <button class="animBtn" data-frame-transition="spinZoom">Spin + Zoom</button> <button class="animBtn" data-frame-transition="clockWipe">Clock Wipe</button> <button class="animBtn" data-frame-transition="radialWipe">Radial Wipe</button> <button class="animBtn" data-frame-transition="barnDoors">Barn Doors</button> <button class="animBtn" data-frame-transition="venetian">Venetian Blinds</button> <button class="animBtn" data-frame-transition="checkerboard">Checkerboard</button> <button class="animBtn" data-frame-transition="iris">Iris</button> <button class="animBtn" data-frame-transition="linearWipe">Linear Wipe</button> </div> <div class="field"><label>Transition Strength <span id="transitionStrengthVal">100%</span></label> <input id="transitionStrength" class="range" type="range" min="0" max="1" step=".05" value="1"> </div> </div> <div class="section"><h3>Frame Duration & FX</h3><div class="field"><label>Duration <span class="value" id="durVal">0.8s</span></label><input id="duration" class="range" type="range" min=".1" max="5" step=".1" value=".8"></div><div class="field"><label>Frame Appearance <span id="fxVal">none</span></label><select id="fx"><option>none</option><option>fade</option><option>grayscale</option><option>sepia</option><option>glow</option><option>blur</option><option>vhs</option><option>neon</option></select></div></div> <div class="section"><h3>Export</h3><div class="field"><label>Format</label><select id="exportFormat"><option value="gif">GIF — Animated</option><option value="png">PNG — Current Frame</option><option value="jpg">JPG — Current Frame</option><option value="webp">WebP — Current Frame</option></select></div><div class="field"><label>Aspect Ratio</label><select id="aspectRatio"><option value="16:9">16:9 — Landscape</option><option value="9:16">9:16 — Portrait</option><option value="1:1">1:1 — Square</option><option value="4:5">4:5 — Social</option><option value="1.91:1">1.91:1 — Ad</option><option value="fit">Fit to Selected Image</option><option value="screen">Fit Preview to Screen</option></select></div><div class="row"><div class="field"><label>Width</label><input id="width" type="number" value="640"></div><div class="field"><label>Height</label><input id="height" type="number" value="360"></div></div><div class="field"><label>FPS <span class="value" id="fpsVal">12</span></label><input id="fps" class="range" type="range" min="5" max="30" value="12"></div><div class="field"><label>Loop</label><select id="loop"><option value="0">Infinite</option><option value="1">1 Time</option></select></div><button class="btn primary" id="exportFileBtn" style="width:100%;margin-top:6px">⬇ Export File</button></div> <div class="section"><h3>Quick Actions</h3><div class="row"><button class="btn" id="duplicate">Duplicate Frame</button><button class="btn" id="delete">Delete Frame</button></div></div> </aside> </section> <section class="timeline"><div class="timelinehead"><strong>Timeline</strong><span class="muted" id="timelineInfo">0 frames</span><button class="btn" id="play">▶ Preview</button><button class="btn" id="stop">■ Stop</button><button class="btn" id="addFrameBtn">+ Add Frame</button></div><div class="frames" id="frames"></div></section> </div> <script> const S={frames:[],selected:0,playing:false};const $=id=>document.getElementById(id);const canvas=$('canvas'),ctx=canvas.getContext('2d'),imgInput=$('imageInput'),bgInput=$('bgInput');let previewRAF=null,previewIndex=0,previewFrameStart=0,activeText=null,draggingText=null,dragOffsetX=0,dragOffsetY=0; // ADDED NEW TRENDING PARTICLES const stickerSets={ hearts:['♥','♡','❤'], stars:['★','☆','✦'], sparkle:['✦','✧','✩','✨'], snow:['❄','❅','❆'], confetti:['◆','●','■','▲','✦'], magicStars:['✨','🌟','✦','⭐'], spinningStars:['💫','⭐','🌟','✨'], fallingStars:['🌠','☄️','✨','🌟'], fireworks:['🎆','🎇','💥','✨'] }; function fit(img,w=canvas.width,h=canvas.height){const r=Math.min(w/img.width,h/img.height),nw=img.width*r,nh=img.height*r;return[(w-nw)/2,(h-nh)/2,nw,nh]} function makeFrame(src){const img=new Image();img.onload=()=>draw(performance.now());img.src=src;return{img,duration:.8,anim:'none',transition:'none',transitionStrength:1,fx:'none',particle:'none',particleSize:1,bgCol:'transparent',bgDeviceImg:null,texts:[],frameImages:[]}} function addImages(files){if(!files?.length)return;[...files].forEach(file=>{if(file.type?.startsWith('image/'))S.frames.push(makeFrame(URL.createObjectURL(file)))});S.selected=Math.max(0,S.frames.length-1);renderFrames()} function renderFrames(){const box=$('frames');box.innerHTML='';S.frames.forEach((f,i)=>{const d=document.createElement('div');d.className='frame '+(i===S.selected?'active':'');d.innerHTML=`<img class="thumb" src="${f.img.src}"><div class="num">Frame ${i+1}</div><input type="number" min=".1" max="5" step=".1" value="${f.duration}">`;d.onclick=()=>{S.selected=i;activeText=null;closeText();syncInspector();renderFrames()};const inp=d.querySelector('input');inp.onclick=e=>e.stopPropagation();inp.onchange=e=>{f.duration=+e.target.value;syncInspector();draw(performance.now())};box.appendChild(d)});$('timelineInfo').textContent=S.frames.length+' frames';$('empty').style.display=S.frames.length?'none':'flex';syncInspector();draw(performance.now())} function syncInspector(){const f=S.frames[S.selected];if(!f){$('canvasFrameInfo').textContent='Frame 0 / 0';return}$('duration').value=f.duration;$('durVal').textContent=f.duration.toFixed(1)+'s';$('fx').value=f.fx;$('fxVal').textContent=f.fx;$('particleSize').value=f.particleSize;$('partSizeVal').textContent=f.particleSize+'x';$('canvasFrameInfo').textContent=`Frame ${S.selected+1} / ${S.frames.length}`;document.querySelectorAll('[data-anim]').forEach(b=>b.classList.toggle('active',b.dataset.anim===(f.anim||'none')));document.querySelectorAll('[data-frame-transition]').forEach(b=>b.classList.toggle('active',b.dataset.frameTransition===(f.transition||'none')));if($('transitionStrength')){$('transitionStrength').value=f.transitionStrength??1;$('transitionStrengthVal').textContent=Math.round((f.transitionStrength??1)*100)+'%'};document.querySelectorAll('[data-particle]').forEach(b=>b.classList.toggle('active',b.dataset.particle===(f.particle||'none')))} function easeOut(p){p=Math.max(0,Math.min(1,p));return 1-Math.pow(1-p,3)}function easeBack(p){const c=1.70158;return 1+(c+1)*Math.pow(p-1,3)+c*Math.pow(p-1,2)} function textMetrics(txt){ctx.save();ctx.font=`${txt.size}px "${txt.font}",sans-serif`;const lines=txt.text.split('\n');const widths=lines.map(l=>ctx.measureText(l).width);ctx.restore();return{lines,w:Math.max(30,...widths),h:lines.length*txt.size*1.22}} function openTextEditor(txt){ activeText=txt;$('textEditorPanel').classList.add('show');$('te_text').value=txt.text;$('te_font').value=txt.font;$('te_size').value=txt.size;$('te_fill').value=txt.fill;$('te_fillOp').value=txt.fillOpacity??1;$('te_stroke').value=txt.stroke;$('te_strokeW').value=txt.strokeWidth;$('te_strokeOp').value=txt.strokeOpacity??1;$('te_speed').value=txt.speed||1;$('te_delay').value=txt.delay||0;$('te_frameColor').value=txt.frameColor||'#ef4444';$('te_frameOp').value=txt.frameOpacity??1;$('te_padding').value=txt.padding??18;$('te_sizeVal').textContent=txt.size;$('te_fillOpVal').textContent=Math.round((txt.fillOpacity??1)*100)+'%';$('te_strokeWVal').textContent=txt.strokeWidth;$('te_spacingVal').textContent=txt.spacing||0;$('te_speedVal').textContent=(txt.speed||1)+'x';$('te_delayVal').textContent=Math.round((txt.delay||0)*100)+'%';$('te_frameOpVal').textContent=Math.round((txt.frameOpacity??1)*100)+'%';$('te_paddingVal').textContent=txt.padding??18; document.querySelectorAll('.effectBtn').forEach(b=>b.classList.toggle('active',b.dataset.effect===(txt.effect||'none'))); document.querySelectorAll('.frameStyleBtn').forEach(b=>b.classList.toggle('active',b.dataset.style===(txt.frameStyle||'none'))); document.querySelectorAll('#te_animGrid .animBtn').forEach(b=>b.classList.toggle('active',b.dataset.anim===(txt.anim||'none'))); document.querySelectorAll('#te_frameAnimGrid .animBtn').forEach(b=>b.classList.toggle('active',b.dataset.anim===(txt.frameAnim||'none'))); const fontLabel=$('fontPickerLabel'); if(fontLabel){fontLabel.textContent=txt.font||'Inter';fontLabel.style.fontFamily=txt.font||'Inter';$('fontLivePreview').textContent='Live preview: '+(txt.font||'Inter');$('fontLivePreview').style.fontFamily=txt.font||'Inter';document.querySelectorAll('.fontChoice').forEach(x=>x.classList.toggle('active',x.dataset.font===(txt.font||'Inter')));} document.querySelectorAll('#textScope .scopeBtn').forEach(b=>b.classList.toggle('active',b.dataset.textScope===textApplyScope)); draw(performance.now()); } function closeText(){activeText=null;$('textEditorPanel').classList.remove('show');draw(performance.now())} $('closeTextPanel').onclick=closeText;let textApplyScope='frame';let textGroupCounter=1;function cloneTextData(t,group){const c=JSON.parse(JSON.stringify(t));c.groupId=group;return c}function ensureTextGroup(txt){if(!txt)return null;if(!txt.groupId)txt.groupId='tg_'+Date.now()+'_'+(textGroupCounter++);return txt.groupId}function getGroupTexts(txt){const g=ensureTextGroup(txt),out=[];S.frames.forEach(f=>(f.texts||[]).forEach(t=>{if(t.groupId===g)out.push(t)}));return out}function applyTextProp(prop,value){if(!activeText)return;if(textApplyScope==='all'){const g=ensureTextGroup(activeText);S.frames.forEach((f)=>{(f.texts||[]).forEach(t=>{if(t.groupId===g)t[prop]=value})})}else activeText[prop]=value}function setTextScope(scope){textApplyScope=scope;document.querySelectorAll('#textScope .scopeBtn').forEach(b=>b.classList.toggle('active',b.dataset.textScope===scope));if(activeText){const g=ensureTextGroup(activeText);if(scope==='all'){activeText.scope='all';S.frames.forEach((f,fi)=>{if(fi===S.selected)return;const existing=(f.texts||[]).find(t=>t.groupId===g);if(!existing)f.texts.push(cloneTextData(activeText,g));(f.texts||[]).forEach(t=>{if(t.groupId===g)t.scope='all'});});}else{S.frames.forEach(f=>(f.texts||[]).forEach(t=>{if(t===activeText||t.groupId===g)t.scope='frame'}));}renderFrames()}}$('addTextBtn').onclick=()=>{if(!S.frames.length)return alert('Pehle image/frame add karein!');const choice=window.confirm('Text kahan apply karna hai?\n\nOK = Main Canvas (All Frames)\nCancel = 1 Frame (Current Frame)');textApplyScope=choice?'all':'frame';document.querySelectorAll('#textScope .scopeBtn').forEach(b=>b.classList.toggle('active',b.dataset.textScope===textApplyScope));const group='tg_'+Date.now()+'_'+(textGroupCounter++);const txt={text:'Double Click\nTo Edit',x:canvas.width/2,y:canvas.height/2,font:'Inter',size:80,fill:'#fff',fillOpacity:1,stroke:'#000',strokeWidth:8,strokeOpacity:1,spacing:0,anim:'none',speed:1,delay:0,effect:'none',frameStyle:'none',frameAnim:'none',frameColor:'#ef4444',frameOpacity:1,padding:18,groupId:group,scope:textApplyScope};if(textApplyScope==='all'){S.frames.forEach((f)=>f.texts.push(cloneTextData(txt,group)));}else S.frames[S.selected].texts.push(txt);openTextEditor(textApplyScope==='all'?S.frames[S.selected].texts.find(t=>t.groupId===group):txt)};document.querySelectorAll('#textScope .scopeBtn').forEach(b=>{b.type='button';b.onclick=(e)=>{e.preventDefault();e.stopPropagation();setTextScope(b.dataset.textScope);}}); ['te_text','te_font','te_fill','te_stroke','te_frameColor'].forEach(id=>{$(id).oninput=e=>{if(!activeText)return;const prop=id.replace('te_','');applyTextProp(prop,e.target.value);if(id==='te_font'){document.fonts.load(`${activeText.size}px "${e.target.value}"`);$('fontPickerLabel').textContent=e.target.value;$('fontPickerLabel').style.fontFamily=e.target.value;$('fontLivePreview').textContent='Live preview: '+e.target.value;$('fontLivePreview').style.fontFamily=e.target.value;document.querySelectorAll('.fontChoice').forEach(x=>x.classList.toggle('active',x.dataset.font===e.target.value));}draw(performance.now())};$(id).onchange=$(id).oninput}); $('te_size').oninput=e=>{if(activeText){applyTextProp('size',+e.target.value);$('te_sizeVal').textContent=e.target.value;draw(performance.now())}};$('te_fillOp').oninput=e=>{if(activeText){applyTextProp('fillOpacity',+e.target.value);$('te_fillOpVal').textContent=Math.round(+e.target.value*100)+'%';draw(performance.now())}};$('te_strokeW').oninput=e=>{if(activeText){applyTextProp('strokeWidth',+e.target.value);$('te_strokeWVal').textContent=e.target.value;draw(performance.now())}};$('te_strokeOp').oninput=e=>{if(activeText){applyTextProp('strokeOpacity',+e.target.value);draw(performance.now())}};$('te_spacing').oninput=e=>{if(activeText){applyTextProp('spacing',+e.target.value);$('te_spacingVal').textContent=e.target.value;draw(performance.now())}};$('te_speed').oninput=e=>{if(activeText){applyTextProp('speed',+e.target.value);$('te_speedVal').textContent=e.target.value+'x';draw(performance.now())}};$('te_delay').oninput=e=>{if(activeText){applyTextProp('delay',+e.target.value);$('te_delayVal').textContent=Math.round(+e.target.value*100)+'%';draw(performance.now())}};$('te_frameOp').oninput=e=>{if(activeText){applyTextProp('frameOpacity',+e.target.value);$('te_frameOpVal').textContent=Math.round(+e.target.value*100)+'%';draw(performance.now())}};$('te_padding').oninput=e=>{if(activeText){applyTextProp('padding',+e.target.value);$('te_paddingVal').textContent=e.target.value;draw(performance.now())}}; document.querySelectorAll('.effectBtn').forEach(b=>b.onclick=()=>{if(!activeText)return;applyTextProp('effect',b.dataset.effect);document.querySelectorAll('.effectBtn').forEach(x=>x.classList.remove('active'));b.classList.add('active');draw(performance.now())}); document.querySelectorAll('.frameStyleBtn').forEach(b=>b.onclick=()=>{if(!activeText)return;applyTextProp('frameStyle',b.dataset.style);document.querySelectorAll('.frameStyleBtn').forEach(x=>x.classList.remove('active'));b.classList.add('active');draw(performance.now())}); document.querySelectorAll('.tabbar .tab').forEach(tab=>tab.onclick=()=>{document.querySelectorAll('.tabbar > .tab').forEach(x=>x.classList.remove('active'));tab.classList.add('active');const name=tab.dataset.tab;['basic','anim','frame','effects'].forEach(n=>{$('tab'+n.charAt(0).toUpperCase()+n.slice(1)).style.display=n===name?'block':'none'})}); $('te_deleteBtn').onclick=()=>{if(!activeText||!S.frames.length)return;historyPush();if(textApplyScope==='all'&&activeText.groupId){const g=activeText.groupId;S.frames.forEach(f=>f.texts=f.texts.filter(t=>t.groupId!==g));}else S.frames[S.selected].texts=S.frames[S.selected].texts.filter(t=>t!==activeText);closeText();renderFrames()}; $('addImages').onclick=()=>imgInput.click();imgInput.onchange=e=>addImages(e.target.files); if($('emptyUploadBtn'))$('emptyUploadBtn').onclick=()=>imgInput.click(); if($('bgImgBtn')) $('bgImgBtn').onclick=()=>{ if(!S.frames.length)return alert('Add frame first!'); bgInput.value=''; bgInput.click(); }; if(bgInput) bgInput.onchange=e=>{ const file=e.target.files?.[0]; if(!file||!S.frames.length)return; const im=new Image(); im.onload=()=>{ const f=S.frames[S.selected]; historyPush&&historyPush(); f.bgDeviceImg=im; f.bgCol='transparent'; draw(performance.now()); renderFrames(); }; im.onerror=()=>alert('Background image load nahi ho saki.'); im.src=URL.createObjectURL(file); }; $('bgColBtn').onclick=()=>{if(!S.frames.length)return;const c=prompt('Enter HEX or color name:',S.frames[S.selected].bgCol==='transparent'?'#000000':S.frames[S.selected].bgCol);if(c){S.frames[S.selected].bgCol=c;draw(performance.now())}}; document.querySelectorAll('[data-anim]').forEach(btn=>btn.onclick=()=>{if(!S.frames.length)return;if(btn.closest('#te_animGrid')||btn.closest('#te_frameAnimGrid')||btn.closest('#stickerAnimGrid')||btn.closest('#innerAnimGrid'))return; S.frames[S.selected].anim=btn.dataset.anim;syncInspector();draw(performance.now())}); document.querySelectorAll('[data-frame-transition]').forEach(btn=>btn.onclick=()=>{ if(!S.frames.length)return; S.frames[S.selected].transition=btn.dataset.frameTransition||'none'; document.querySelectorAll('[data-frame-transition]').forEach(b=>b.classList.toggle('active',b===btn)); draw(performance.now()); renderFrames(); }); if($('transitionStrength'))$('transitionStrength').oninput=()=>{ if(!S.frames.length)return; S.frames[S.selected].transitionStrength=+$('transitionStrength').value; $('transitionStrengthVal').textContent=Math.round(S.frames[S.selected].transitionStrength*100)+'%'; draw(performance.now()); }; document.querySelectorAll('[data-particle]').forEach(btn=>btn.onclick=()=>{if(!S.frames.length)return;S.frames[S.selected].particle=btn.dataset.particle;syncInspector();draw(performance.now())});$('particleSize').oninput=e=>{if(S.frames.length){S.frames[S.selected].particleSize=+e.target.value;$('partSizeVal').textContent=e.target.value+'x';draw(performance.now())}};$('fx').onchange=e=>{if(S.frames.length){S.frames[S.selected].fx=e.target.value;$('fxVal').textContent=e.target.value;draw(performance.now())}};$('duration').oninput=e=>{if(S.frames.length){S.frames[S.selected].duration=+e.target.value;$('durVal').textContent=(+e.target.value).toFixed(1)+'s';renderFrames()}};$('fps').oninput=e=>{$('fpsVal').textContent=e.target.value;$('fpsBadge').textContent=e.target.value+' FPS'}; /* ================= NEXT LEVEL PATCH: stickers + history + proper text engines ================= */ (function(){ const inspector=document.querySelector('.inspector'), panel=$('textEditorPanel'); if(inspector && panel && panel.parentElement!==inspector){ inspector.prepend(panel); } if(panel){ panel.classList.remove('show'); } S.activeSticker=null; window.__editorPreviewStart=performance.now(); const _makeFrame=makeFrame; makeFrame=function(src){ const f=_makeFrame(src); f.stickers=[]; return f; }; S.frames.forEach(f=>{if(!f.stickers)f.stickers=[]}); const undoStack=[], redoStack=[]; let restoring=false; function snap(){ return JSON.stringify({selected:S.selected,frames:S.frames.map(f=>({ duration:f.duration,anim:f.anim,transitionStrength:f.transitionStrength??1,transition:f.transition||'none',fx:f.fx,particle:f.particle,particleSize:f.particleSize,bgCol:f.bgCol, imgSrc:f.img?.src||'',bgSrc:f.bgDeviceImg?.src||'',texts:JSON.parse(JSON.stringify(f.texts||[])), stickers:(f.stickers||[]).map(st=>{const x={...st};x.imgSrc=st.img?.src||'';delete x.img;return x}), frameImages:(f.frameImages||[]).map(li=>{const x={...li};x.imgSrc=li.img?.src||'';delete x.img;return x}) }))}); } function restore(raw){ restoring=true; const d=JSON.parse(raw); S.frames=d.frames.map(f=>{ const nf=makeFrame(f.imgSrc); Object.assign(nf,{duration:f.duration,anim:f.anim,transitionStrength:f.transitionStrength??1,transition:f.transition||'none',fx:f.fx,particle:f.particle,particleSize:f.particleSize,bgCol:f.bgCol||'transparent',texts:f.texts||[],stickers:[],frameImages:[]}); if(f.bgSrc){const im=new Image();im.onload=()=>draw(performance.now());im.src=f.bgSrc;nf.bgDeviceImg=im;} (f.stickers||[]).forEach(st=>{const im=new Image();Object.assign(st,{img:im});im.onload=()=>draw(performance.now());im.src=st.imgSrc||'';nf.stickers.push(st)}); (f.frameImages||[]).forEach(li=>{const im=new Image();const copy={...li,img:im};im.onload=()=>draw(performance.now());im.src=li.imgSrc||'';nf.frameImages.push(copy)}); return nf; }); S.selected=Math.max(0,Math.min(d.selected||0,S.frames.length-1)); S.activeSticker=null; closeText(); renderFrames(); restoring=false; updateHistoryButtons(); } function historyPush(){if(restoring)return; const now=snap(); if(undoStack[undoStack.length-1]===now)return; undoStack.push(now); if(undoStack.length>60)undoStack.shift(); redoStack.length=0;} function undo(){if(!undoStack.length)return; const current=snap(); redoStack.push(current); restore(undoStack.pop()); updateHistoryButtons()} function redo(){if(!redoStack.length)return; const current=snap(); undoStack.push(current); restore(redoStack.pop()); updateHistoryButtons()} function updateHistoryButtons(){ $('undoBtn').disabled=!undoStack.length; $('redoBtn').disabled=!redoStack.length; } $('undoBtn').onclick=undo; $('redoBtn').onclick=redo; const _openTextEditor=openTextEditor; openTextEditor=function(txt){ if(inspector&&panel&&panel.parentElement!==inspector)inspector.prepend(panel); _openTextEditor(txt); panel.classList.add('show'); }; const _closeText=closeText; closeText=function(){ _closeText(); if(panel)panel.classList.remove('show'); }; function animProgress(p,txt,loop){ const delay=Math.max(0,Math.min(.9,txt.delay||0)); if(p<=delay)return 0; const base=(p-delay)/(1-delay), speed=Math.max(.1,Number(txt.speed)||1); if(loop){ return (base*speed)%1; } return Math.min(1, base*speed); } const loopingAnims=new Set(['shake','swing','wave','glitch','neonFlicker','pulse','wobble','shimmer','neonPulse','pendulum','tiktokWobble','floatSpin','tremble','heartbeat']); // NAYA HELPER FUNCTION JO MISSING THA (Text Frame Render fix) function roundRect(x, y, w, h, r) { ctx.beginPath(); if(ctx.roundRect){ ctx.roundRect(x, y, w, h, r); } else { ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); } ctx.closePath(); } window.drawTextFrame = function(txt,p,metrics){ const style=txt.frameStyle||'none'; if(style==='none')return; const pad=txt.padding??18,w=metrics.w+pad*2,h=metrics.h+pad*1.25; let scale=1,alpha=txt.frameOpacity??1,dx=0,dy=0,rot=0; const a=txt.frameAnim||'none'; const q=animProgress(p,{speed:txt.frameSpeed||1,delay:txt.frameDelay||0},false); const pulse=.5+.5*Math.sin(q*Math.PI*2); if(a==='pop'){scale=.45+.55*easeOut(q);alpha*=q}else if(a==='slide'){dx=-100*(1-easeOut(q));alpha*=q}else if(a==='stamp'){scale=.2+easeBack(q)*.8;alpha*=q;rot=(1-q)*-.18}else if(a==='wipe'){alpha*=q}else if(a==='arrowIn'){dx=-180*(1-easeOut(q));alpha*=q}else if(a==='pulse'){scale=1+.07*pulse}else if(a==='shake'){dx=Math.sin(q*Math.PI*18)*6}else if(a==='swing'){rot=Math.sin(q*Math.PI*3)*.14}else if(a==='zoom'){scale=.2+.8*easeOut(q);alpha*=q}else if(a==='drop'){dy=-100*(1-easeOut(q));alpha*=q}else if(a==='flip'){scale=.25+.75*q;rot=(1-q)*Math.PI;alpha*=q}else if(a==='expand'){scale=.05+.95*easeOut(q);alpha*=q}else if(a==='elastic'){scale=.2+Math.max(0,easeBack(q))*.8;alpha*=q}else if(a==='rotate'){rot=(1-q)*.7;alpha*=q}else if(a==='shimmer'){alpha*=.75+.25*Math.sin(q*Math.PI*8)}else if(a==='wipeY'){alpha*=q} ctx.save();ctx.translate(dx,dy);ctx.rotate(rot);ctx.scale(scale,scale);ctx.globalAlpha=alpha;const c=txt.frameColor||'#ef4444'; if(style==='sale'){ctx.fillStyle=c;roundRect(-w/2,-h/2,w,h,14);ctx.fill();ctx.fillStyle='rgba(0,0,0,.16)';ctx.fillRect(-w/2,h*.12,w,h*.38)} else if(style==='ribbon'){ctx.fillStyle=c;roundRect(-w/2,-h*.44,w,h*.88,7);ctx.fill();ctx.fillStyle='#fff';ctx.fillRect(-w/2-12,-h*.24,14,h*.48);ctx.fillRect(w/2-2,-h*.24,14,h*.48)} else if(style==='arrow'){ctx.fillStyle=c;ctx.beginPath();ctx.moveTo(-w/2,-h/2);ctx.lineTo(w*.28,-h/2);ctx.lineTo(w/2,0);ctx.lineTo(w*.28,h/2);ctx.lineTo(-w/2,h/2);ctx.lineTo(-w*.34,0);ctx.closePath();ctx.fill()} else if(style==='badge'||style==='pill'){ctx.fillStyle=c;roundRect(-w/2,-h/2,w,h,999);ctx.fill()} else if(style==='highlight'){ctx.fillStyle=c;roundRect(-w/2,-h*.33,w,h*.66,5);ctx.fill()} else if(style==='box'){ctx.strokeStyle=c;ctx.lineWidth=4;roundRect(-w/2,-h/2,w,h,10);ctx.stroke()} else if(style==='speech'){ctx.fillStyle=c;roundRect(-w/2,-h/2,w,h*.82,12);ctx.fill();ctx.beginPath();ctx.moveTo(-w*.12,h*.30);ctx.lineTo(-w*.02,h*.56);ctx.lineTo(w*.05,h*.30);ctx.fill()} else if(style==='circle'){ctx.fillStyle=c;ctx.beginPath();ctx.arc(0,0,Math.max(w,h)*.62,0,Math.PI*2);ctx.fill()} else if(style==='ticket'){ctx.fillStyle=c;roundRect(-w/2,-h/2,w,h,8);ctx.fill();ctx.save();ctx.globalCompositeOperation='destination-out';for(let yy=-h/2+8;yy<h/2;yy+=12){ctx.beginPath();ctx.arc(-w/2,yy,4,0,Math.PI*2);ctx.arc(w/2,yy,4,0,Math.PI*2);ctx.fill()}ctx.restore()} else if(style==='tag'){ctx.fillStyle=c;ctx.beginPath();ctx.moveTo(-w/2,-h/2);ctx.lineTo(w*.25,-h/2);ctx.lineTo(w/2,0);ctx.lineTo(w*.25,h/2);ctx.lineTo(-w/2,h/2);ctx.closePath();ctx.fill()} else if(style==='burst'){ctx.fillStyle=c;ctx.beginPath();for(let i=0;i<20;i++){const r=i%2?Math.min(w,h)*.48:Math.min(w,h)*.68,ang=-Math.PI/2+i*Math.PI/10;ctx.lineTo(Math.cos(ang)*r,Math.sin(ang)*r)}ctx.closePath();ctx.fill()} else if(style==='neonBox'){ctx.strokeStyle=c;ctx.shadowColor=c;ctx.shadowBlur=18;ctx.lineWidth=3;roundRect(-w/2,-h/2,w,h,10);ctx.stroke()} else if(style==='tape'){ctx.fillStyle=c;ctx.rotate(-.04);roundRect(-w/2,-h/2,w,h,4);ctx.fill()} if(a==='wipe'){ctx.save();ctx.globalCompositeOperation='destination-in';ctx.fillStyle='#fff';ctx.fillRect(-w/2,-h/2,w*q,h);ctx.restore()}else if(a==='wipeY'){ctx.save();ctx.globalCompositeOperation='destination-in';ctx.fillStyle='#fff';ctx.fillRect(-w/2,-h/2,w,h*q);ctx.restore()} ctx.restore(); } window.renderTextLines = function(t){ const lines=t.text.split('\n'),lh=t.size*1.22;const fill=t.fill||'#fff',effect=t.effect||'none';function each(fn){lines.forEach((line,i)=>fn(line,(i-(lines.length-1)/2)*lh))}if(!t.text)return; if(effect==='shadow'||effect==='3d'){ctx.save();ctx.shadowColor='rgba(0,0,0,.75)';ctx.shadowBlur=effect==='3d'?5:14;ctx.shadowOffsetX=effect==='3d'?8:5;ctx.shadowOffsetY=effect==='3d'?10:6;ctx.fillStyle=effect==='3d'?'#4b267d':fill;each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} if(effect==='glow'||effect==='neon'){ctx.save();ctx.shadowColor=fill;ctx.shadowBlur=effect==='neon'?28:18;ctx.fillStyle=fill;each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} if(effect==='fire'){ctx.save();ctx.shadowColor='#ff6a00';ctx.shadowBlur=24;ctx.fillStyle='#ffd166';each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} if(effect==='chrome'){const g=ctx.createLinearGradient(0,-t.size,0,t.size);g.addColorStop(0,'#fff');g.addColorStop(.3,'#666');g.addColorStop(.5,'#fff');g.addColorStop(.75,'#555');g.addColorStop(1,'#eee');ctx.fillStyle=g;each((l,y)=>ctx.fillText(l,0,y))} if(effect==='gradient'){const g=ctx.createLinearGradient(-t.size*2,0,t.size*2,0);g.addColorStop(0,fill);g.addColorStop(.5,'#fff');g.addColorStop(1,'#8b5cf6');ctx.fillStyle=g;each((l,y)=>ctx.fillText(l,0,y))} if(effect==='rgb'){ctx.save();ctx.globalAlpha*=.45;ctx.fillStyle='#00eaff';ctx.translate(-6,0);each((l,y)=>ctx.fillText(l,0,y));ctx.fillStyle='#ff1744';ctx.translate(12,0);each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} if(t.strokeWidth>0){ctx.save();ctx.globalAlpha=t.strokeOpacity??1;ctx.lineJoin='round';ctx.lineWidth=t.strokeWidth;ctx.strokeStyle=t.stroke||'#000';each((l,y)=>ctx.strokeText(l,0,y));ctx.restore()} ctx.fillStyle=fill;ctx.globalAlpha=t.fillOpacity??1;each((l,y)=>ctx.fillText(l,0,y)); if(effect==='outline'||effect==='hollow'){ctx.save();ctx.globalAlpha=t.fillOpacity??1;ctx.lineWidth=Math.max(2,t.strokeWidth+3);ctx.strokeStyle=t.stroke||'#fff';each((l,y)=>ctx.strokeText(l,0,y));if(effect==='hollow'){ctx.globalCompositeOperation='destination-out';ctx.lineWidth=Math.max(1,t.size*.08);each((l,y)=>ctx.strokeText(l,0,y))}ctx.restore()} if(effect==='reflect'){ctx.save();ctx.globalAlpha*=.2;ctx.scale(1,-1);ctx.translate(0,-t.size*1.5);each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} if(effect==='glitch'){ctx.save();ctx.globalAlpha*=.45;ctx.fillStyle='#00ffff';ctx.translate(-8,2);each((l,y)=>ctx.fillText(l,0,y));ctx.fillStyle='#ff004c';ctx.translate(16,-2);each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} if(effect==='emboss'){ctx.save();ctx.globalAlpha=.5;ctx.fillStyle='#fff';ctx.translate(-2,-2);each((l,y)=>ctx.fillText(l,0,y));ctx.fillStyle='#000';ctx.translate(4,4);each((l,y)=>ctx.fillText(l,0,y));ctx.restore()} }; window.drawText = function(txt,p){ if(!txt?.text)return; const m=textMetrics(txt), a=txt.anim||'none', loop=loopingAnims.has(a), q=animProgress(p,txt,loop); let x=txt.x,y=txt.y,sc=1,rot=0,alpha=1,blur=0,extraY=0,extraX=0; if(a==='pop'){sc=.55+.45*easeOut(q);alpha=q} else if(a==='dynamicPop'){sc=q<.5?.2+1.8*q:1+.1*Math.sin((q-.5)*Math.PI*4);alpha=Math.min(1,q*2)} else if(a==='springDrop'){y-=200*(1-q)*Math.abs(Math.cos(q*Math.PI*4));alpha=Math.min(1,q*3)} else if(a==='elastic'){sc=.15+Math.max(0,easeBack(q))*.85;alpha=q} else if(a==='sale'){sc=.4+easeBack(q)*.6;alpha=q;rot=(1-q)*-.12} else if(a==='stamp'){sc=.25+.75*easeOut(q);alpha=q;rot=(1-q)*-.28} else if(a==='slideUp'){y+=100*(1-easeOut(q));alpha=q} else if(a==='slideDown'){y-=100*(1-easeOut(q));alpha=q} else if(a==='slideLeft'){x+=150*(1-easeOut(q));alpha=q} else if(a==='slideRight'){x-=150*(1-easeOut(q));alpha=q} else if(a==='arrowSwipe'){x+=180*(1-easeOut(q));alpha=q} else if(a==='bounceIn'){sc=.1+easeBack(q)*.9;alpha=q} else if(a==='zoomIn'){sc=.05+.95*easeOut(q);alpha=q} else if(a==='rotateIn'){rot=(1-easeOut(q))*.6;sc=.75+.25*q;alpha=q} else if(a==='flipIn'){sc=.2+.8*q;rot=(1-q)*Math.PI;alpha=q} else if(a==='blurIn'){blur=(1-q)*12;alpha=q} else if(a==='shake'){extraX=Math.sin(q*Math.PI*18)*10;extraY=Math.cos(q*Math.PI*15)*5} else if(a==='rollLeft'){extraX=-canvas.width*.32*(1-easeOut(q));rot=-(1-q)*Math.PI*2;alpha=q} else if(a==='rollRight'){extraX=canvas.width*.32*(1-easeOut(q));rot=(1-q)*Math.PI*2;alpha=q} else if(a==='rollUp'){extraY=-canvas.height*.28*(1-easeOut(q));rot=-(1-q)*Math.PI*2;alpha=q} else if(a==='rollDown'){extraY=canvas.height*.28*(1-easeOut(q));rot=(1-q)*Math.PI*2;alpha=q} else if(a==='swingLeft'){rot=-(1-q)*.65;extraX=-28*(1-q);alpha=q} else if(a==='swingRight'){rot=(1-q)*.65;extraX=28*(1-q);alpha=q} else if(a==='swingUp'){rot=-(1-q)*.65;extraY=-28*(1-q);alpha=q} else if(a==='swingDown'){rot=(1-q)*.65;extraY=28*(1-q);alpha=q} else if(a==='centerTwist'){rot=(1-q)*Math.PI*2;sc=.72+.28*easeOut(q);alpha=q} else if(a==='wordTwist'||a==='wordRoll'||a==='letterTwist'){alpha=q} else if(a==='swing'){rot=Math.sin(q*Math.PI*3)*.25} else if(a==='wave'){extraY=Math.sin(q*Math.PI*2)*12} else if(a==='glitch'){extraX=(Math.floor(performance.now()/45)%3-1)*8} else if(a==='neonFlicker'){alpha=.65+.35*Math.abs(Math.sin(performance.now()/70))} else if(a==='zoomPulse'){sc=1+.08*Math.sin(q*Math.PI*2)} else if(a==='rubber'){sc=1+.12*Math.sin(q*Math.PI*2)*(1-q)} else if(a==='whip'){extraX=Math.sin(q*Math.PI)*180*(1-q);alpha=q} else if(a==='dropBounce'){y-=140*(1-easeOut(q));sc=.85+.15*easeOut(q);alpha=q} else if(a==='swingIn'){rot=(1-easeOut(q))*-.55;alpha=q} else if(a==='flipY'){sc=.1+.9*q;rot=Math.sin((1-q)*Math.PI);alpha=q} else if(a==='trackingIn'){sc=.75+.25*q;alpha=q} else if(a==='revealUp'){y+=80*(1-easeOut(q));alpha=q} else if(a==='shimmer'){alpha=0.6+0.4*Math.sin(q*Math.PI*10); extraX=Math.sin(q*Math.PI*20)*2;} else if(a==='neonPulse'){sc=1+0.05*Math.sin(q*Math.PI*8);blur=2+2*Math.sin(q*Math.PI*8);} let visible=txt.text;if(a==='typewriter')visible=txt.text.slice(0,Math.floor(txt.text.length*q)); if(a==='wordReveal'){const words=txt.text.split(/(\s+)/);let count=Math.floor(words.filter(w=>!/\s+/.test(w)).length*q),seen=0;visible=words.map(w=>/^\s+$/.test(w)?w:(seen++<count?w:'')).join('')} if(a==='charPop'){visible=txt.text} ctx.save();ctx.translate(x+extraX,y+extraY);ctx.rotate(rot);ctx.scale(sc,sc);ctx.textAlign='center';ctx.textBaseline='middle';ctx.font=`${txt.size}px "${txt.font}",sans-serif`;ctx.filter=blur?`blur(${blur}px)`:'none'; const fillOpacity=(txt.fillOpacity??1)*alpha,strokeOpacity=(txt.strokeOpacity??1)*alpha; drawTextFrame(txt,q,m); if(a==='charPop'){ const chars=[...txt.text];let cursor=-m.w/2;ctx.save();chars.forEach((ch,i)=>{const cp=Math.max(0,Math.min(1,q*chars.length-i));const ss=.25+.75*easeOut(cp);ctx.save();ctx.translate(cursor+ctx.measureText(ch)/2,0);ctx.scale(ss,ss);renderTextLines({text:ch,size:txt.size,fill:txt.fill,fillOpacity:fillOpacity,stroke:txt.stroke,strokeWidth:txt.strokeWidth,strokeOpacity:strokeOpacity,effect:txt.effect});ctx.restore();cursor+=ctx.measureText(ch).width});ctx.restore(); } else if(a==='wordTwist'||a==='wordRoll'){ const words=txt.text.split(/(\s+)/), widths=words.map(w=>ctx.measureText(w).width), total=widths.reduce((a,b)=>a+b,0), start=-total/2; let cursor=start, wi=0; ctx.save(); words.forEach((w,i)=>{if(!w){return} const isSpace=/^\s+$/.test(w); if(isSpace){cursor+=widths[i];return} const cp=Math.max(0,Math.min(1,q*words.filter(x=>!/^\s+$/.test(x)).length-wi)); const angle=a==='wordTwist'?(1-cp)*Math.PI*2:(1-cp)*Math.PI*1.25; const sc=.45+.55*easeOut(cp); ctx.save();ctx.translate(cursor+widths[i]/2,0);ctx.rotate(angle);ctx.scale(sc,sc);renderTextLines({text:w,size:txt.size,fill:txt.fill,fillOpacity:fillOpacity,stroke:txt.stroke,strokeWidth:txt.strokeWidth,strokeOpacity:strokeOpacity,effect:txt.effect});ctx.restore();cursor+=widths[i];wi++});ctx.restore(); } else if(a==='letterTwist'){ const chars=[...txt.text], total=chars.reduce((n,ch)=>n+ctx.measureText(ch).width,0);let cursor=-total/2;ctx.save();chars.forEach((ch,i)=>{const cp=Math.max(0,Math.min(1,q*chars.length-i));const ang=(1-cp)*Math.PI*1.6;const sc=.55+.45*easeOut(cp);const cw=ctx.measureText(ch).width;ctx.save();ctx.translate(cursor+cw/2,0);ctx.rotate(ang);ctx.scale(sc,sc);renderTextLines({text:ch,size:txt.size,fill:txt.fill,fillOpacity:fillOpacity,stroke:txt.stroke,strokeWidth:txt.strokeWidth,strokeOpacity:strokeOpacity,effect:txt.effect});ctx.restore();cursor+=cw});ctx.restore(); } else renderTextLines({...txt,text:visible,fillOpacity,strokeOpacity}); ctx.restore(); if(txt===activeText&&!S.playing){ctx.save();ctx.strokeStyle='#a78bfa';ctx.lineWidth=2;ctx.setLineDash([6,5]);ctx.strokeRect(txt.x-m.w/2-28,txt.y-m.h/2-22,m.w+56,m.h+44);ctx.setLineDash([]);ctx.restore()} }; const stickerIntroAnims=new Set(['pop','dynamicPop','springDrop','flyIn','zoomFade','zoom','zoomOut','slideUp','slideDown','slideLeft','slideRight','drop','spring']); function stickerProgress(p,st){ const base=Math.max(0,Math.min(1,p)); const speed=Math.max(.1,Number(st.speed)||1); if(stickerIntroAnims.has(st.anim||'none')){ return Math.min(1, base*speed); } return base*speed; } function getAnimProps(a, q) { let scale=1, rot=0, alpha=1, dx=0, dy=0, sx=1, sy=1; if(a==='pop'){scale*=.25+.75*easeOut(q)} else if(a==='dynamicPop'){scale*=q<.5?.2+1.8*q:1+.1*Math.sin((q-.5)*Math.PI*4);alpha*=Math.min(1,q*2)} else if(a==='springDrop'){dy=-200*(1-q)*Math.abs(Math.cos(q*Math.PI*4));alpha*=Math.min(1,q*3)} else if(a==='flyIn'){dx=-150*(1-easeOut(q));rot+=(1-easeOut(q))*-Math.PI;alpha*=q} else if(a==='zoomFade'){scale*=.4+.6*easeOut(q);alpha*=q} else if(a==='bounce'){dy=-35*Math.abs(Math.sin(q*Math.PI*2));} else if(a==='zoom'){scale*=.1+.9*easeOut(q)} else if(a==='zoomOut'){scale*=1.9-.9*easeOut(q)} else if(a==='slideUp'){dy=90*(1-easeOut(q))} else if(a==='slideDown'){dy=-90*(1-easeOut(q))} else if(a==='slideLeft'){dx=110*(1-easeOut(q))} else if(a==='slideRight'){dx=-110*(1-easeOut(q))} else if(a==='spin'){rot+=q*Math.PI*2} else if(a==='spinFast'){rot+=q*Math.PI*6} else if(a==='rotateCW'){rot+=q*Math.PI/2} else if(a==='rotateCCW'){rot-=q*Math.PI/2} else if(a==='rotate3D'){sx=Math.cos(q*Math.PI*2);rot+=q*Math.PI*2;scale*=.92+.08*Math.abs(sx)} else if(a==='roll'){dx=(q-.5)*220;rot+=q*Math.PI*2} else if(a==='orbit'){dx=Math.cos(q*Math.PI*2)*45;dy=Math.sin(q*Math.PI*2)*25;rot+=q*Math.PI*2} else if(a==='flip'){sx=Math.cos(q*Math.PI*2);scale*=Math.max(.05,Math.abs(sx))} else if(a==='flipX'){sx=Math.cos(q*Math.PI*2);scale*=Math.max(.05,Math.abs(sx))} else if(a==='flipY'){sy=Math.cos(q*Math.PI*2);scale*=Math.max(.05,Math.abs(sy))} else if(a==='float'){dy=Math.sin(q*Math.PI*2)*18} else if(a==='floatSpin'){dy=Math.sin(q*Math.PI*2)*15; rot+=q*Math.PI*2} else if(a==='pendulum'){rot+=Math.sin(q*Math.PI*2)*.35} else if(a==='tiktokWobble'){rot+=Math.sin(q*Math.PI*4)*.15; scale*=1+.05*Math.cos(q*Math.PI*4)} else if(a==='tremble'){dx=(Math.random()-.5)*8; dy=(Math.random()-.5)*8; rot+=(Math.random()-.5)*.1} else if(a==='heartbeat'){scale*=1+.15*Math.pow(Math.sin(q*Math.PI*2),4)} else if(a==='swing'){rot+=Math.sin(q*Math.PI*2)*.25} else if(a==='shake'){dx=Math.sin(q*Math.PI*14)*8} else if(a==='pulse'){scale*=1+.08*Math.sin(q*Math.PI*2)} else if(a==='wobble'){rot+=Math.sin(q*Math.PI*4)*.18} else if(a==='rubber'){scale*=1+.18*Math.sin(q*Math.PI*4)*(1-q)} else if(a==='elastic'){scale*=.85+.15*Math.sin(q*Math.PI/2)} else if(a==='jello'){rot+=Math.sin(q*Math.PI*6)*.12;scale*=1+.06*Math.sin(q*Math.PI*6)} else if(a==='flash'){alpha*=q<.2?0:q<.4?1:.75} else if(a==='glitch'){dx=(Math.random()-.5)*14;dy=(Math.random()-.5)*8} else if(a==='drop'){dy=-220*(1-easeOut(q))} else if(a==='spring'){scale*=.5+.5*easeBack(q)} return {scale, rot, alpha, dx, dy, sx, sy}; } function drawSticker(st,p,time){ if(!st?.img?.naturalWidth)return; const q=stickerProgress(p,st),a=st.anim||'none'; const props = getAnimProps(a, q); const x=st.x,y=st.y; const scale=(st.scale||1)*props.scale, rot=(st.rotation||0)+props.rot, alpha=(st.opacity??1)*props.alpha; const dx=props.dx, dy=props.dy, sx=props.sx, sy=props.sy; const [ix,iy,iw,ih]=fit(st.img,st.baseW||120,st.baseH||120); ctx.save();ctx.globalAlpha=alpha;ctx.translate(x+dx,y+dy);ctx.rotate(rot);ctx.scale(scale*sx,scale*sy); ctx.drawImage(st.img,-iw/2,-ih/2,iw,ih); if(st===S.activeSticker&&!S.playing){ctx.strokeStyle='#22d3ee';ctx.lineWidth=2;ctx.setLineDash([5,4]);ctx.strokeRect(-iw/2-8,-ih/2-8,iw+16,ih+16);ctx.setLineDash([])} ctx.restore(); } function drawParticles(type,time,size){ if(!type||type==='none')return; const set=stickerSets[type]||stickerSets.sparkle; ctx.save();ctx.textAlign='center';ctx.textBaseline='middle'; for(let i=0;i<40;i++){ const seed=i*999.91, z=1+(i%4)*.6; let x=(seed*1.37)%canvas.width, y, rot=time*.001*(i%3+1), s=(14+(i%8)*4)*size/z, a=(.3+.5*((i%5)/5))/(z*.8); if(type==='fireworks'){ const t = ((time*.001 + seed*.1)%2); y = canvas.height - (t*500/z) + (t*t*200); x += Math.sin(t*5+seed)*50; s *= Math.max(0, 1-t*.4); a *= Math.max(0, 1-t*.5); } else if(type==='fallingStars'){ const t = ((time*.002 + seed*.1)%2); x = (x + t*300) % canvas.width; y = (t*800/z) - 100; rot = Math.PI/4; a *= Math.max(0, 1 - Math.abs(t-0.5)*1.5); } else { const speed=(.035+(i%7)*.006)/z; y=((seed*.73+time*speed)%(canvas.height+100))-50; if(type==='spinningStars') rot *= 5; if(type==='magicStars') a *= (0.5 + 0.5*Math.sin(time*0.005 + seed)); } ctx.globalAlpha=Math.max(0,Math.min(1,a)); ctx.save();ctx.translate(x,y);ctx.rotate(rot);ctx.font=`${s}px "Segoe UI Emoji",sans-serif`;ctx.fillText(set[i%set.length],0,0);ctx.restore(); } ctx.restore(); } function drawStickers(f,p,time){(f.stickers||[]).forEach(st=>drawSticker(st,p,time))} function drawFrameImages(f, p){ (f.frameImages||[]).forEach(layer=>{ if(!layer?.img?.naturalWidth)return; const w=layer.w||240,h=layer.h||180; const q=stickerProgress(p,layer), a=layer.anim||'none'; const props = getAnimProps(a, q); const x=layer.x||canvas.width/2, y=layer.y||canvas.height/2; const scale=(layer.scale||1)*props.scale, rot=(layer.rotation||0)+props.rot, alpha=(layer.opacity??1)*props.alpha; const dx=props.dx, dy=props.dy, sx=props.sx, sy=props.sy; ctx.save();ctx.globalAlpha=alpha;ctx.translate(x+dx,y+dy);ctx.rotate(rot);ctx.scale(scale*sx,scale*sy); ctx.drawImage(layer.img,-w/2,-h/2,w,h); if(layer===S.activeFrameImage&&!S.playing){ctx.strokeStyle='#8a6cff';ctx.lineWidth=2;ctx.setLineDash([6,4]);ctx.strokeRect(-w/2-5,-h/2-5,w+10,h+10);ctx.setLineDash([])} ctx.restore(); }); } function drawFrameBackdrop(targetCtx, f, alpha=1){ targetCtx.save(); targetCtx.globalAlpha=alpha; if(f.bgCol&&f.bgCol!=='transparent'){targetCtx.fillStyle=f.bgCol;targetCtx.fillRect(0,0,canvas.width,canvas.height)} if(f.bgDeviceImg){ const bw=f.bgDeviceImg.naturalWidth||f.bgDeviceImg.width, bh=f.bgDeviceImg.naturalHeight||f.bgDeviceImg.height; if(bw&&bh){const br=Math.max(canvas.width/bw,canvas.height/bh),dw=bw*br,dh=bh*br;targetCtx.drawImage(f.bgDeviceImg,(canvas.width-dw)/2,(canvas.height-dh)/2,dw,dh);} } targetCtx.restore(); } function transitionImageProps(type,q,dir){ let x=0,y=0,scale=1,alpha=1,rot=0,sx=1,sy=1; const e=easeOut(q), inDir=dir==='in'; if(inDir){ switch(type){ case'fade': alpha=e; break; case'crossDissolve': alpha=e; break; case'dipBlack': alpha=e; break; case'dipWhite': alpha=e; break; case'filmDissolve': alpha=1; break; case'slideLeft': x=-(1-e)*canvas.width; break; case'slideRight': x=(1-e)*canvas.width; break; case'slideUp': y=(1-e)*canvas.height; break; case'slideDown': y=-(1-e)*canvas.height; break; case'pushLeft': x=(1-e)*canvas.width; break; case'pushRight': x=-(1-e)*canvas.width; break; case'pushUp': y=(1-e)*canvas.height; break; case'pushDown': y=-(1-e)*canvas.height; break; case'zoom': scale=.72+.28*e; break; case'spinZoom': scale=.65+.35*e; rot=(1-e)*-.75; alpha=e; break; case'zoomIn': scale=.72+.28*e; break; case'zoomOut': scale=1.25-.25*e; alpha=e; break; case'zoomFade': scale=.62+.38*e; alpha=e; break; case'wipeLeft': break; case'wipeRight': break; case'blur': alpha=e; break; case'rotate': rot=(1-e)*-.55; scale=.84+.16*e; alpha=e; break; case'bounce': scale=.55+.45*easeBack(e); alpha=e; break; case'elastic': scale=.35+.65*(1-Math.exp(-7*e)*Math.cos(e*14)); alpha=e; break; case'clockWipe': alpha=1; break; case'radialWipe': alpha=1; break; case'barnDoors': alpha=1; break; case'venetian': alpha=1; break; case'checkerboard': alpha=1; break; case'iris': scale=1; alpha=1; break; case'linearWipe': alpha=1; break; case'split': alpha=1; break; default: alpha=e; } } else { switch(type){ case'fade': alpha=1-e; break; case'crossDissolve': alpha=1-e; break; case'dipBlack': alpha=1; break; case'dipWhite': alpha=1; break; case'filmDissolve': alpha=1; break; case'slideLeft': x=e*canvas.width; break; case'slideRight': x=-e*canvas.width; break; case'slideUp': y=-e*canvas.height; break; case'slideDown': y=e*canvas.height; break; case'pushLeft': x=-e*canvas.width; break; case'pushRight': x=e*canvas.width; break; case'pushUp': y=-e*canvas.height; break; case'pushDown': y=e*canvas.height; break; case'zoom': scale=1+.10*e; alpha=1-e*.2; break; case'spinZoom': scale=1+.08*e; rot=e*.28; alpha=1-e*.2; break; case'zoomIn': scale=1+.10*e; alpha=1-e*.2; break; case'zoomOut': scale=1-.08*e; alpha=1-e*.2; break; case'zoomFade': scale=1+.10*e; alpha=1-e; break; case'wipeLeft': break; case'wipeRight': break; case'blur': alpha=1-e; break; case'rotate': rot=e*.18; alpha=1-e*.15; break; case'bounce': scale=1+.08*e; alpha=1-e*.15; break; case'elastic': scale=1+.06*e; alpha=1-e*.15; break; case'clockWipe': alpha=1; break; case'radialWipe': alpha=1; break; case'barnDoors': alpha=1; break; case'venetian': alpha=1; break; case'checkerboard': alpha=1; break; case'iris': scale=1; alpha=1; break; case'linearWipe': alpha=1; break; case'split': alpha=1; break; default: alpha=1-e; } } return{x,y,scale,alpha,rot,sx,sy}; } function clipTransitionMask(type,q,dir){ if(dir!=='in')return false; const e=Math.max(0,Math.min(1,q)); const W=canvas.width,H=canvas.height,cx=W/2,cy=H/2; ctx.beginPath(); if(type==='clockWipe' || type==='radialWipe'){ const end=-Math.PI/2 + e*Math.PI*2; ctx.moveTo(cx,cy); ctx.arc(cx,cy,Math.hypot(W,H),-Math.PI/2,end,false); ctx.closePath(); ctx.clip(); return true; } if(type==='iris'){ ctx.arc(cx,cy,Math.hypot(W,H)*e,0,Math.PI*2); ctx.clip(); return true; } if(type==='wipeLeft'){ ctx.rect(W*(1-e),0,W*e,H); ctx.clip(); return true; } if(type==='wipeRight'){ ctx.rect(0,0,W*e,H); ctx.clip(); return true; } if(type==='linearWipe'){ ctx.rect(0,0,W*e,H); ctx.clip(); return true; } if(type==='barnDoors'){ const half=W*e/2; ctx.rect(cx-half,0,half*2,H); ctx.clip(); return true; } if(type==='venetian'){ const n=10,sw=W/n; for(let i=0;i<n;i++){const hh=H*e;ctx.rect(i*sw,0,sw,hh);} ctx.clip(); return true; } if(type==='checkerboard'){ const cols=8,rows=6,cw=W/cols,rh=H/rows; const count=Math.floor(e*(cols*rows)); for(let i=0;i<count;i++){const c=i%cols,r=Math.floor(i/cols);ctx.rect(c*cw,r*rh,cw+.5,rh+.5);} ctx.clip(); return true; } if(type==='split'){ const half=W*e/2; ctx.rect(cx-half,0,half*2,H); ctx.clip(); return true; } return false; } function drawTransitionFrame(f, type, q, dir){ const im=f?.img;if(!im?.naturalWidth)return; const [x,y,w,h]=fit(im), props=transitionImageProps(type,q,dir); ctx.save(); if(clipTransitionMask(type,q,dir)){ // mask already applied; keep the image fully opaque ctx.globalAlpha=1; } else { ctx.globalAlpha=Math.max(0,Math.min(1,props.alpha)); } ctx.translate(x+w/2+props.x,y+h/2+props.y); ctx.rotate(props.rot); ctx.scale(props.scale*props.sx,props.scale*props.sy); ctx.translate(-(x+w/2),-(y+h/2)); ctx.drawImage(im,x,y,w,h); ctx.restore(); } function isGroupedCanvasText(txt){ if(!txt?.groupId)return false; let count=0; S.frames.forEach(f=>{if((f.texts||[]).some(x=>x.groupId===txt.groupId))count++}); return count>1; } window.draw=function(t=performance.now()){ ctx.clearRect(0,0,canvas.width,canvas.height); if(!S.frames.length){ctx.fillStyle='#161a22';ctx.fillRect(0,0,canvas.width,canvas.height);return} const f=S.frames[S.playing?previewIndex:S.selected]; const dur=Math.max(.1,f.duration)*1000; const start=S.playing?previewFrameStart:window.__editorPreviewStart; const transition=(S.playing&&f.transition&&f.transition!=='none')?f.transition:'none'; const strength=Math.max(0,Math.min(1,f.transitionStrength??1)); // IMPORTANT: transition and frame animation use SEPARATE timelines. // The transition gets its own time slice first; the destination frame animation // starts only after the transition finishes. This prevents effects such as // wave/glitch/zoom/etc. from appearing to drive or restart the frame transition. const transitionDuration=Math.max(220,Math.min(520,dur*.42))*Math.max(.35,strength); const elapsed=Math.max(0,t-start); const tq=Math.max(0,Math.min(1,elapsed/transitionDuration)); const frameElapsed=Math.max(0,elapsed-transitionDuration); const frameAnimDuration=Math.max(100,dur-transitionDuration); let p=Math.max(0,Math.min(1,frameElapsed/frameAnimDuration)); const skipFirstLoopTransition=!!S.__exportSingleRun && previewIndex===0; if(transition!=='none' && tq<1 && !skipFirstLoopTransition){ const prev=S.frames[(previewIndex-1+S.frames.length)%S.frames.length]; // Real transition composition: start from the outgoing frame, then reveal/blend the incoming frame. if(transition==='dipBlack' || transition==='dipWhite'){ const q=tq; if(q<.5){ drawFrameBackdrop(ctx,prev,1); drawTransitionFrame(prev,transition, q*2, 'out'); ctx.save(); ctx.globalAlpha=q*2; ctx.fillStyle=transition==='dipBlack'?'#000':'#fff'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.restore(); }else{ drawFrameBackdrop(ctx,f,1); drawTransitionFrame(f,transition,(q-.5)*2,'in'); ctx.save(); ctx.globalAlpha=1-(q-.5)*2; ctx.fillStyle=transition==='dipBlack'?'#000':'#fff'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.restore(); } }else if(transition==='filmDissolve'){ drawFrameBackdrop(ctx,prev,1); drawTransitionFrame(prev,transition,tq,'out'); ctx.save(); ctx.globalAlpha=.5+.5*Math.sin(tq*Math.PI*12); ctx.globalCompositeOperation='screen'; drawTransitionFrame(f,transition,tq,'in'); ctx.restore(); ctx.globalCompositeOperation='source-over'; }else{ drawFrameBackdrop(ctx,prev,1); drawTransitionFrame(prev,transition,tq,'out'); drawTransitionFrame(f,transition,tq,'in'); } } else { drawFrameBackdrop(ctx,f,1); const img=f.img; if(!img?.naturalWidth)return; const [x,y,w,h]=fit(img); let animP=p; const transitionStren=Math.max(0,Math.min(1,f.transitionStrength??1)); animP=transitionStren*p+(1-transitionStren)*1; p=animP; let ax=x,ay=y,sc=1,rot=0,alpha=1,sx=1,sy=1; switch(f.anim){ case'fade':alpha=Math.min(1,p*3);break; case'zoom':sc=.72+.28*Math.min(1,p*1.5);break; case'zoomOut':sc=1.3-.3*easeOut(p);alpha=Math.min(1,p*3);break; case'slide':ax=x-(1-p)*canvas.width*.35;break; case'slideUp':ay=y+(1-easeOut(p))*80;alpha=Math.min(1,p*3);break; case'slideDown':ay=y-(1-easeOut(p))*80;alpha=Math.min(1,p*3);break; case'bounce':ay=y-Math.abs(Math.sin(p*Math.PI*2.5))*32*(1-p*.35);break; case'rotate':rot=(p-.5)*.16;sc=.94+.06*Math.sin(p*Math.PI);break; case'float':ay=y+Math.sin(p*Math.PI*2)*16;break; case'pulse':sc=1+.04*Math.sin(p*Math.PI*2);break; case'glitch':ax=x+(Math.random()-.5)*10;break; case'swing':rot=Math.sin(p*Math.PI*2)*(1-p)*.12;break; case'blur':ctx.filter=`blur(${(1-p)*15}px)`;alpha=Math.min(1,p*2);break; case'flip':sc=.25+.75*p;rot=(1-p)*Math.PI;alpha=Math.min(1,p*2);break; case'jello':rot=Math.sin(p*Math.PI*4)*.05;sc=1+.05*Math.sin(p*Math.PI*6);break; case'drop':ay=y-150*(1-easeOut(p));alpha=Math.min(1,p*2);break; case'zoomFade':sc=.55+.45*easeOut(p);alpha=Math.min(1,p*2);break; case'coverLeft':ax=x-(1-easeOut(p))*canvas.width*.55;break; case'coverRight':ax=x+(1-easeOut(p))*canvas.width*.55;break; case'pushUp':ay=y+(1-easeOut(p))*canvas.height*.45;break; case'pushDown':ay=y-(1-easeOut(p))*canvas.height*.45;break; case'whip':ax=x+(1-p)*canvas.width*.75;rot=(1-p)*.55;sc=.9+.1*easeOut(p);alpha=Math.min(1,p*2);break; case'spinZoom':rot=(1-p)*Math.PI*1.5;sc=.25+.75*easeOut(p);alpha=Math.min(1,p*2);break; case'blurIn':ctx.filter=`blur(${(1-p)*18}px)`;alpha=Math.min(1,p*2);break; case'flashIn':alpha=p<.12?0:Math.min(1,(p-.12)*2);break; case'bounceIn':sc=.45+.55*easeBack(p);alpha=Math.min(1,p*2);break; case'elasticIn':sc=.35+.65*(1-Math.exp(-7*p)*Math.cos(p*14));alpha=Math.min(1,p*2);break; case'rotateIn':rot=(1-p)*-.65;sc=.75+.25*easeOut(p);alpha=Math.min(1,p*2);break; case'squeeze':sc=.45+.55*easeOut(p);rot=Math.sin(p*Math.PI)*.04;alpha=Math.min(1,p*2);break; case'reveal':ax=x-(1-easeOut(p))*canvas.width*.28;alpha=Math.min(1,p*2);break; case'door':sx=.08+.92*easeOut(p);rot=(1-p)*-.12;alpha=Math.min(1,p*2);break; case'split':sx=.2+.8*easeOut(p);sy=.8+.2*easeOut(p);alpha=Math.min(1,p*2);break; } ctx.save();ctx.globalAlpha=alpha;ctx.translate(ax+w/2,ay+h/2);ctx.rotate(rot);ctx.scale(sc*sx,sc*sy);ctx.translate(-(x+w/2),-(y+h/2)); if(f.fx==='grayscale')ctx.filter='grayscale(1)';else if(f.fx==='sepia')ctx.filter='sepia(1)';else if(f.fx==='neon')ctx.filter='saturate(2) contrast(1.25)';else if(f.fx==='blur')ctx.filter='blur(3px)'; ctx.drawImage(img,x,y,w,h);drawFrameImages(f,p);ctx.restore();ctx.filter='none'; } // Overlays always belong to the currently entering/current frame. drawParticles(f.particle,t,f.particleSize);drawStickers(f,p,t); const canvasElapsed=Math.max(0,(t-start)/1000); // TEXT HAS ITS OWN PROJECT TIMELINE — it never restarts just because the frame changes. // This applies to both 1-Frame text and Main Canvas / All Frames text. // Looping effects (Wave, Glitch, Swing, etc.) continue smoothly; intro effects // such as Roll/Twist/Pop play from the project timeline and then hold. f.texts.forEach(txt=>{ drawText(txt,canvasElapsed); }); }; /* Sticker upload */ $('addStickerBtn').onclick=()=>{if(!S.frames.length)return alert('Pehle image/frame add karein!');$('stickerInput').click()}; $('stickerInput').onchange=e=>{if(!S.frames.length)return;const f=S.frames[S.selected];f.stickers=f.stickers||[];[...e.target.files].forEach(file=>{if(!file.type.startsWith('image/'))return;historyPush();const im=new Image();const st={img:im,x:canvas.width/2,y:canvas.height/2,scale:1,rotation:0,opacity:1,anim:'pop',speed:1,baseW:150,baseH:150};im.onload=()=>{const r=Math.min(180/im.width,180/im.height,1);st.baseW=im.width*r;st.baseH=im.height*r;S.activeSticker=st; S.activeFrameImage=null; if(window.syncLayer)window.syncLayer(); syncStickerInspector();draw(performance.now())};im.src=URL.createObjectURL(file);f.stickers.push(st)});e.target.value='';renderFrames()}; function currentSticker(){const f=S.frames[S.selected];return f?.stickers?.includes(S.activeSticker)?S.activeSticker:null} window.syncStickerInspector = function(){ const st=currentSticker(); const panel=$('stickerInspector'); if(!st){ if(panel) panel.style.display='none'; return; } if(panel) panel.style.display='block'; document.querySelectorAll('#stickerAnimGrid .animBtn').forEach(b=>b.classList.toggle('active',b.dataset.anim===(st.anim||'none'))); $('stickerSpeed').value=st.speed||1;$('stickerScale').value=st.scale||1;$('stickerRotation').value=Math.round((st.rotation||0)*180/Math.PI);$('stickerSpeedVal').textContent=(st.speed||1)+'x';$('stickerScaleVal').textContent=Math.round((st.scale||1)*100)+'%'; }; // Sticker Anim Logic document.querySelectorAll('#stickerAnimGrid .animBtn').forEach(b=>{ b.onclick = () => { const st=currentSticker(); if(!st)return; historyPush(); st.anim=b.dataset.anim; document.querySelectorAll('#stickerAnimGrid .animBtn').forEach(x=>x.classList.remove('active')); b.classList.add('active'); draw(performance.now()); }; }); // Inner Image Anim Logic document.querySelectorAll('#innerAnimGrid .animBtn').forEach(b=>{ b.onclick = () => { const l=S.activeFrameImage; if(!l)return; historyPush(); l.anim=b.dataset.anim; document.querySelectorAll('#innerAnimGrid .animBtn').forEach(x=>x.classList.remove('active')); b.classList.add('active'); draw(performance.now()); }; }); // Text Anim Logic document.querySelectorAll('#te_animGrid .animBtn').forEach(b=>{ b.onclick = () => { if(!activeText)return; historyPush(); applyTextProp('anim',b.dataset.anim); document.querySelectorAll('#te_animGrid .animBtn').forEach(x=>x.classList.remove('active')); b.classList.add('active'); draw(performance.now()); }; }); document.querySelectorAll('#te_frameAnimGrid .animBtn').forEach(b=>{ b.onclick = () => { if(!activeText)return; historyPush(); applyTextProp('frameAnim',b.dataset.anim); document.querySelectorAll('#te_frameAnimGrid .animBtn').forEach(x=>x.classList.remove('active')); b.classList.add('active'); draw(performance.now()); }; }); $('stickerSpeed').oninput=e=>{const st=currentSticker();if(!st)return;st.speed=+e.target.value;$('stickerSpeedVal').textContent=e.target.value+'x';draw(performance.now())}; $('stickerScale').oninput=e=>{const st=currentSticker();if(!st)return;st.scale=+e.target.value;$('stickerScaleVal').textContent=Math.round(+e.target.value*100)+'%';draw(performance.now())}; $('stickerRotation').oninput=e=>{const st=currentSticker();if(!st)return;st.rotation=+e.target.value*Math.PI/180;$('stickerRotationVal').textContent=e.target.value+'°';draw(performance.now())}; // Sticker Layer Ordering $('stickerStepUp').onclick=()=>{const f=S.frames[S.selected],st=currentSticker();if(!f||!st)return;const i=f.stickers.indexOf(st);if(i<0||i>=f.stickers.length-1)return;typeof historyPush==='function'&&historyPush();[f.stickers[i],f.stickers[i+1]]=[f.stickers[i+1],f.stickers[i]];draw(performance.now())}; $('stickerStepBack').onclick=()=>{const f=S.frames[S.selected],st=currentSticker();if(!f||!st)return;const i=f.stickers.indexOf(st);if(i<=0)return;typeof historyPush==='function'&&historyPush();[f.stickers[i],f.stickers[i-1]]=[f.stickers[i-1],f.stickers[i]];draw(performance.now())}; $('deleteSticker').onclick=()=>{const f=S.frames[S.selected],st=currentSticker();if(!f||!st)return;historyPush();f.stickers=f.stickers.filter(x=>x!==st);S.activeSticker=null;window.syncStickerInspector();draw(performance.now())}; $('duplicateSticker').onclick=()=>{const f=S.frames[S.selected],st=currentSticker();if(!f||!st)return;historyPush();const cp={...st};const im=new Image();cp.img=im;im.onload=()=>draw(performance.now());im.src=st.img.src;cp.x+=25;cp.y+=25;f.stickers.push(cp);S.activeSticker=cp;window.syncStickerInspector();draw(performance.now())}; const wrap=(el,ev,fn)=>{if(!el)return;el.addEventListener(ev,fn,true)}; wrap($('addImages'),'click',()=>historyPush()); wrap($('addTextBtn'),'click',()=>historyPush()); wrap($('delete'),'click',()=>historyPush()); wrap($('duplicate'),'click',()=>historyPush()); wrap($('addFrameBtn'),'click',()=>historyPush()); wrap($('newBtn'),'click',()=>historyPush()); document.querySelectorAll('[data-anim],[data-particle]').forEach(b=>b.addEventListener('click',()=>historyPush(),true)); ['fx','duration','transitionStrength','te_text','te_font','te_fill','te_stroke','te_size','te_fillOp','te_strokeW','te_strokeOp','te_spacing','te_speed','te_delay','te_frameColor','te_frameOp','te_padding'].forEach(id=>{const el=$(id);if(el)el.addEventListener('input',()=>historyPush(),true);if(el)el.addEventListener('change',()=>historyPush(),true)}); document.querySelectorAll('.effectBtn,.frameStyleBtn').forEach(b=>b.addEventListener('click',()=>historyPush(),true)); document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='z'){e.preventDefault();e.shiftKey?redo():undo();return}if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='y'){e.preventDefault();redo();return}if((e.key==='Delete'||e.key==='Backspace')&&!['INPUT','TEXTAREA','SELECT'].includes(document.activeElement.tagName)){const st=currentSticker();if(st){historyPush();S.frames[S.selected].stickers=S.frames[S.selected].stickers.filter(x=>x!==st);S.activeSticker=null;window.syncStickerInspector();draw(performance.now());return}if(activeText){historyPush();S.frames[S.selected].texts=S.frames[S.selected].texts.filter(x=>x!==activeText);closeText()}}}); // Persistent font picker: one click selects/previews, Done closes it. (function(){ const fonts=['Inter','Roboto','Poppins','Montserrat','Anton','Archivo Black','Bebas Neue','Bungee','Oswald','Rubik','Pacifico','Lobster','Playfair Display','Noto Nastaliq Urdu','Abril Fatface','Caveat','Comfortaa','Dancing Script','DM Sans','Lato','Merriweather','Nunito','Permanent Marker','Quicksand','Raleway','Space Grotesk']; const grid=$('fontGrid'),btn=$('fontPickerBtn'),panel=$('fontPickerPanel'),done=$('fontDone'),search=$('fontSearch'); function renderFontChoices(q=''){if(!grid)return;grid.innerHTML='';fonts.filter(f=>f.toLowerCase().includes(q.toLowerCase())).forEach(font=>{const b=document.createElement('button');b.type='button';b.className='fontChoice';b.dataset.font=font;b.style.fontFamily=font;b.innerHTML=`${font}<small>Preview — Aa 123</small>`;b.onclick=()=>{if(!activeText)return;historyPush();applyTextProp('font',font);$('te_font').value=font;$('fontPickerLabel').textContent=font;$('fontPickerLabel').style.fontFamily=font;$('fontLivePreview').textContent='Live preview: '+font;$('fontLivePreview').style.fontFamily=font;document.fonts.load(`${activeText.size}px "${font}"`);document.querySelectorAll('.fontChoice').forEach(x=>x.classList.toggle('active',x.dataset.font===font));draw(performance.now());};grid.appendChild(b)});} renderFontChoices(); if(btn)btn.onclick=e=>{e.preventDefault();if(!activeText)return;panel.classList.toggle('open');if(panel.classList.contains('open')){renderFontChoices(search?.value||'');search?.focus();}}; if(done)done.onclick=()=>panel.classList.remove('open'); if(search)search.oninput=e=>renderFontChoices(e.target.value); })(); undoStack.push(snap()); updateHistoryButtons(); window.syncStickerInspector(); })(); /* Standard play/export handlers */ function play(){if(!S.frames.length)return alert('Pehle image/frame add karein.');stop(false);S.playing=true;previewIndex=S.selected;previewFrameStart=performance.now();window.__editorPreviewStart=previewFrameStart;const loop=now=>{if(!S.playing)return;const f=S.frames[previewIndex];if(now-previewFrameStart>=Math.max(.1,f.duration)*1000){previewIndex=(previewIndex+1)%S.frames.length;previewFrameStart=now}draw(now);previewRAF=requestAnimationFrame(loop)};previewRAF=requestAnimationFrame(loop)}function stop(reset=true){S.playing=false;if(previewRAF)cancelAnimationFrame(previewRAF);previewRAF=null;if(reset){previewIndex=S.selected;previewFrameStart=performance.now();draw(performance.now())}} $('play').onclick=play;$('previewBtn').onclick=()=>S.playing?stop(true):play();$('stop').onclick=()=>stop(true); $('duplicate').onclick=()=>{if(!S.frames.length)return;const f=S.frames[S.selected],copy=makeFrame(f.img.src);Object.assign(copy,{duration:f.duration,anim:f.anim,transitionStrength:f.transitionStrength??1,transition:f.transition||'none',fx:f.fx,particle:f.particle,particleSize:f.particleSize,bgCol:f.bgCol,bgDeviceImg:f.bgDeviceImg,texts:JSON.parse(JSON.stringify(f.texts||[])),stickers:[]});(f.stickers||[]).forEach(st=>{const cp={...st};const im=new Image();cp.img=im;im.onload=()=>draw(performance.now());im.src=st.img.src;copy.stickers.push(cp)});S.frames.splice(S.selected+1,0,copy);S.selected++;renderFrames()}; $('addFrameBtn').onclick=()=>{if(!S.frames.length)return alert('Pehle image add karein!');const f=S.frames[S.selected],copy=makeFrame(f.img.src);Object.assign(copy,{duration:f.duration,anim:'none',transitionStrength:1,fx:f.fx,particle:'none',particleSize:f.particleSize,bgCol:f.bgCol,bgDeviceImg:f.bgDeviceImg,texts:[],stickers:[]});S.frames.splice(S.selected+1,0,copy);S.selected++;renderFrames()}; $('delete').onclick=()=>{if(!S.frames.length)return;S.frames.splice(S.selected,1);S.selected=Math.max(0,S.selected-1);S.activeSticker=null;closeText();renderFrames()}; $('newBtn').onclick=()=>{stop();S.frames=[];S.selected=0;closeText();renderFrames()}; $('demoBtn').onclick=()=>{if(S.frames.length)return;const c=document.createElement('canvas');c.width=960;c.height=540;const x=c.getContext('2d');const g=x.createLinearGradient(0,0,960,540);g.addColorStop(0,'#17102a');g.addColorStop(1,'#0c1828');x.fillStyle=g;x.fillRect(0,0,960,540);x.fillStyle='#9b7cff';x.font='800 105px Inter';x.textAlign='center';x.fillText('GIF STUDIO',480,285);x.fillStyle='#fff';x.font='32px Inter';x.fillText('Next Level Text Demo',480,345);S.frames.push(makeFrame(c.toDataURL('image/png')));S.selected=0;renderFrames()}; function fitStageToArea(){const area=document.querySelector('.canvasarea'), stage=$('dropStage');if(!area||!stage)return;const ratio=Math.max(.05,canvas.width/Math.max(1,canvas.height));const maxW=Math.max(80,area.clientWidth-36), maxH=Math.max(80,area.clientHeight-36);let w=Math.min(maxW,maxH*ratio), h=w/ratio;if(h>maxH){h=maxH;w=h*ratio;}stage.style.width=Math.max(80,Math.floor(w))+'px';stage.style.height=Math.max(80,Math.floor(h))+'px';stage.style.aspectRatio='auto';} function applyPreviewAspect(value){const map={'16:9':[960,540],'9:16':[540,960],'1:1':[720,720],'4:5':[648,810],'1.91:1':[1031,540]};let next;if(value==='fit'&&S.frames.length){const im=S.frames[S.selected].img;next=(im?.naturalWidth&&im?.naturalHeight)?[im.naturalWidth,im.naturalHeight]:map['16:9'];}else{if(value==='screen') value='16:9';next=map[value]||map['16:9'];}const ow=canvas.width||960, oh=canvas.height||540, nw=next[0], nh=next[1];if(ow!==nw||oh!==nh){const sx=nw/ow, sy=nh/oh, ss=Math.sqrt(sx*sy);S.frames.forEach(f=>{(f.texts||[]).forEach(t=>{t.x*=sx;t.y*=sy;t.size*=ss;t.padding=(t.padding||0)*ss});(f.stickers||[]).forEach(st=>{st.x*=sx;st.y*=sy;st.baseW*=sx;st.baseH*=sy});});canvas.width=nw; canvas.height=nh;}fitStageToArea();draw(performance.now());} function setExportSize(){const a=$('aspectRatio').value;if(a==='fit'&&S.frames.length){const im=S.frames[S.selected].img;if(im?.naturalWidth&&im?.naturalHeight){$('width').value=im.naturalWidth;$('height').value=im.naturalHeight;}}else{const p={'16:9':[640,360],'9:16':[360,640],'1:1':[600,600],'4:5':[600,750],'1.91:1':[768,402]};if(p[a]){$('width').value=p[a][0];$('height').value=p[a][1];}}} $('aspectRatio').onchange=()=>{setExportSize();applyPreviewAspect($('aspectRatio').value)}; window.addEventListener('resize',fitStageToArea); function renderForExport(w,h,index,p){const ow=canvas.width,oh=canvas.height,os=S.selected,op=S.playing,opi=previewIndex,ops=previewFrameStart, f=S.frames[index],sx=w/ow,sy=h/oh,ss=Math.sqrt(sx*sy);const changed=[];(f.texts||[]).forEach(t=>{changed.push(['t',t,t.x,t.y,t.size,t.padding]);t.x*=sx;t.y*=sy;t.size*=ss;t.padding=(t.padding||0)*ss});(f.stickers||[]).forEach(st=>{changed.push(['s',st,st.x,st.y,st.baseW,st.baseH]);st.x*=sx;st.y*=sy;st.baseW*=sx;st.baseH*=sy});canvas.width=w;canvas.height=h;S.selected=index;S.playing=true;previewIndex=index;previewFrameStart=performance.now()-p*Math.max(.1,f.duration)*1000;draw(performance.now());const out=document.createElement('canvas');out.width=w;out.height=h;out.getContext('2d').drawImage(canvas,0,0);changed.forEach(v=>{if(v[0]==='t'){v[1].x=v[2];v[1].y=v[3];v[1].size=v[4];v[1].padding=v[5]}else{v[1].x=v[2];v[1].y=v[3];v[1].baseW=v[4];v[1].baseH=v[5]}});canvas.width=ow;canvas.height=oh;S.selected=os;S.playing=op;previewIndex=opi;previewFrameStart=ops;draw(performance.now());return out} async function exportAnimatedGIF(){ if(!S.frames.length)return alert('Pehle image/frame add karein!'); setExportSize(); const format=($('exportFormat')?.value||'gif').toLowerCase(); const w=+$('width').value,h=+$('height').value,fps=+$('fps').value||12; const btn=$('exportBtn'), old=btn.textContent; btn.disabled=true; try{ if(format!=='gif'){ btn.textContent=`Rendering ${format.toUpperCase()}…`; const c=renderForExport(w,h,S.selected,0); const mime=format==='jpg'?'image/jpeg':(format==='webp'?'image/webp':'image/png'); const ext=format==='jpg'?'jpg':format; const quality=format==='jpg'?0.92:0.95; c.toBlob(blob=>{ if(!blob){btn.disabled=false;btn.textContent=old;return alert('Image export mein error aya.');} const a=document.createElement('a');a.download=`gif-studio-${w}x${h}.${ext}`;a.href=URL.createObjectURL(blob); document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(a.href),2000); btn.disabled=false;btn.textContent=old;draw(performance.now()); },mime,quality); return; } if(typeof GIF==='undefined')return alert('GIF encoder load nahi hua. Internet on karke page dobara open karein.'); btn.textContent='Preparing GIF…'; let workerScript=''; try{ const wr=await fetch('https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.worker.js'); if(wr.ok){const wt=await wr.text();workerScript=URL.createObjectURL(new Blob([wt],{type:'application/javascript'}));} }catch(e){console.warn('Worker fetch failed; using main thread encoder.',e);} const gifOptions={workers:workerScript?2:0,quality:10,repeat:(+$('loop').value===1?-1:0),width:w,height:h}; if(workerScript)gifOptions.workerScript=workerScript; const gif=new GIF(gifOptions),wasPlaying=S.playing; S.playing=false; S.__exportSingleRun=(+$('loop').value===1); let total=0;S.frames.forEach(f=>total+=Math.max(1,Math.round(f.duration*fps))); let done=0; for(let i=0;i<S.frames.length;i++){ const f=S.frames[i],count=Math.max(1,Math.round(f.duration*fps)); for(let n=0;n<count;n++){ const c=renderForExport(w,h,i,n/count); gif.addFrame(c,{copy:true,delay:1000/fps});done++; if(done%3===0)btn.textContent=`Rendering GIF ${Math.round(done/total*100)}%`; } } gif.on('finished',blob=>{ const a=document.createElement('a');a.download=`gif-studio-${w}x${h}.gif`;a.href=URL.createObjectURL(blob); document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(a.href),2000); btn.disabled=false;btn.textContent=old;S.playing=wasPlaying;S.__exportSingleRun=false;fitStageToArea();draw(performance.now());if(workerScript)URL.revokeObjectURL(workerScript); }); gif.render(); }catch(err){ console.error(err);S.__exportSingleRun=false;btn.disabled=false;btn.textContent=old;draw(performance.now()); alert('Export mein error: '+(err?.message||err)); } } $('exportFileBtn').onclick=exportAnimatedGIF;$('exportBtn').onclick=exportAnimatedGIF; if($('exportFormat'))$('exportFormat').onchange=()=>{ const v=$('exportFormat').value; $('exportBtn').textContent=v==='gif'?'Export GIF':`Export ${v.toUpperCase()}`; }; renderFrames(); setTimeout(fitStageToArea,0); /* ================= ADD-ONLY IMAGE TOOLS ================= */ (function(){ const status=$('imageToolStatus'); window.imgSelectMode=false; window.setImgStatus=(msg,ok=false)=>{if(!status)return;status.textContent=msg;status.classList.toggle('ok',ok);status.classList.toggle('active',window.imgSelectMode)}; const hasFrame=()=>!!(S.frames&&S.frames.length&&S.frames[S.selected]); const currentFrame=()=>hasFrame()?S.frames[S.selected]:null; const getTargetContext = () => { if (S.activeSticker) return { type: 'sticker', obj: S.activeSticker }; if (S.activeFrameImage) return { type: 'frameImage', obj: S.activeFrameImage }; return { type: 'main', obj: currentFrame() }; }; const currentImage = () => { const target = getTargetContext(); return target.obj?.img || null; }; const imageToCanvas=()=>{ const im=currentImage(); if(!im||!im.naturalWidth||!im.naturalHeight)return null; const c=document.createElement('canvas'); c.width=im.naturalWidth; c.height=im.naturalHeight; c.getContext('2d').drawImage(im,0,0); return c; }; const replaceFrameImage=(dataUrl)=>{ const target = getTargetContext(); if(!target.obj) return; const im=new Image(); im.onload=()=>{ if (target.type === 'sticker' || target.type === 'frameImage') { if (!target.obj.__originalImgSrc) target.obj.__originalImgSrc = target.obj.img.src; target.obj.img = im; } else { const f = target.obj; if(!f.__originalImgSrc)f.__originalImgSrc=f.img?.src||''; f.img=im; fitStageToArea&&fitStageToArea(); } draw(performance.now()); renderFrames(); }; im.src=dataUrl; }; if($('selectImageTool'))$('selectImageTool').onclick=()=>{ window.imgSelectMode=!window.imgSelectMode; $('selectImageTool').classList.toggle('active',window.imgSelectMode); window.setImgStatus(window.imgSelectMode?'Selection mode ON — click the image in the preview.':'Selection mode OFF'); if(window.imgSelectMode&&hasFrame())draw(performance.now()); }; if($('copyImageTool'))$('copyImageTool').onclick=async()=>{ const c=imageToCanvas(); if(!c)return window.setImgStatus('Pehle image/sticker select karein.'); try{ const blob=await new Promise(r=>c.toBlob(r,'image/png')); if(navigator.clipboard?.write&&window.ClipboardItem){await navigator.clipboard.write([new ClipboardItem({'image/png':blob})]);window.setImgStatus('Image copied to clipboard.',true);} else{window.__gifStudioImageClipboard=c.toDataURL('image/png');window.setImgStatus('Image copied inside GIF Studio. Browser clipboard permission available nahi.',true);} }catch(e){window.__gifStudioImageClipboard=c.toDataURL('image/png');window.setImgStatus('Image copied inside GIF Studio. Clipboard permission denied.');} }; const pasteBlob=blob=>{ if(!blob||!blob.type?.startsWith('image/'))return false; const reader=new FileReader(); reader.onload=()=>{ if(!hasFrame()){const f=makeFrame(reader.result);S.frames.push(f);S.selected=S.frames.length-1;renderFrames();} else replaceFrameImage(reader.result); window.setImgStatus('Image pasted successfully.',true); }; reader.readAsDataURL(blob); return true; }; if($('pasteImageTool'))$('pasteImageTool').onclick=async()=>{ try{ if(navigator.clipboard?.read){ const items=await navigator.clipboard.read(); for(const item of items){const type=item.types.find(t=>t.startsWith('image/'));if(type){pasteBlob(await item.getType(type));return;}} } }catch(e){} if(window.__gifStudioImageClipboard){replaceFrameImage(window.__gifStudioImageClipboard);window.setImgStatus('Image pasted from GIF Studio clipboard.',true);return;} window.setImgStatus('Clipboard mein image nahi mili. Copy Image pehle karein.'); }; let interactiveSegmenter=null, interactiveSegmenterLoading=null; window.aiObjectMode=false; async function loadInteractiveObjectModel(){ if(interactiveSegmenter) return interactiveSegmenter; if(interactiveSegmenterLoading) return interactiveSegmenterLoading; interactiveSegmenterLoading=(async()=>{ const mod=await import('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0'); const vision=await mod.FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm'); interactiveSegmenter=await mod.InteractiveSegmenter.createFromOptions(vision,{ baseOptions:{ modelAssetPath:'https://storage.googleapis.com/mediapipe-models/interactive_segmenter/magic_touch/float32/1/magic_touch.tflite', delegate:'GPU' }, outputCategoryMask:false, outputConfidenceMasks:true }); return interactiveSegmenter; })(); try{return await interactiveSegmenterLoading} catch(e){interactiveSegmenterLoading=null;throw e} } window.setObjectSelectMode = function(on){ window.aiObjectMode=!!on; const btn=$('aiObjectTool'); if(btn){ btn.classList.toggle('active',window.aiObjectMode); btn.textContent=window.aiObjectMode?'🧠 Click Object…':'🧠 Select Object'; } canvas.style.cursor=window.aiObjectMode?'crosshair':''; if(window.aiObjectMode) window.setImgStatus('Image/Sticker par jis object ko select karna hai us par CLICK karein.'); } window.aiSelectSubject = async function(mx,my){ if(!hasFrame()) return; const target = getTargetContext(); const src = target.obj?.img; if(!src?.naturalWidth) return window.setImgStatus('Current image available nahi.'); let nx, ny; if (target.type === 'sticker') { const st = target.obj; const dx = mx - st.x, dy = my - st.y; const cos = Math.cos(-st.rotation || 0), sin = Math.sin(-st.rotation || 0); const rx = dx * cos - dy * sin, ry = dx * sin + dy * cos; const w = st.baseW * st.scale, h = st.baseH * st.scale; nx = Math.max(0, Math.min(1, (rx + w/2) / w)); ny = Math.max(0, Math.min(1, (ry + h/2) / h)); } else if (target.type === 'frameImage') { const fi = target.obj; const dx = mx - (fi.x || canvas.width/2), dy = my - (fi.y || canvas.height/2); const cos = Math.cos(-fi.rotation || 0), sin = Math.sin(-fi.rotation || 0); const rx = dx * cos - dy * sin, ry = dx * sin + dy * cos; const w = fi.w, h = fi.h; nx = Math.max(0, Math.min(1, (rx + w/2) / w)); ny = Math.max(0, Math.min(1, (ry + h/2) / h)); } else { const [ix, iy, iw, ih] = fit(src); nx = Math.max(0, Math.min(1, (mx - ix) / iw)); ny = Math.max(0, Math.min(1, (my - iy) / ih)); } const btn=$('aiObjectTool'), old=btn?.textContent; if(btn){btn.disabled=true;btn.textContent='🧠 Loading AI…'} window.setImgStatus('AI subject selector load ho raha hai…'); try{ const segmenter=await loadInteractiveObjectModel(); if(btn)btn.textContent='🧠 Selecting…'; const result=segmenter.segment(src,{keypoint:{x:nx,y:ny}}); const mask=result?.confidenceMasks?.[0]; if(!mask) throw new Error('Segmentation mask nahi mili.'); const mw=mask.width||src.naturalWidth, mh=mask.height||src.naturalHeight; const md=mask.getAsFloat32Array(); const out=document.createElement('canvas'); out.width=src.naturalWidth; out.height=src.naturalHeight; const ox=out.getContext('2d',{willReadFrequently:true}); ox.drawImage(src,0,0,out.width,out.height); const pix=ox.getImageData(0,0,out.width,out.height), d=pix.data; const sx=mw/out.width, sy=mh/out.height; for(let y=0;y<out.height;y++){ const myy=Math.min(mh-1,Math.max(0,Math.floor(y*sy))); for(let x=0;x<out.width;x++){ const mxx=Math.min(mw-1,Math.max(0,Math.floor(x*sx))); const conf=md[myy*mw+mxx]||0; const i=(y*out.width+x)*4; let a=0; if(conf>=0.55)a=255; else if(conf>0.15)a=Math.round((conf-0.15)/0.40*255); d[i+3]=Math.min(d[i+3],a); } } ox.putImageData(pix,0,0); const dataUrl=out.toDataURL('image/png'); const im=new Image(); im.onload=()=>{ const f = currentFrame(); f.stickers=f.stickers||[]; const r=Math.min(1,320/Math.max(im.naturalWidth,im.naturalHeight)); const st={img:im,x:canvas.width/2,y:canvas.height/2,scale:1,rotation:0,opacity:1,anim:'none',speed:1,baseW:im.naturalWidth*r,baseH:im.naturalHeight*r}; f.stickers.push(st); S.activeSticker=st; S.activeFrameImage=null; if(window.syncLayer)window.syncLayer(); if(window.syncStickerInspector)window.syncStickerInspector(); if(window.setObjectSelectMode) window.setObjectSelectMode(false); draw(performance.now()); renderFrames(); window.setImgStatus('Subject select complete — isolated subject movable sticker ban gaya. Original image safe hai.',true); }; im.onerror=()=>{throw new Error('Selected subject image create nahi ho saki.')}; im.src=dataUrl; }catch(err){ console.error('Interactive Subject Select:',err); if(window.setObjectSelectMode) window.setObjectSelectMode(false); window.setImgStatus('Subject select nahi ho saka: '+(err?.message||err)); alert('Select Subject model load/run nahi ho saka. Internet on karke dobara try karein.\n\n'+(err?.message||err)); }finally{ if(btn){btn.disabled=false;if(!window.aiObjectMode)btn.textContent=old||'🧠 Select Object'} } }; if($('aiObjectTool')) $('aiObjectTool').onclick=()=>{ if(!hasFrame()) return window.setImgStatus('Pehle image/frame add karein.'); if(window.setObjectSelectMode) window.setObjectSelectMode(!window.aiObjectMode); }; if($('removeBgTool'))$('removeBgTool').onclick=()=>{ const target = getTargetContext(); if (!target.obj) return window.setImgStatus('Pehle image/sticker select karein.'); const c=imageToCanvas(); if(!c)return; if(!target.obj.__originalImgSrc) target.obj.__originalImgSrc = target.obj.img.src; const ctx2=c.getContext('2d',{willReadFrequently:true}), w=c.width,h=c.height, img=ctx2.getImageData(0,0,w,h), d=img.data; const tol=+$('bgTol').value, soft=+$('bgSoft').value; const pts=[[0,0],[w-1,0],[0,h-1],[w-1,h-1],[Math.floor(w*.02),Math.floor(h*.02)],[Math.floor(w*.98),Math.floor(h*.02)],[Math.floor(w*.02),Math.floor(h*.98)],[Math.floor(w*.98),Math.floor(h*.98)]]; let rr=0,gg=0,bb=0;pts.forEach(([x,y])=>{const i=(y*w+x)*4;rr+=d[i];gg+=d[i+1];bb+=d[i+2]});rr/=pts.length;gg/=pts.length;bb/=pts.length; const softness=Math.max(1,soft); for(let y=0;y<h;y++)for(let x=0;x<w;x++){const i=(y*w+x)*4;const dist=Math.hypot(d[i]-rr,d[i+1]-gg,d[i+2]-bb);if(dist<=tol){d[i+3]=0;}else if(soft&&dist<tol+softness){d[i+3]=Math.round(255*(dist-tol)/softness);}} ctx2.putImageData(img,0,0); replaceFrameImage(c.toDataURL('image/png')); window.setImgStatus('Background removed from the selected item.',true); }; if($('resetBgTool'))$('resetBgTool').onclick=()=>{ const target = getTargetContext(); if(!target.obj?.__originalImgSrc)return window.setImgStatus('Is image ka original version available nahi.'); const im=new Image(); im.onload=()=>{ target.obj.img=im; draw(performance.now()); renderFrames(); window.setImgStatus('Original image restored.',true) }; im.src=target.obj.__originalImgSrc; }; $('bgTol').oninput=e=>$('bgTolVal').textContent=e.target.value; $('bgSoft').oninput=e=>$('bgSoftVal').textContent=e.target.value; })(); /* ================= MULTI IMAGE INSIDE FRAME PATCH v2 ================= */ (function(){ S.activeFrameImage=null; const hasFrame=()=>!!(S.frames&&S.frames.length&&S.frames[S.selected]); const cur=()=>hasFrame()?S.frames[S.selected]:null; const status=msg=>{const el=$('frameLayerStatus');if(el)el.textContent=msg}; window.getInnerImgAt = function(mx,my){ const f=cur(); if(!f)return null; const arr=f.frameImages||[]; for(let i=arr.length-1;i>=0;i--){ const l=arr[i]; if(!l?.img?.naturalWidth)continue; const w=l.w||240,h=l.h||180,dx=mx-(l.x??canvas.width/2),dy=my-(l.y??canvas.height/2); if(Math.abs(dx)<=w/2+10&&Math.abs(dy)<=h/2+10)return l; } return null; }; window.syncLayer = function(){ const f=cur(),l=S.activeFrameImage,n=f?.frameImages?.length||0; const idx=l&&f?.frameImages?f.frameImages.indexOf(l):-1; const panel=$('frameImageInspector'); if(l){ if(panel) panel.style.display='block'; status(`Selected image ${idx+1}/${n} — Active in Image Tools.`); document.querySelectorAll('#innerAnimGrid .animBtn').forEach(b=>b.classList.toggle('active',b.dataset.anim===(l.anim||'none'))); } else { if(panel) panel.style.display='none'; status(``); } } const addBtn=$('addFrameImageBtn'), input=$('frameImageInput'); const canvasTools=$('frameCanvasTools'); const canvasAdd=$('canvasAddFrameImage'); const canvasUp=$('canvasStepUp'); const canvasBack=$('canvasStepBack'); function syncCanvasTools(){ if(canvasTools) canvasTools.classList.toggle('show',!!hasFrame()&&!S.playing); } if(canvasAdd&&addBtn) canvasAdd.onclick=(e)=>{e.preventDefault();e.stopPropagation();addBtn.click();}; if(canvasUp) canvasUp.onclick=(e)=>{e.preventDefault();e.stopPropagation();$('frameStepUp')?.click();}; if(canvasBack) canvasBack.onclick=(e)=>{e.preventDefault();e.stopPropagation();$('frameStepBack')?.click();}; if(addBtn&&input){ addBtn.onclick=()=>{ if(!hasFrame()){alert('Pehle frame add karein!');return;} input.value=''; input.click(); }; input.onchange=e=>{ const f=cur(); const files=[...(e.target.files||[])].filter(file=>file.type&&file.type.startsWith('image/')); e.target.value=''; if(!f||!files.length)return; f.frameImages=f.frameImages||[]; typeof historyPush==='function'&&historyPush(); let pending=files.length; files.forEach(file=>{ const reader=new FileReader(); reader.onload=ev=>{ const im=new Image(); const layer={img:im,x:canvas.width/2,y:canvas.height/2,w:240,h:180,rotation:0,opacity:1}; im.onload=()=>{ const maxW=Math.max(80,canvas.width*.45), maxH=Math.max(80,canvas.height*.45); const scale=Math.min(maxW/im.naturalWidth,maxH/im.naturalHeight,1); layer.w=im.naturalWidth*scale; layer.h=im.naturalHeight*scale; f.frameImages.push(layer); S.activeFrameImage=layer; S.activeSticker=null; if(window.syncStickerInspector) window.syncStickerInspector(); pending--; if(pending===0){ renderFrames(); draw(performance.now()); window.syncLayer(); syncCanvasTools(); } }; im.onerror=()=>{pending--; if(pending===0)window.syncLayer(); alert('Image load nahi ho saki: '+file.name)}; im.src=ev.target.result; }; reader.onerror=()=>{pending--; if(pending===0)window.syncLayer(); alert('Image read nahi ho saki: '+file.name)}; reader.readAsDataURL(file); }); }; } $('frameStepUp').onclick=()=>{const f=cur(),l=S.activeFrameImage;if(!f||!l)return;const i=f.frameImages.indexOf(l);if(i<0||i>=f.frameImages.length-1)return;typeof historyPush==='function'&&historyPush();[f.frameImages[i],f.frameImages[i+1]]=[f.frameImages[i+1],f.frameImages[i]];draw(performance.now());window.syncLayer()}; $('frameStepBack').onclick=()=>{const f=cur(),l=S.activeFrameImage;if(!f||!l)return;const i=f.frameImages.indexOf(l);if(i<=0)return;typeof historyPush==='function'&&historyPush();[f.frameImages[i],f.frameImages[i-1]]=[f.frameImages[i-1],f.frameImages[i]];draw(performance.now());window.syncLayer()}; $('frameDeleteImage').onclick=()=>{const f=cur(),l=S.activeFrameImage;if(!f||!l)return;const i=f.frameImages.indexOf(l);if(i<0)return;typeof historyPush==='function'&&historyPush();f.frameImages.splice(i,1);S.activeFrameImage=null;draw(performance.now());window.syncLayer()}; $('frameCenterImage').onclick=()=>{const l=S.activeFrameImage;if(!l)return;typeof historyPush==='function'&&historyPush();l.x=canvas.width/2;l.y=canvas.height/2;draw(performance.now());window.syncLayer()}; const oldRender=renderFrames; renderFrames=function(){oldRender();window.syncLayer();syncCanvasTools()}; window.syncLayer(); syncCanvasTools(); })(); // ================= UNIFIED MASTER MOUSE EVENTS (100% BUG FREE) ================= (function(){ canvas.onmousedown = function(e) { if(!S.frames.length || S.playing) return; const r = canvas.getBoundingClientRect(); const mx = (e.clientX - r.left) * canvas.width / r.width; const my = (e.clientY - r.top) * canvas.height / r.height; const f = S.frames[S.selected]; let clickedText = null; for(let i = f.texts.length - 1; i >= 0; i--){ const t = f.texts[i], m = textMetrics(t), pad = (t.padding || 18) + 35; if(Math.abs(mx - t.x) < m.w/2 + pad && Math.abs(my - t.y) < m.h/2 + pad){ clickedText = t; break; } } let clickedSticker = null; if (!clickedText) { for(let i = (f.stickers||[]).length - 1; i >= 0; i--){ const st = f.stickers[i], w = st.baseW * st.scale, h = st.baseH * st.scale; if(Math.abs(mx - st.x) < w/2 + 15 && Math.abs(my - st.y) < h/2 + 15){ clickedSticker = st; break; } } } let clickedInner = null; if (!clickedText && !clickedSticker && window.getInnerImgAt) { clickedInner = window.getInnerImgAt(mx, my); } if (window.aiObjectMode) { e.preventDefault(); e.stopPropagation(); if (clickedSticker) { S.activeSticker = clickedSticker; S.activeFrameImage = null; } else if (clickedInner) { S.activeFrameImage = clickedInner; S.activeSticker = null; } else { S.activeSticker = null; S.activeFrameImage = null; } if(window.syncStickerInspector) window.syncStickerInspector(); if(window.syncLayer) window.syncLayer(); draw(performance.now()); if(window.aiSelectSubject) window.aiSelectSubject(mx, my); return; } if (clickedText) { activeText = clickedText; draggingText = clickedText; dragOffsetX = mx - clickedText.x; dragOffsetY = my - clickedText.y; if (canvas.setPointerCapture && e.pointerId != null) { try { canvas.setPointerCapture(e.pointerId); } catch(_){} } openTextEditor(clickedText); S.activeSticker = null; S.activeFrameImage = null; if(window.syncStickerInspector) window.syncStickerInspector(); if(window.syncLayer) window.syncLayer(); return; } if (clickedSticker) { S.activeSticker = clickedSticker; S.activeFrameImage = null; draggingText = null; if(window.closeText) window.closeText(); clickedSticker._dragX = mx - clickedSticker.x; clickedSticker._dragY = my - clickedSticker.y; if(window.syncStickerInspector) window.syncStickerInspector(); if(window.syncLayer) window.syncLayer(); draw(performance.now()); return; } if (clickedInner) { S.activeFrameImage = clickedInner; S.activeSticker = null; draggingText = null; if(window.closeText) window.closeText(); if(window.syncStickerInspector) window.syncStickerInspector(); window.dragFrameImg = clickedInner; window.fDragX = mx - clickedInner.x; window.fDragY = my - clickedInner.y; if(window.syncLayer) window.syncLayer(); draw(performance.now()); return; } S.activeSticker = null; S.activeFrameImage = null; draggingText = null; if(window.closeText) window.closeText(); if(window.syncStickerInspector) window.syncStickerInspector(); if(window.syncLayer) window.syncLayer(); draw(performance.now()); if (window.imgSelectMode) { S.activeImage = true; if(window.setImgStatus) window.setImgStatus('Selected! Now Copy, Paste, or Remove Background will apply to this.',true); } }; canvas.onmousemove = function(e) { if(S.playing) return; const r = canvas.getBoundingClientRect(); const mx = (e.clientX - r.left) * canvas.width / r.width; const my = (e.clientY - r.top) * canvas.height / r.height; if (draggingText) { const nx=mx-dragOffsetX, ny=my-dragOffsetY, dx=nx-draggingText.x, dy=ny-draggingText.y; if(textApplyScope==='all' && draggingText.groupId){ S.frames.forEach(f=>(f.texts||[]).forEach(t=>{if(t.groupId===draggingText.groupId){t.x+=dx;t.y+=dy;}})); } else { draggingText.x=nx; draggingText.y=ny; } draw(performance.now()); return; } if (S.activeSticker && S.activeSticker._dragX != null) { S.activeSticker.x = mx - S.activeSticker._dragX; S.activeSticker.y = my - S.activeSticker._dragY; draw(performance.now()); return; } if (window.dragFrameImg) { window.dragFrameImg.x = mx - window.fDragX; window.dragFrameImg.y = my - window.fDragY; draw(performance.now()); return; } }; // Keep text dragging reliable even when the pointer leaves the canvas while dragging. canvas.addEventListener('pointermove', function(e){ if(!draggingText || S.playing) return; const r=canvas.getBoundingClientRect(); const mx=(e.clientX-r.left)*canvas.width/r.width; const my=(e.clientY-r.top)*canvas.height/r.height; const nx=mx-dragOffsetX, ny=my-dragOffsetY, dx=nx-draggingText.x, dy=ny-draggingText.y; if(textApplyScope==='all' && draggingText.groupId){ S.frames.forEach(f=>(f.texts||[]).forEach(t=>{if(t.groupId===draggingText.groupId){t.x+=dx;t.y+=dy;}})); } else { draggingText.x=nx; draggingText.y=ny; } draw(performance.now()); }); canvas.addEventListener('pointerup', function(e){ if(canvas.releasePointerCapture && e.pointerId != null){ try { canvas.releasePointerCapture(e.pointerId); } catch(_){} } draggingText=null; }); canvas.onmouseup = function() { draggingText = null; if (S.activeSticker) { delete S.activeSticker._dragX; delete S.activeSticker._dragY; } window.dragFrameImg = null; }; canvas.onmouseleave = function() { draggingText = null; if (S.activeSticker) { delete S.activeSticker._dragX; delete S.activeSticker._dragY; } window.dragFrameImg = null; }; })(); </script> <script src="https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.js"></script> </body> </html>
Gemini

Pixelmotion Studio

Add to Cart
PromptBase Select
Get this prompt + 9 more with SelectFrom $14/mo · Cancel anytimeFrom $14/mo · Cancel anytime
View Select
Instant accessInstant access
Usage rightsCommercial use
Money-back guaranteeMoney‑back
By purchasing this prompt, you agree to our terms of service
GEMINI-3.1-PRO
Tested icon
Guide icon
4 examples icon
Free credits icon
Generate highly complex, production-ready, single-file HTML5 web application UIs with zero external dependencies. Instead of struggling with layouts, use this elite-level prompt to build ANY custom app interface (CRM dashboards, Video Editors, Music Players, SaaS admin panels). Features custom CSS grids, modern themes, and advanced UI elements based entirely on your specific variables.
...more
Added 3 weeks ago
Report
Browse Marketplace