by3axap commited on
Commit
b0019de
·
verified ·
1 Parent(s): 353eaca

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +134 -119
templates/index.html CHANGED
@@ -1,5 +1,3 @@
1
- <!DOCTYPE html>
2
- <html lang="ru">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -10,8 +8,6 @@
10
  :root {
11
  --bg-page: #ffffff;
12
  --text-main: #000000;
13
- --text-muted: #aaaaaa;
14
- --border: #e0e0e0;
15
  --font-sans: 'Inter', sans-serif;
16
  --font-mono: 'JetBrains Mono', monospace;
17
  }
@@ -54,6 +50,12 @@
54
 
55
  /* --- Controls --- */
56
  .control-group { margin-bottom: 16px; width: 100%; }
 
 
 
 
 
 
57
 
58
  .label-row {
59
  display: flex;
@@ -76,7 +78,7 @@
76
  color: var(--text-main);
77
  padding: 0;
78
  }
79
- .num-input:focus { color: blue; } /* Индикация фокуса цветом, а не линией */
80
 
81
  /* Custom Range Slider Styling */
82
  input[type="range"] {
@@ -87,7 +89,6 @@
87
  cursor: pointer;
88
  }
89
 
90
- /* The Track (Thin black line) */
91
  input[type="range"]::-webkit-slider-runnable-track {
92
  width: 100%;
93
  height: 1px;
@@ -95,14 +96,13 @@
95
  border: none;
96
  }
97
 
98
- /* The Thumb (Thick black line/rect) */
99
  input[type="range"]::-webkit-slider-thumb {
100
  -webkit-appearance: none;
101
  height: 14px;
102
  width: 4px;
103
  background: #000000;
104
  border-radius: 0;
105
- margin-top: -6.5px; /* (height/2 - track_height/2) */
106
  border: none;
107
  }
108
 
@@ -133,7 +133,6 @@
133
  background: #f5f5f5;
134
  }
135
 
