prithivMLmods commited on
Commit
3bcf5de
Β·
verified Β·
1 Parent(s): 3c36366

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +281 -196
index.html CHANGED
@@ -153,11 +153,15 @@ body{
153
  padding: 24px;
154
  }
155
 
156
- .canvas-wrapper {
157
  position: relative;
158
- display: inline-block;
159
  max-width: 100%;
160
  max-height: 100%;
 
 
 
 
 
161
  }
162
 
163
  #draw-canvas {
@@ -171,7 +175,6 @@ body{
171
  box-shadow: 0 12px 48px rgba(0,0,0,.9);
172
  touch-action: none;
173
  display: block;
174
- z-index: 1;
175
  }
176
  #draw-canvas.cursor-brush { cursor: crosshair; }
177
  #draw-canvas.cursor-eraser {
@@ -191,41 +194,65 @@ body{
191
  display: none;
192
  }
193
 
194
- /* Draggable Assets */
195
- .draggable-asset {
196
  position: absolute;
197
- cursor: move;
 
 
 
 
 
 
198
  user-select: none;
199
  touch-action: none;
200
- z-index: 10;
201
  border: 1px dashed transparent;
 
202
  }
203
- .draggable-asset:hover {
204
- border-color: rgba(255, 255, 255, 0.5);
205
  }
206
- .draggable-asset img {
207
- pointer-events: none;
208
- display: block;
209
  width: 100%;
210
- height: auto;
 
 
 
211
  }