136
- /* Active State (Black Filled) */
137
  .btn-upload.filled {
138
  background: #000000;
139
  color: #ffffff;
@@ -157,6 +156,10 @@
157
 
158
  /* --- Action Buttons --- */
159
  .buttons-container { display: flex; gap: 12px; margin-top: auto; padding-top: 20px;}
 
 
 
 
160
 
161
  .btn-action {
162
  flex: 1;
@@ -187,7 +190,7 @@
187
  /* --- Preview Area --- */
188
  .preview-area {
189
  flex: 1;
190
- background-color: #ffffff; /* White background */
191
  display: flex;
192
  align-items: center;
193
  justify-content: center;
@@ -196,7 +199,7 @@
196
  }
197
 
198
  canvas {
199
- box-shadow: 0 0 0 1px #f0f0f0; /* Subtle border for visibility on white */
200
  max-width: 100%;
201
  max-height: 100%;
202
  object-fit: contain;
@@ -216,9 +219,29 @@
216
  -webkit-appearance: none;
217
  }
218
 
219
- /* Checkbox custom styling (simple) */
220
  input[type="checkbox"] {
221
  accent-color: black;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
 
224
  /* Hidden sources */
@@ -233,46 +256,6 @@
233
  </div>
234
 
235
  <div class="section">
236
- <span class="section-title">01 / Shape Pattern</span>
237
- <input type="file" id="shape_svg" accept=".svg">
238
- <label for="shape_svg" class="btn-upload" id="lbl-shape">Select SVG</label>
239
- <button id="rm-shape" class="remove-link" style="display:none;">remove</button>
240
- </div>
241
-
242
- <div class="section">
243
- <span class="section-title">02 / Content Source</span>
244
- <input type="file" id="content_file" accept=".png, .jpg, .jpeg, .mp4, .mov, .webm">
245
- <label for="content_file" class="btn-upload" id="lbl-content">Select Content</label>
246
- <button id="rm-content" class="remove-link" style="display:none;">remove</button>
247
- </div>
248
-
249
- <form id="params-form">
250
- <div class="section">
251
- <span class="section-title">03 / Parameters</span>
252
-
253
- <div class="control-group">
254
- <div class="label-row">
255
- <span>Density</span>
256
- <input type="number" class="num-input" id="num-density" value="40" min="5" max="150">
257
- </div>
258
- <input type="range" id="density" min="5" max="150" value="40">
259
- </div>
260
-
261
- <div class="control-group">
262
- <div class="label-row">
263
- <span>Gap X</span>
264
- <input type="number" class="num-input" id="num-gap_x" value="0" step="0.01" min="0" max="0.9">
265
- </div>
266
- <input type="range" id="gap_x" min="0" max="0.9" step="0.01" value="0">
267
- </div>
268
-
269
- <div class="control-group">
270
- <div class="label-row">
271
- <span>Gap Y</span>
272
- <input type="number" class="num-input" id="num-gap_y" value="0" step="0.01" min="0" max="0.9">
273
- </div>
274
- <input type="range" id="gap_y" min="0" max="0.9" step="0.01" value="0">
275
- </div>
276
 
277
  <div class="control-group">
278
  <div class="label-row">
@@ -280,15 +263,6 @@
280
  <input type="number" class="num-input" id="num-shift_stagger" value="0" step="0.01" min="0" max="1">
281
  </div>
282
  <input type="range" id="shift_stagger" min="0" max="1" step="0.01" value="0">
283
- </div>
284
-
285
- <div class="control-group" style="margin-top:24px;">
286
- <div class="label-row">
287
- <span>Scale</span>
288
- <input type="number" class="num-input" id="num-base_scale" value="1.0" step="0.05" min="0.1" max="2.0">
289
- </div>
290
- <input type="range" id="base_scale" min="0.1" max="2.0" step="0.05" value="1.0">
291
- </div>
292
 
293
  <div class="control-group">
294
  <div class="label-row">
@@ -296,18 +270,26 @@
296
  <input type="number" class="num-input" id="num-growth" value="0" step="0.05" min="0" max="2.0">
297
  </div>
298
  <input type="range" id="growth" min="0" max="2.0" step="0.05" value="0">
 
 
 
 
 
 
 
 
 
299
  </div>
300
 
301
- <div class="control-group">
302
- <div class="label-row">
303
- <label style="display:flex; align-items:center; gap:8px; cursor:pointer;">
304
- <input type="checkbox" id="invert">
305
  <span>Invert Logic</span>
306
  </label>
307
  </div>
308
- </div>
309
 
310
- <div class="control-group">
 
 
 
311
  <span style="font-size:10px; color:#aaa; font-family:var(--font-mono); display:block; margin-bottom:4px;">GROWTH TYPE</span>
312
  <select id="growth_type">
313
  <option value="sine">Diagonal Sine</option>
@@ -316,23 +298,18 @@
316
  </select>
317
  </div>
318
  </div>
319
- </form>
320
-
321
- <div class="buttons-container">
322
  <button class="btn-action secondary" id="btn-dl-svg">Download SVG</button>
323
  <button class="btn-action" id="btn-dl-video" disabled>Export MP4</button>
324
  </div>
325
  <div id="status-msg" style="font-size:10px; font-family:var(--font-mono); margin-top:10px; text-align:center;"></div>
326
  </div>
327
 
328
- <div class="preview-area">
329
  <canvas id="main-canvas" width="1400" height="900"></canvas>
330
 
331
  <img id="source-image" src="" crossorigin="anonymous">
332
  <video id="source-video" src="" playsinline loop muted crossorigin="anonymous"></video>
333
  <canvas id="helper-canvas" style="display:none;"></canvas>
334
- <img id="shape-img" src="" style="display:none;">
335
- </div>
336
 
337
  <script>
338
  // --- STATE ---
@@ -340,34 +317,55 @@
340
  let currentFile = null; // Храним сам объект файла
341
  let animationId = null;
342
  let customShapeLoaded = false;
 
 
 
 
343
 
344
  // --- DOM ---
345
  const canvas = document.getElementById('main-canvas');
346
- const ctx = canvas.getContext('2d', { alpha: false });
347
- const helperCanvas = document.getElementById('helper-canvas');
348
- const helperCtx = helperCanvas.getContext('2d');
349
-
350
- const sourceImage = document.getElementById('source-image');
351
- const sourceVideo = document.getElementById('source-video');
352
- const shapeImg = document.getElementById('shape-img');
353
-
354
- // --- UI HELPERS ---
355
- const setBtnState = (labelId, rmId, file, isActive) => {
356
- const lbl = document.getElementById(labelId);
357
- const rm = document.getElementById(rmId);
358
- if(isActive && file) {
359
- lbl.classList.add('filled');
360
- lbl.innerText = file.name;
361
- rm.style.display = 'block';
362
- } else {
363
- lbl.classList.remove('filled');
364
- lbl.innerText = labelId === 'lbl-shape' ? 'Select SVG' : 'Select Content';
365
- rm.style.display = 'none';
366
  }
367
  };
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  // --- EVENT LISTENERS ---
370
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  // 1. Shape Upload
372
  const shapeInput = document.getElementById('shape_svg');
373
  shapeInput.addEventListener('change', (e) => {
@@ -379,9 +377,6 @@
379
  const reader = new FileReader();
380
  reader.onload = (evt) => {
381
  const blob = new Blob([evt.target.result], {type: 'image/svg+xml'});
382
- shapeImg.src = URL.createObjectURL(blob);
383
- shapeImg.onload = () => { customShapeLoaded = true; requestRender(); };
384
- };
385
  reader.readAsText(file);
386
  });
387
 
@@ -389,6 +384,7 @@
389
  shapeInput.value = '';
390
  customShapeLoaded = false;
391
  shapeImg.src = '';
 
392
  setBtnState('lbl-shape', 'rm-shape', null, false);
393
  requestRender();
394
  });
@@ -424,7 +420,7 @@
424
  sourceVideo.play();
425
 
426
  document.getElementById('btn-dl-video').disabled = false;
427
- document.getElementById('btn-dl-svg').disabled = true; // SVG export only for static
428
  requestRender();
429
  }
430
  });
@@ -457,22 +453,28 @@
457
  document.getElementById('invert').addEventListener('change', requestRender);
458
  document.getElementById('growth_type').addEventListener('change', requestRender);
459
 
460
- // --- RENDER LOGIC ---
461
- function requestRender() {
462
- if (currentMode === 'video' && !sourceVideo.paused) {
463
- if (!animationId) renderLoop();
464
- } else {
465
- drawFrame();
 
 
 
 
 
 
 
 
466
  }
467
- }
 
 
468
 
469
- function renderLoop() {
470
- drawFrame();
471
  if (currentMode === 'video' && !sourceVideo.paused) {
472
- animationId = requestAnimationFrame(renderLoop);
473
- } else {
474
- animationId = null;
475
- }
476
  }
477
 
478
  function drawFrame() {
@@ -483,14 +485,16 @@
483
  const density = parseInt(document.getElementById('density').value);
484
  const gapX = parseFloat(document.getElementById('gap_x').value);
485
  const gapY = parseFloat(document.getElementById('gap_y').value);
486
- const stagger = parseFloat(document.getElementById('shift_stagger').value);
487
  const baseScale = parseFloat(document.getElementById('base_scale').value);
488
  const growth = parseFloat(document.getElementById('growth').value);
489
  const growthType = document.getElementById('growth_type').value;
 
490
  const invert = document.getElementById('invert').checked;
 
 
491
 
492
- // Clear
493
- ctx.fillStyle = "white";
494
  ctx.fillRect(0, 0, w, h);
495
 
496
  // Read Pixel Data
@@ -512,7 +516,7 @@
512
  }
513
 
514
  // Draw Grid
515
- ctx.fillStyle = "black";
516
 
517
  const step = w / density;
518
  const rows = Math.ceil(h / step);
@@ -550,6 +554,10 @@
550
  if (!isVisible) continue;
551
 
552
  // 2. Growth
 
 
 
 
553
  let wave = 0;
554
  if (growthType === 'sine') wave = (Math.sin(c * 0.3 + r * 0.3) + 1) / 2;
555
  else if (growthType === 'linear') wave = ((c % density) / density);
@@ -558,6 +566,9 @@
558
  let modW = 1.0 - (wave * growth);
559
  modW = Math.max(0.01, Math.min(1.0, modW));
560
 
 
 
 
561
  const contentW = cellWMax * baseScale * modW;
562
  const contentH = cellHMax * baseScale;
563
 
@@ -568,9 +579,11 @@
568
  ctx.save();
569
  ctx.translate(cx, cy);
570
 
571
- if (customShapeLoaded) {
572
- ctx.drawImage(shapeImg, -contentW/2, -contentH/2, contentW, contentH);
 
573
  } else {
 
574
  ctx.fillRect(-contentW/2, -contentH/2, contentW, contentH);
575
  }
576
  ctx.restore();
@@ -589,12 +602,13 @@
589
  paramIds.forEach(id => form.append(id, document.getElementById(id).value));