212
- .resize-handle {
213
  position: absolute;
214
- bottom: -6px;
215
- right: -6px;
216
- width: 12px;
217
- height: 12px;
218
  background: #fff;
219
- border: 2px solid #000;
220
- border-radius: 50%;
221
- cursor: se-resize;
222
- z-index: 20;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
224
 
225
  /* Loader */
226
  .modern-loader{
227
  display:none;position:absolute;inset:0;background:rgba(0,0,0,.92);
228
- z-index:15;flex-direction:column;align-items:center;justify-content:center;gap:16px;backdrop-filter:blur(5px);
229
  }
230
  .modern-loader.active{display:flex}
231
  .loader-spinner{
@@ -443,7 +470,7 @@ body{
443
 
444
  <!-- ══ Left icon rail ══ -->
445
  <div class="rail">
446
- <div class="rail-label">Draw</div>
447
  <button class="rail-btn active" id="btn-brush" title="Brush">
448
  <svg viewBox="0 0 24 24"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>
449
  </button>
@@ -453,19 +480,13 @@ body{
453
  <div class="color-input-wrap">
454
  <input type="color" id="color-picker" class="color-input" value="#000000" title="Brush Color">
455
  </div>
 
456
  <div class="size-slider-wrap">
457
  <input type="range" id="size-slider" class="size-slider" min="1" max="50" value="5" title="Brush Size">
458
  </div>
459
 
460
  <div class="rail-sep"></div>
461
- <div class="rail-label">Assets</div>
462
- <button class="rail-btn" id="btn-upload-asset" title="Add Image/Asset">
463
- <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
464
- </button>
465
- <input type="file" id="asset-upload-input" accept="image/*" style="display:none;">
466
-
467
- <div class="rail-sep"></div>
468
- <div class="rail-label">Edit</div>
469
  <button class="rail-btn" id="btn-undo" title="Undo">
470
  <svg viewBox="0 0 24 24"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-15-6.7L3 13"/></svg>
471
  </button>
@@ -476,6 +497,13 @@ body{
476
  <svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
477
  </button>
478
 
 
 
 
 
 
 
 
479
  <div class="rail-sep"></div>
480
  <div class="rail-label">Result</div>
481
  <button class="rail-btn" id="btn-download" title="Download Result" disabled>
@@ -490,7 +518,7 @@ body{
490
  <button class="seg-btn active" id="seg-input">Sketch</button>
491
  <button class="seg-btn" id="seg-result">Result</button>
492
  </div>
493
- <div class="canvas-meta" id="canvas-meta">Draw a sketch or add assets</div>
494
  <div class="canvas-actions"></div>
495
  </div>
496
 
@@ -501,12 +529,11 @@ body{
501
  <div class="loader-bar-track"><div class="loader-bar-fill" id="loader-bar-fill"></div></div>
502
  </div>
503
 
504
- <div class="canvas-wrapper" id="canvas-wrapper">
505
- <canvas id="draw-canvas" width="1024" height="576" class="cursor-brush" style="aspect-ratio: 16 / 9;"></canvas>
506
- <!-- Assets are appended dynamically here -->
 
507
  </div>
508
-
509
- <img id="result-img" alt="Result Image" style="display:none; aspect-ratio: 16 / 9;">
510
  </div>
511
  </div>
512
 
@@ -593,7 +620,7 @@ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.m
593
  /* ── DOM refs ── */
594
  const canvas = document.getElementById('draw-canvas');
595
  const ctx = canvas.getContext('2d');
596
- const canvasWrapper = document.getElementById('canvas-wrapper');
597
  const resultImg = document.getElementById('result-img');
598
  const btnDownload = document.getElementById('btn-download');
599
 
@@ -604,8 +631,9 @@ const btnRedo = document.getElementById('btn-redo');
604
  const btnClear = document.getElementById('btn-clear');
605
  const colorPicker = document.getElementById('color-picker');
606
  const sizeSlider = document.getElementById('size-slider');
607
- const btnUploadAsset = document.getElementById('btn-upload-asset');
608
- const assetInput = document.getElementById('asset-upload-input');
 
609
 
610
  const promptInput = document.getElementById('custom-prompt-input');
611
  const charCount = document.getElementById('char-count');
@@ -680,15 +708,16 @@ function updateCanvasRatio(ratio) {
680
  canvas.width = w;
681
  canvas.height = h;
682
  canvas.style.aspectRatio = ratio.replace(":", " / ");
 
683
  resultImg.style.aspectRatio = ratio.replace(":", " / ");
684
 
685
- setupCanvas();
686
- undoStack = [];
687
  redoStack = [];
688
- saveState();
689
 
690
- // Clear assets to prevent scaling issues
691
- document.querySelectorAll('.draggable-asset').forEach(el => el.remove());
692
  }
693
 
694
  ratioSelect.addEventListener('change', (e) => {
@@ -715,6 +744,8 @@ function getPos(e) {
715
  }
716
 
717
  function startDrawing(e) {
 
 
718
  e.preventDefault();
719
  isDrawing = true;
720
  const pos = getPos(e);
@@ -754,117 +785,12 @@ function stopDrawing() {
754
  }
755
 
756
  canvas.addEventListener('mousedown', startDrawing);
757
- canvas.addEventListener('mousemove', draw);
758
- canvas.addEventListener('mouseup', stopDrawing);
759
- canvas.addEventListener('mouseout', stopDrawing);
760
 
761
  canvas.addEventListener('touchstart', startDrawing);
762
- canvas.addEventListener('touchmove', draw);
763
- canvas.addEventListener('touchend', stopDrawing);
764
-
765
- /* ── Asset Logic ── */
766
- btnUploadAsset.addEventListener('click', () => assetInput.click());
767
- assetInput.addEventListener('change', (e) => {
768
- const file = e.target.files[0];
769
- if (file) {
770
- const reader = new FileReader();
771
- reader.onload = (ev) => addAsset(ev.target.result);
772
- reader.readAsDataURL(file);
773
- }
774
- e.target.value = '';
775
- });
776
-
777
- function addAsset(src) {
778
- const wrapper = document.createElement('div');
779
- wrapper.className = 'draggable-asset';
780
-
781
- const img = document.createElement('img');
782
- img.src = src;
783
- img.draggable = false;
784
-
785
- const handle = document.createElement('div');
786
- handle.className = 'resize-handle';
787
-
788
- wrapper.appendChild(img);
789
- wrapper.appendChild(handle);
790
-
791
- img.style.width = '150px';
792
- img.style.height = 'auto';
793
- img.style.display = 'block';
794
-
795
- // Center it initially
796
- const wrapperRect = canvasWrapper.getBoundingClientRect();
797
- const startLeft = wrapperRect.width / 2 - 75;
798
- const startTop = wrapperRect.height / 2 - 75;
799
- wrapper.style.left = startLeft + 'px';
800
- wrapper.style.top = startTop + 'px';
801
-
802
- canvasWrapper.appendChild(wrapper);
803
- initDrag(wrapper);
804
- showToast('Asset added. Double-click to delete.', 'info');
805
- }
806
-
807
- function initDrag(el) {
808
- let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
809
- let isResizing = false;
810
- let startX, startY, startW;
811
- const handle = el.querySelector('.resize-handle');
812
-
813
- el.onmousedown = dragMouseDown;
814
- el.ontouchstart = (e) => { if(e.touches.length===1) dragMouseDown(e.touches[0]); };
815
-
816
- function dragMouseDown(e) {
817
- e.preventDefault();
818
- if (e.target === handle) {
819
- isResizing = true;
820
- startX = e.clientX;
821
- startW = el.offsetWidth;
822
- document.onmousemove = resizeMove;
823
- document.ontouchmove = (ev) => { if(ev.touches.length===1) resizeMove(ev.touches[0]); };
824
- document.onmouseup = closeDragElement;
825
- document.ontouchend = closeDragElement;
826
- } else {
827
- isResizing = false;
828
- pos3 = e.clientX;
829
- pos4 = e.clientY;
830
- document.onmousemove = elementDrag;
831
- document.ontouchmove = (ev) => { if(ev.touches.length===1) elementDrag(ev.touches[0]); };
832
- document.onmouseup = closeDragElement;
833
- document.ontouchend = closeDragElement;
834
- }
835
- }
836
-
837
- function elementDrag(e) {
838
- e.preventDefault();
839
- pos1 = pos3 - e.clientX;
840
- pos2 = pos4 - e.clientY;
841
- pos3 = e.clientX;
842
- pos4 = e.clientY;
843
- el.style.top = (el.offsetTop - pos2) + "px";
844
- el.style.left = (el.offsetLeft - pos1) + "px";
845
- }
846
-
847
- function resizeMove(e) {
848
- e.preventDefault();
849
- let newW = startW + (e.clientX - startX);
850
- if (newW > 30) el.style.width = newW + 'px';
851
- el.style.height = 'auto';
852
- }
853
-
854
- function closeDragElement() {
855
- document.onmouseup = null;
856
- document.onmousemove = null;
857
- document.ontouchend = null;
858
- document.ontouchmove = null;
859
- isResizing = false;
860
- }
861
-
862
- // Double click to delete
863
- el.ondblclick = () => {
864
- el.remove();
865
- showToast('Asset removed', 'info');
866
- };
867
- }
868
 
869
  /* ── Tool Listeners ── */
870
  btnBrush.addEventListener('click', () => {
@@ -899,8 +825,8 @@ sizeSlider.addEventListener('input', (e) => {
899
  btnClear.addEventListener('click', () => {
900
  setupCanvas();
901
  saveState();
902
- document.querySelectorAll('.draggable-asset').forEach(el => el.remove());
903
- showToast('Canvas & Assets cleared', 'info');
904
  });
905
 
906
  btnUndo.addEventListener('click', async () => {
@@ -919,6 +845,196 @@ btnRedo.addEventListener('click', async () => {
919
  }
920
  });
921
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
  btnDownload.addEventListener('click', () => {
923
  if (resultImg.src && resultImg.src !== window.location.href) {
924
  const a = document.createElement('a');
@@ -931,17 +1047,19 @@ btnDownload.addEventListener('click', () => {
931
  /* ── UI Toggles ── */
932
  function setViewMode(mode) {
933
  if (mode === 'result') {
934
- canvasWrapper.style.display = 'none';
 
935
  resultImg.style.display = 'block';
936
  segResult.classList.add('active');
937
  segInput.classList.remove('active');
938
  canvasMeta.textContent = 'Generated Result';
939
  } else {
940
- canvasWrapper.style.display = 'inline-block';
 
941
  resultImg.style.display = 'none';
942
  segResult.classList.remove('active');
943
  segInput.classList.add('active');
944
- canvasMeta.textContent = 'Draw a sketch or add assets';
945
  }
946
  }
947
  segInput.addEventListener('click', () => setViewMode('input'));
@@ -1063,39 +1181,6 @@ function setRunningUI(on) {
1063
  cancelBtn.style.display = on ? 'flex' : 'none';
1064
  }
1065
 
1066
- async function getFlattenedDataURL() {
1067
- // Create temp canvas to merge base sketch + assets
1068
- const tempCanvas = document.createElement('canvas');
1069
- tempCanvas.width = canvas.width;
1070
- tempCanvas.height = canvas.height;
1071
- const tempCtx = tempCanvas.getContext('2d');
1072
-
1073
- tempCtx.fillStyle = '#ffffff';
1074
- tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
1075
- tempCtx.drawImage(canvas, 0, 0);
1076
-
1077
- const assets = document.querySelectorAll('.draggable-asset');
1078
- const wrapperRect = canvasWrapper.getBoundingClientRect();
1079
- const scaleX = canvas.width / wrapperRect.width;
1080
- const scaleY = canvas.height / wrapperRect.height;
1081
-
1082
- for (let asset of assets) {
1083
- const img = asset.querySelector('img');
1084
- const rect = asset.getBoundingClientRect();
1085
-
1086
- const x = (rect.left - wrapperRect.left) * scaleX;
1087
- const y = (rect.top - wrapperRect.top) * scaleY;
1088
- const w = rect.width * scaleX;
1089
- const h = rect.height * scaleY;
1090
-
1091
- if (!img.complete) {
1092
- await new Promise(resolve => { img.onload = resolve; });
1093
- }
1094
- tempCtx.drawImage(img, x, y, w, h);
1095
- }
1096
- return tempCanvas.toDataURL('image/png');
1097
- }
1098
-
1099
  async function runEdit() {
1100
  if (running) return;
1101
 
@@ -1107,8 +1192,8 @@ async function runEdit() {
1107
  showLoader('Submitting to queue…');
1108
  btnDownload.disabled = true;
1109
 
1110
- // Flatten sketch and assets
1111
- const sketch_b64 = await getFlattenedDataURL();
1112
  const selectedRatio = ratioSelect.value;
1113
  const [targetW, targetH] = dims[selectedRatio];
1114
 
@@ -1160,7 +1245,7 @@ async function runEdit() {
1160
  }
1161
  hideLoader('Done', 'done');
1162
  showToast('Image generated successfully', 'info');
1163
- btnDownload.disabled = false;
1164
  } else {
1165
  hideLoader('Done', 'done');
1166
  }
 
153
  padding: 24px;
154
  }
155
 
156
+ #board-wrapper {
157
  position: relative;
 
158
  max-width: 100%;
159
  max-height: 100%;
160
+ width: 100%;
161
+ height: 100%;
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
  }
166
 
167
  #draw-canvas {
 
175
  box-shadow: 0 12px 48px rgba(0,0,0,.9);
176
  touch-action: none;
177
  display: block;
 
178
  }
179
  #draw-canvas.cursor-brush { cursor: crosshair; }
180
  #draw-canvas.cursor-eraser {
 
194
  display: none;
195
  }
196
 
197
+ #asset-layer {
 
198
  position: absolute;
199
+ top: 0; left: 0; width: 100%; height: 100%;
200
+ pointer-events: none; /* Allows clicks to pass through to canvas */
201
+ }
202
+
203
+ .asset-wrapper {
204
+ position: absolute;
205
+ pointer-events: auto;
206
  user-select: none;
207
  touch-action: none;
 
208
  border: 1px dashed transparent;
209
+ transition: border-color 0.15s ease;
210
  }
211
+ .asset-wrapper:hover, .asset-wrapper.active {
212
+ border-color: var(--accent2);
213
  }
214
+ .asset-img {
 
 
215
  width: 100%;
216
+ height: 100%;
217
+ object-fit: contain;
218
+ pointer-events: none;
219
+ background: transparent;
220
  }
221
+ .asset-handle {
222
  position: absolute;
 
 
 
 
223
  background: #fff;
224
+ border: 1px solid #000;
225
+ border-radius: 2px;
226
+ width: 10px;
227
+ height: 10px;
228
+ z-index: 10;
229
+ }
230
+ .asset-handle.resize {
231
+ bottom: -5px;
232
+ right: -5px;
233
+ cursor: nwse-resize;
234
+ }
235
+ .asset-handle.delete {
236
+ top: -5px;
237
+ right: -5px;
238
+ cursor: pointer;
239
+ background: #ef4444;
240
+ border-color: #b91c1c;
241
+ }
242
+ .asset-handle.delete::before {
243
+ content: 'Γ—';
244
+ color: #fff;
245
+ font-size: 12px;
246
+ font-weight: bold;
247
+ position: absolute;
248
+ top: -2px;
249
+ left: 1px;
250
  }
251
 
252
  /* Loader */
253
  .modern-loader{
254
  display:none;position:absolute;inset:0;background:rgba(0,0,0,.92);
255
+ z-index:100;flex-direction:column;align-items:center;justify-content:center;gap:16px;backdrop-filter:blur(5px);
256
  }
257
  .modern-loader.active{display:flex}
258
  .loader-spinner{
 
470
 
471
  <!-- ══ Left icon rail ══ -->
472
  <div class="rail">
473
+ <div class="rail-label">Tools</div>
474
  <button class="rail-btn active" id="btn-brush" title="Brush">
475
  <svg viewBox="0 0 24 24"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>
476
  </button>
 
480
  <div class="color-input-wrap">
481
  <input type="color" id="color-picker" class="color-input" value="#000000" title="Brush Color">
482
  </div>
483
+
484
  <div class="size-slider-wrap">
485
  <input type="range" id="size-slider" class="size-slider" min="1" max="50" value="5" title="Brush Size">
486
  </div>
487
 
488
  <div class="rail-sep"></div>
489
+
 
 
 
 
 
 
 
490
  <button class="rail-btn" id="btn-undo" title="Undo">
491
  <svg viewBox="0 0 24 24"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-15-6.7L3 13"/></svg>
492
  </button>
 
497
  <svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
498
  </button>
499
 
500
+ <div class="rail-sep"></div>
501
+ <div class="rail-label">Assets</div>
502
+ <button class="rail-btn" id="btn-asset" title="Add Asset / Sticker">
503
+ <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
504
+ </button>
505
+ <input type="file" id="asset-file-input" accept="image/*" style="display:none;">
506
+
507
  <div class="rail-sep"></div>
508
  <div class="rail-label">Result</div>
509
  <button class="rail-btn" id="btn-download" title="Download Result" disabled>
 
518
  <button class="seg-btn active" id="seg-input">Sketch</button>
519
  <button class="seg-btn" id="seg-result">Result</button>
520
  </div>
521
+ <div class="canvas-meta" id="canvas-meta">Draw a sketch or add assets to begin</div>
522
  <div class="canvas-actions"></div>
523
  </div>
524
 
 
529
  <div class="loader-bar-track"><div class="loader-bar-fill" id="loader-bar-fill"></div></div>
530
  </div>
531
 
532
+ <div id="board-wrapper">
533
+ <canvas id="draw-canvas" width="1024" height="576" class="cursor-brush" style="aspect-ratio: 16 / 9;"></canvas>
534
+ <div id="asset-layer"></div>
535
+ <img id="result-img" alt="Result Image" style="display:none; aspect-ratio: 16 / 9;">
536
  </div>
 
 
537
  </div>
538
  </div>
539
 
 
620
  /* ── DOM refs ── */
621
  const canvas = document.getElementById('draw-canvas');
622
  const ctx = canvas.getContext('2d');
623
+ const assetLayer = document.getElementById('asset-layer');
624
  const resultImg = document.getElementById('result-img');
625
  const btnDownload = document.getElementById('btn-download');
626
 
 
631
  const btnClear = document.getElementById('btn-clear');
632
  const colorPicker = document.getElementById('color-picker');
633
  const sizeSlider = document.getElementById('size-slider');
634
+
635
+ const btnAsset = document.getElementById('btn-asset');
636
+ const assetFileInput = document.getElementById('asset-file-input');
637
 
638
  const promptInput = document.getElementById('custom-prompt-input');
639
  const charCount = document.getElementById('char-count');
 
708
  canvas.width = w;
709
  canvas.height = h;
710
  canvas.style.aspectRatio = ratio.replace(":", " / ");
711
+
712
  resultImg.style.aspectRatio = ratio.replace(":", " / ");
713
 
714
+ setupCanvas(); // Clear and reset canvas on ratio change
715
+ undoStack = []; // Clear history on ratio change
716
  redoStack = [];
717
+ saveState(); // Save initial blank state
718
 
719
+ // Clear assets on ratio change to prevent weird overlapping
720
+ assetLayer.innerHTML = "";
721
  }
722
 
723
  ratioSelect.addEventListener('change', (e) => {
 
744
  }
745
 
746
  function startDrawing(e) {
747
+ // Only draw if clicking directly on canvas, not an asset
748
+ if (e.target !== canvas) return;
749
  e.preventDefault();
750
  isDrawing = true;
751
  const pos = getPos(e);
 
785
  }
786
 
787
  canvas.addEventListener('mousedown', startDrawing);
788
+ window.addEventListener('mousemove', draw); // Listen on window to allow dragging outside
789
+ window.addEventListener('mouseup', stopDrawing);
 
790
 
791
  canvas.addEventListener('touchstart', startDrawing);
792
+ window.addEventListener('touchmove', draw, { passive: false });
793
+ window.addEventListener('touchend', stopDrawing);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
794
 
795
  /* ── Tool Listeners ── */
796
  btnBrush.addEventListener('click', () => {
 
825
  btnClear.addEventListener('click', () => {
826
  setupCanvas();
827
  saveState();
828
+ assetLayer.innerHTML = ""; // Clear assets too
829
+ showToast('Canvas cleared', 'info');
830
  });
831
 
832
  btnUndo.addEventListener('click', async () => {
 
845
  }
846
  });
847
 
848
+ /* ── Asset Management Logic ── */
849
+ let activeAsset = null;
850
+ let activeResize = null;
851
+ let maxZ = 1;
852
+
853
+ btnAsset.addEventListener('click', () => {
854
+ assetFileInput.click();
855
+ });
856
+
857
+ assetFileInput.addEventListener('change', (e) => {
858
+ const file = e.target.files[0];
859
+ if (!file) return;
860
+
861
+ const reader = new FileReader();
862
+ reader.onload = (event) => {
863
+ addAsset(event.target.result);
864
+ };
865
+ reader.readAsDataURL(file);
866
+ e.target.value = ''; // Reset input
867
+ });
868
+
869
+ function addAsset(src) {
870
+ const wrapper = document.createElement('div');
871
+ wrapper.className = 'asset-wrapper';
872
+
873
+ const img = document.createElement('img');
874
+ img.className = 'asset-img';
875
+ img.src = src;
876
+ img.draggable = false;
877
+
878
+ const delBtn = document.createElement('div');
879
+ delBtn.className = 'asset-handle delete';
880
+ delBtn.addEventListener('mousedown', (e) => {
881
+ e.stopPropagation();
882
+ wrapper.remove();
883
+ });
884
+ delBtn.addEventListener('touchstart', (e) => {
885
+ e.stopPropagation();
886
+ wrapper.remove();
887
+ });
888
+
889
+ const resizeHandle = document.createElement('div');
890
+ resizeHandle.className = 'asset-handle resize';
891
+
892
+ wrapper.appendChild(img);
893
+ wrapper.appendChild(delBtn);
894
+ wrapper.appendChild(resizeHandle);
895
+ assetLayer.appendChild(wrapper);
896
+
897
+ // Set initial size & pos (center)
898
+ img.onload = () => {
899
+ const aspect = img.naturalWidth / img.naturalHeight;
900
+ let initW = 150;
901
+ let initH = initW / aspect;
902
+ wrapper.style.width = initW + 'px';
903
+ wrapper.style.height = initH + 'px';
904
+
905
+ const rect = canvas.getBoundingClientRect();
906
+ wrapper.style.left = (rect.width / 2 - initW / 2) + 'px';
907
+ wrapper.style.top = (rect.height / 2 - initH / 2) + 'px';
908
+ wrapper.style.zIndex = ++maxZ;
909
+ };
910
+
911
+ // Drag listeners
912
+ wrapper.addEventListener('mousedown', (e) => startDragAsset(e, wrapper));
913
+ wrapper.addEventListener('touchstart', (e) => startDragAsset(e, wrapper), { passive: false });
914
+
915
+ // Resize listeners
916
+ resizeHandle.addEventListener('mousedown', (e) => startResizeAsset(e, wrapper));
917
+ resizeHandle.addEventListener('touchstart', (e) => startResizeAsset(e, wrapper), { passive: false });
918
+ }
919
+
920
+ function startDragAsset(e, asset) {
921
+ e.preventDefault();
922
+ e.stopPropagation();
923
+ activeAsset = asset;
924
+ activeAsset.classList.add('active');
925
+ activeAsset.style.zIndex = ++maxZ;
926
+
927
+ const touch = e.touches ? e.touches[0] : e;
928
+ const rect = asset.getBoundingClientRect();
929
+ activeAsset._dragOffsetX = touch.clientX - rect.left;
930
+ activeAsset._dragOffsetY = touch.clientY - rect.top;
931
+ }
932
+
933
+ function startResizeAsset(e, asset) {
934
+ e.preventDefault();
935
+ e.stopPropagation();
936
+ activeResize = asset;
937
+ activeResize.classList.add('active');
938
+ activeAsset.style.zIndex = ++maxZ;
939
+
940
+ const touch = e.touches ? e.touches[0] : e;
941
+ activeResize._startW = asset.offsetWidth;
942
+ activeResize._startH = asset.offsetHeight;
943
+ activeResize._startRX = touch.clientX;
944
+ activeResize._startRY = touch.clientY;
945
+ }
946
+
947
+ window.addEventListener('mousemove', (e) => {
948
+ if (activeAsset) {
949
+ const touch = e.touches ? e.touches[0] : e;
950
+ const wrapperRect = document.getElementById('board-wrapper').getBoundingClientRect();
951
+ let x = touch.clientX - wrapperRect.left - activeAsset._dragOffsetX;
952
+ let y = touch.clientY - wrapperRect.top - activeAsset._dragOffsetY;
953
+
954
+ // Clamp to wrapper bounds
955
+ x = Math.max(0, Math.min(x, wrapperRect.width - activeAsset.offsetWidth));
956
+ y = Math.max(0, Math.min(y, wrapperRect.height - activeAsset.offsetHeight));
957
+
958
+ activeAsset.style.left = x + 'px';
959
+ activeAsset.style.top = y + 'px';
960
+ }
961
+ if (activeResize) {
962
+ const touch = e.touches ? e.touches[0] : e;
963
+ const dx = touch.clientX - activeResize._startRX;
964
+ const aspect = activeResize._startH / activeResize._startW;
965
+ let newW = Math.max(20, activeResize._startW + dx);
966
+ let newH = newW * aspect;
967
+ activeResize.style.width = newW + 'px';
968
+ activeResize.style.height = newH + 'px';
969
+ }
970
+ });
971
+
972
+ window.addEventListener('touchmove', (e) => {
973
+ if (activeAsset || activeResize) {
974
+ e.preventDefault();
975
+ if (activeAsset) {
976
+ const touch = e.touches[0];
977
+ const wrapperRect = document.getElementById('board-wrapper').getBoundingClientRect();
978
+ let x = touch.clientX - wrapperRect.left - activeAsset._dragOffsetX;
979
+ let y = touch.clientY - wrapperRect.top - activeAsset._dragOffsetY;
980
+ x = Math.max(0, Math.min(x, wrapperRect.width - activeAsset.offsetWidth));
981
+ y = Math.max(0, Math.min(y, wrapperRect.height - activeAsset.offsetHeight));
982
+ activeAsset.style.left = x + 'px';
983
+ activeAsset.style.top = y + 'px';
984
+ }
985
+ if (activeResize) {
986
+ const touch = e.touches[0];
987
+ const dx = touch.clientX - activeResize._startRX;
988
+ const aspect = activeResize._startH / activeResize._startW;
989
+ let newW = Math.max(20, activeResize._startW + dx);
990
+ let newH = newW * aspect;
991
+ activeResize.style.width = newW + 'px';
992
+ activeResize.style.height = newH + 'px';
993
+ }
994
+ }
995
+ }, { passive: false });
996
+
997
+ function stopAssetAction() {
998
+ if (activeAsset) activeAsset.classList.remove('active');
999
+ if (activeResize) activeResize.classList.remove('active');
1000
+ activeAsset = null;
1001
+ activeResize = null;
1002
+ }
1003
+ window.addEventListener('mouseup', stopAssetAction);
1004
+ window.addEventListener('touchend', stopAssetAction);
1005
+
1006
+ /* ── Compositing Logic ── */
1007
+ function getCompositeImage() {
1008
+ const wrapper = document.getElementById('board-wrapper');
1009
+ const rect = wrapper.getBoundingClientRect();
1010
+ const scaleX = canvas.width / rect.width;
1011
+ const scaleY = canvas.height / rect.height;
1012
+
1013
+ const tempCanvas = document.createElement('canvas');
1014
+ tempCanvas.width = canvas.width;
1015
+ tempCanvas.height = canvas.height;
1016
+ const tempCtx = tempCanvas.getContext('2d');
1017
+
1018
+ // 1. Draw the base sketch
1019
+ tempCtx.drawImage(canvas, 0, 0);
1020
+
1021
+ // 2. Draw all assets on top
1022
+ const assets = assetLayer.querySelectorAll('.asset-wrapper');
1023
+ assets.forEach(asset => {
1024
+ const img = asset.querySelector('img');
1025
+ if (img.complete) {
1026
+ const x = asset.offsetLeft * scaleX;
1027
+ const y = asset.offsetTop * scaleY;
1028
+ const w = asset.offsetWidth * scaleX;
1029
+ const h = asset.offsetHeight * scaleY;
1030
+ tempCtx.drawImage(img, x, y, w, h);
1031
+ }
1032
+ });
1033
+
1034
+ return tempCanvas.toDataURL('image/png');
1035
+ }
1036
+
1037
+ /* ── Download Logic ── */
1038
  btnDownload.addEventListener('click', () => {
1039
  if (resultImg.src && resultImg.src !== window.location.href) {
1040
  const a = document.createElement('a');
 
1047
  /* ── UI Toggles ── */
1048
  function setViewMode(mode) {
1049
  if (mode === 'result') {
1050
+ canvas.style.display = 'none';
1051
+ assetLayer.style.display = 'none';
1052
  resultImg.style.display = 'block';
1053
  segResult.classList.add('active');
1054
  segInput.classList.remove('active');
1055
  canvasMeta.textContent = 'Generated Result';
1056
  } else {
1057
+ canvas.style.display = 'block';
1058
+ assetLayer.style.display = 'block';
1059
  resultImg.style.display = 'none';
1060
  segResult.classList.remove('active');
1061
  segInput.classList.add('active');
1062
+ canvasMeta.textContent = 'Draw a sketch or add assets to begin';
1063
  }
1064
  }
1065
  segInput.addEventListener('click', () => setViewMode('input'));
 
1181
  cancelBtn.style.display = on ? 'flex' : 'none';
1182
  }
1183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1184
  async function runEdit() {
1185
  if (running) return;
1186
 
 
1192
  showLoader('Submitting to queue…');
1193
  btnDownload.disabled = true;
1194
 
1195
+ // Composite sketch and assets into a single image
1196
+ const sketch_b64 = getCompositeImage();
1197
  const selectedRatio = ratioSelect.value;
1198
  const [targetW, targetH] = dims[selectedRatio];
1199
 
 
1245
  }
1246
  hideLoader('Done', 'done');
1247
  showToast('Image generated successfully', 'info');
1248
+ btnDownload.disabled = false; // Enable download
1249
  } else {
1250
  hideLoader('Done', 'done');
1251
  }