590
  form.append('growth_type', document.getElementById('growth_type').value);
591
  form.append('invert', document.getElementById('invert').checked);
 
 
592
 
593
  // Attach SVG Shape
594
  if(shapeInput.files[0]) form.append('shape_svg', shapeInput.files[0]);
595
 
596
  // Attach Image (if existing)
597
- // Note: Logic expects 'raster_image' key for images
598
  if(currentFile && currentMode === 'image') {
599
  form.append('raster_image', currentFile);
600
  }
@@ -628,9 +642,10 @@
628
  paramIds.forEach(id => form.append(id, document.getElementById(id).value));
629
  form.append('growth_type', document.getElementById('growth_type').value);
630
  form.append('invert', document.getElementById('invert').checked);
 
 
631
 
632
  // Attach Video
633
- // Note: Logic expects 'video_file' key
634
  if(currentFile && currentMode === 'video') {
635
  form.append('video_file', currentFile);
636
  }
@@ -661,4 +676,4 @@
661
 
662
  </script>
663
  </body>
664
- </html>

 
 
 
1
  <head>
2
  <meta charset="UTF-8">
3
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
8
  :root {
9
  --bg-page: #ffffff;
10
  --text-main: #000000;
 
 
11
  --font-sans: 'Inter', sans-serif;
12
  --font-mono: 'JetBrains Mono', monospace;
13
  }
 
50
 
51
  /* --- Controls --- */
52
  .control-group { margin-bottom: 16px; width: 100%; }
53
+
54
+
55
+
56
+
57
+
58
+
59
 
60
  .label-row {
61
  display: flex;
 
78
  color: var(--text-main);
79
  padding: 0;
80
  }
81
+ .num-input:focus { color: blue; }
82
 
83
  /* Custom Range Slider Styling */
84
  input[type="range"] {
 
89
  cursor: pointer;
90
  }
91
 
 
92
  input[type="range"]::-webkit-slider-runnable-track {
93
  width: 100%;
94
  height: 1px;
 
96
  border: none;
97
  }
98
 
 
99
  input[type="range"]::-webkit-slider-thumb {
100
  -webkit-appearance: none;
101
  height: 14px;
102
  width: 4px;
103
  background: #000000;
104
  border-radius: 0;
105
+ margin-top: -6.5px;
106
  border: none;
107
  }
108
 
 
133
  background: #f5f5f5;
134
  }
135
 
 
136
  .btn-upload.filled {
137
  background: #000000;
138
  color: #ffffff;
 
156
 
157
  /* --- Action Buttons --- */
158
  .buttons-container { display: flex; gap: 12px; margin-top: auto; padding-top: 20px;}
159
+
160
+
161
+
162
+
163
 
164
  .btn-action {
165
  flex: 1;
 
190
  /* --- Preview Area --- */
191
  .preview-area {
192
  flex: 1;
193
+ background-color: #ffffff; /* Default, will be overridden by JS */
194
  display: flex;
195
  align-items: center;
196
  justify-content: center;
 
199
  }
200
 
201
  canvas {
202
+ box-shadow: 0 0 0 1px #f0f0f0;
203
  max-width: 100%;
204
  max-height: 100%;
205
  object-fit: contain;
 
219
  -webkit-appearance: none;
220
  }
221
 
 
222
  input[type="checkbox"] {
223
  accent-color: black;
224
+ width: 13px;
225
+ height: 13px;
226
+ }
227
+
228
+ /* --- Custom Color Picker Styling --- */
229
+ input[type="color"] {
230
+ -webkit-appearance: none;
231
+ border: 1px solid #000000;
232
+ width: 13px;
233
+ height: 13px;
234
+ padding: 0;
235
+ background: none;
236
+ cursor: pointer;
237
+ border-radius: 0;
238
+ }
239
+ input[type="color"]::-webkit-color-swatch-wrapper {
240
+ padding: 0;
241
+ }
242
+ input[type="color"]::-webkit-color-swatch {
243
+ border: none;
244
+ border-radius: 0;
245
  }
246
 
247
  /* Hidden sources */
 
256
  </div>
257
 
258
  <div class="section">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  <div class="control-group">
261
  <div class="label-row">
 
263
  <input type="number" class="num-input" id="num-shift_stagger" value="0" step="0.01" min="0" max="1">
264
  </div>
265
  <input type="range" id="shift_stagger" min="0" max="1" step="0.01" value="0">
 
 
 
 
 
 
 
 
 
266
 
267
  <div class="control-group">
268
  <div class="label-row">
 
270
  <input type="number" class="num-input" id="num-growth" value="0" step="0.05" min="0" max="2.0">
271
  </div>
272
  <input type="range" id="growth" min="0" max="2.0" step="0.05" value="0">
273
+
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
  </div>
283
 
284
+ <div class="control-group" style="display:flex; justify-content: space-between; align-items: center; margin-top:24px;">
 
 
 
285
  <span>Invert Logic</span>
286
  </label>
287
  </div>
 
288
 
289
+ <div style="display:flex; align-items:center; gap:8px;">
290
+ <span style="font-size:11px; font-family:var(--font-mono); cursor: default;">COLOR</span>
291
+ <input type="color" id="bg_color" value="#ffffff" title="Background Color">
292
+ <div class="control-group" style="margin-top:16px;">
293
  <span style="font-size:10px; color:#aaa; font-family:var(--font-mono); display:block; margin-bottom:4px;">GROWTH TYPE</span>
294
  <select id="growth_type">
295
  <option value="sine">Diagonal Sine</option>
 
298
  </select>
299
  </div>
300
  </div>
 
 
 
301
  <button class="btn-action secondary" id="btn-dl-svg">Download SVG</button>
302
  <button class="btn-action" id="btn-dl-video" disabled>Export MP4</button>
303
  </div>
304
  <div id="status-msg" style="font-size:10px; font-family:var(--font-mono); margin-top:10px; text-align:center;"></div>
305
  </div>
306
 
307
+ <div class="preview-area" id="preview-wrapper">
308
  <canvas id="main-canvas" width="1400" height="900"></canvas>
309
 
310
  <img id="source-image" src="" crossorigin="anonymous">
311
  <video id="source-video" src="" playsinline loop muted crossorigin="anonymous"></video>
312
  <canvas id="helper-canvas" style="display:none;"></canvas>
 
 
313
 
314
  <script>
315
  // --- STATE ---
 
317
  let currentFile = null; // Храним сам объект файла
318
  let animationId = null;
319
  let customShapeLoaded = false;
320
+
321
+ // Cached colored shape canvas to avoid recoloring every frame
322
+ let cachedShapeCanvas = null;
323
+ let lastFillColor = "#000000";
324
 
325
  // --- DOM ---
326
  const canvas = document.getElementById('main-canvas');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  }
328
  };
329
 
330
+ // --- HELPER: TINT SHAPE ---
331
+ function updateCachedShape(color) {
332
+ if (!customShapeLoaded || !shapeImg.src) {
333
+ cachedShapeCanvas = null;
334
+ return;
335
+ }
336
+
337
+ const c = document.createElement('canvas');
338
+ c.width = shapeImg.width || 100;
339
+ c.height = shapeImg.height || 100;
340
+ const x = c.getContext('2d');
341
+
342
+ // Draw original shape
343
+ x.drawImage(shapeImg, 0, 0, c.width, c.height);
344
+
345
+ // Composite operation to tint non-transparent pixels
346
+ x.globalCompositeOperation = 'source-in';
347
+ x.fillStyle = color;
348
+ x.fillRect(0, 0, c.width, c.height);
349
+
350
+ cachedShapeCanvas = c;
351
+ lastFillColor = color;
352
+ }
353
+
354
  // --- EVENT LISTENERS ---
355
 
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
  // 1. Shape Upload
370
  const shapeInput = document.getElementById('shape_svg');
371
  shapeInput.addEventListener('change', (e) => {
 
377
  const reader = new FileReader();
378
  reader.onload = (evt) => {
379
  const blob = new Blob([evt.target.result], {type: 'image/svg+xml'});
 
 
 
380
  reader.readAsText(file);
381
  });
382
 
 
384
  shapeInput.value = '';
385
  customShapeLoaded = false;
386
  shapeImg.src = '';
387
+ cachedShapeCanvas = null;
388
  setBtnState('lbl-shape', 'rm-shape', null, false);
389
  requestRender();
390
  });
 
420
  sourceVideo.play();
421
 
422
  document.getElementById('btn-dl-video').disabled = false;
423
+ document.getElementById('btn-dl-svg').disabled = true;
424
  requestRender();
425
  }
426
  });
 
453
  document.getElementById('invert').addEventListener('change', requestRender);
454
  document.getElementById('growth_type').addEventListener('change', requestRender);
455
 
456
+ // 4. Color Inputs
457
+ const bgColorInput = document.getElementById('bg_color');
458
+ const fillColorInput = document.getElementById('fill_color');
459
+
460
+ bgColorInput.addEventListener('input', () => {
461
+ // Меняем цвет фона страницы предпросмотра для визуального комфорта
462
+ previewWrapper.style.backgroundColor = bgColorInput.value;
463
+ requestRender();
464
+ });
465
+
466
+ fillColorInput.addEventListener('input', () => {
467
+ const color = fillColorInput.value;
468
+ if(customShapeLoaded) {
469
+ updateCachedShape(color);
470
  }
471
+ requestRender();
472
+ });
473
+
474
 
475
+ // --- RENDER LOGIC ---
476
+ function requestRender() {
477
  if (currentMode === 'video' && !sourceVideo.paused) {
 
 
 
 
478
  }
479
 
480
  function drawFrame() {
 
485
  const density = parseInt(document.getElementById('density').value);
486
  const gapX = parseFloat(document.getElementById('gap_x').value);
487
  const gapY = parseFloat(document.getElementById('gap_y').value);
 
488
  const baseScale = parseFloat(document.getElementById('base_scale').value);
489
  const growth = parseFloat(document.getElementById('growth').value);
490
  const growthType = document.getElementById('growth_type').value;
491
+
492
  const invert = document.getElementById('invert').checked;
493
+ const bgColor = bgColorInput.value;
494
+ const fillColor = fillColorInput.value;
495
 
496
+ // Clear with BG Color
497
+ ctx.fillStyle = bgColor;
498
  ctx.fillRect(0, 0, w, h);
499
 
500
  // Read Pixel Data
 
516
  }
517
 
518
  // Draw Grid
519
+ ctx.fillStyle = fillColor;
520
 
521
  const step = w / density;
522
  const rows = Math.ceil(h / step);
 
554
  if (!isVisible) continue;
555
 
556
  // 2. Growth
557
+
558
+
559
+
560
+
561
  let wave = 0;
562
  if (growthType === 'sine') wave = (Math.sin(c * 0.3 + r * 0.3) + 1) / 2;
563
  else if (growthType === 'linear') wave = ((c % density) / density);
 
566
  let modW = 1.0 - (wave * growth);
567
  modW = Math.max(0.01, Math.min(1.0, modW));
568
 
569
+
570
+
571
+
572
  const contentW = cellWMax * baseScale * modW;
573
  const contentH = cellHMax * baseScale;
574
 
 
579
  ctx.save();
580
  ctx.translate(cx, cy);
581
 
582
+ if (customShapeLoaded && cachedShapeCanvas) {
583
+ // Draw tinted shape
584
+ ctx.drawImage(cachedShapeCanvas, -contentW/2, -contentH/2, contentW, contentH);
585
  } else {
586
+ // Draw simple rect
587
  ctx.fillRect(-contentW/2, -contentH/2, contentW, contentH);
588
  }
589
  ctx.restore();
 
602
  paramIds.forEach(id => form.append(id, document.getElementById(id).value));
603
  form.append('growth_type', document.getElementById('growth_type').value);
604
  form.append('invert', document.getElementById('invert').checked);
605
+ form.append('bg_color', document.getElementById('bg_color').value);
606
+ form.append('fill_color', document.getElementById('fill_color').value);
607
 
608
  // Attach SVG Shape
609
  if(shapeInput.files[0]) form.append('shape_svg', shapeInput.files[0]);
610
 
611
  // Attach Image (if existing)
 
612
  if(currentFile && currentMode === 'image') {
613
  form.append('raster_image', currentFile);
614
  }
 
642
  paramIds.forEach(id => form.append(id, document.getElementById(id).value));
643
  form.append('growth_type', document.getElementById('growth_type').value);
644
  form.append('invert', document.getElementById('invert').checked);
645
+ form.append('bg_color', document.getElementById('bg_color').value);
646
+ form.append('fill_color', document.getElementById('fill_color').value);
647
 
648
  // Attach Video
 
649
  if(currentFile && currentMode === 'video') {
650
  form.append('video_file', currentFile);
651
  }
 
676
 
677
  </script>
678
  </body>
679
+ </html>