Open-Source-Lab commited on
Commit
11ad04c
·
verified ·
1 Parent(s): fff5858

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +635 -940
index.html CHANGED
@@ -1,970 +1,665 @@
 
 
 
 
1
  <html>
2
- <style>
3
- body {
4
- display: flex;
5
- flex-direction: column;
6
- align-items: center;
7
- justify-content: center;
8
- height: 100vh;
9
- margin: 0;
10
- background-color: #f0f0f0;
11
- font-family: Arial, sans-serif;
12
- }
13
-
14
- .container {
15
- display: flex;
16
- flex-direction: column;
17
- gap: 15px;
18
- max-width: 800px;
19
- width: 100%;
20
- }
21
-
22
- .canvas-container {
23
- position: relative;
24
- width: 100%;
25
- }
26
-
27
- canvas {
28
- border: 1px solid #000;
29
- cursor: crosshair;
30
- background-color: white;
31
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
32
- width: 100%;
33
- height: auto;
34
- }
35
-
36
- .toolbar {
37
- display: flex;
38
- flex-wrap: wrap;
39
- gap: 10px;
40
- background-color: #ffffff;
41
- padding: 12px;
42
- border-radius: 5px;
43
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
44
- }
45
-
46
- .tool-group {
47
- display: flex;
48
- align-items: center;
49
- gap: 8px;
50
- padding: 6px;
51
- border-radius: 4px;
52
- background-color: #f7f7f7;
53
- flex-wrap: wrap;
54
- }
55
-
56
- button {
57
- padding: 8px 12px;
58
- font-size: 14px;
59
- border: none;
60
- border-radius: 4px;
61
- background-color: #4a90e2;
62
- color: white;
63
- cursor: pointer;
64
- transition: all 0.2s;
65
- }
66
-
67
- button:hover {
68
- background-color: #357abd;
69
- transform: translateY(-1px);
70
- }
71
-
72
- button:active {
73
- transform: translateY(0);
74
- }
75
-
76
- button.active {
77
- background-color: #2c5990;
78
- }
79
-
80
- button.danger {
81
- background-color: #e74c3c;
82
- }
83
-
84
- button.danger:hover {
85
- background-color: #c0392b;
86
- }
87
-
88
- button.secondary {
89
- background-color: #95a5a6;
90
- }
91
-
92
- button.secondary:hover {
93
- background-color: #7f8c8d;
94
- }
95
-
96
- input[type="color"] {
97
- width: 40px;
98
- height: 40px;
99
- padding: 0;
100
- border: none;
101
- border-radius: 4px;
102
- cursor: pointer;
103
- }
104
-
105
- input[type="range"] {
106
- width: 100px;
107
- }
108
-
109
- .color-preview {
110
- width: 24px;
111
- height: 24px;
112
- border-radius: 50%;
113
- border: 2px solid #ddd;
114
- }
115
-
116
- .tool-option {
117
- display: flex;
118
- align-items: center;
119
- gap: 5px;
120
- }
121
-
122
- label {
123
- font-size: 14px;
124
- }
125
-
126
- .size-display {
127
- min-width: 30px;
128
- text-align: center;
129
- }
130
-
131
- .layer-panel {
132
- display: flex;
133
- flex-direction: column;
134
- gap: 8px;
135
- padding: 12px;
136
- background-color: #ffffff;
137
- border-radius: 5px;
138
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
139
- }
140
-
141
- .layer-list {
142
- display: flex;
143
- flex-direction: column;
144
- gap: 5px;
145
- }
146
-
147
- .layer-item {
148
- display: flex;
149
- align-items: center;
150
- justify-content: space-between;
151
- padding: 6px;
152
- background-color: #f7f7f7;
153
- border-radius: 4px;
154
- }
155
-
156
- .layer-item.active {
157
- background-color: #d3e8ff;
158
- }
159
-
160
- .layer-buttons {
161
- display: flex;
162
- gap: 5px;
163
- }
164
-
165
- .status-bar {
166
- display: flex;
167
- justify-content: space-between;
168
- width: 100%;
169
- padding: 6px 12px;
170
- background-color: #ffffff;
171
- border-radius: 5px;
172
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
173
- font-size: 12px;
174
- color: #555;
175
- }
176
-
177
- #colorPalette {
178
- display: flex;
179
- gap: 5px;
180
- flex-wrap: wrap;
181
- }
182
-
183
- .palette-color {
184
- width: 24px;
185
- height: 24px;
186
- border-radius: 4px;
187
- cursor: pointer;
188
- border: 1px solid #ddd;
189
- }
190
-
191
- .palette-color:hover {
192
- transform: scale(1.1);
193
- }
194
-
195
- .brush-preview {
196
- width: 40px;
197
- height: 40px;
198
- border-radius: 4px;
199
- background-color: #f7f7f7;
200
- position: relative;
201
- }
202
-
203
- .brush-preview-dot {
204
- position: absolute;
205
- top: 50%;
206
- left: 50%;
207
- transform: translate(-50%, -50%);
208
- border-radius: 50%;
209
- background-color: #000;
210
- }
211
-
212
- .modal {
213
- display: none;
214
- position: fixed;
215
- top: 0;
216
- left: 0;
217
- width: 100%;
218
- height: 100%;
219
- background-color: rgba(0, 0, 0, 0.5);
220
- z-index: 1000;
221
- justify-content: center;
222
- align-items: center;
223
- }
224
-
225
- .modal-content {
226
- background-color: white;
227
- padding: 20px;
228
- border-radius: 5px;
229
- max-width: 400px;
230
- width: 100%;
231
- }
232
-
233
- .modal-title {
234
- margin-top: 0;
235
- }
236
-
237
- .modal-footer {
238
- display: flex;
239
- justify-content: flex-end;
240
- gap: 10px;
241
- margin-top: 20px;
242
- }
243
-
244
- .image-preview {
245
- max-width: 100%;
246
- margin-top: 10px;
247
- border: 1px solid #ddd;
248
- border-radius: 4px;
249
- }
250
-
251
- .loading {
252
- display: flex;
253
- align-items: center;
254
- justify-content: center;
255
- padding: 20px;
256
- }
257
-
258
- .loading:after {
259
- content: " ";
260
- display: block;
261
- width: 24px;
262
- height: 24px;
263
- border-radius: 50%;
264
- border: 6px solid #5D5CDE;
265
- border-color: #5D5CDE transparent #5D5CDE transparent;
266
- animation: loading 1.2s linear infinite;
267
- }
268
-
269
- @keyframes loading {
270
- 0% {
271
- transform: rotate(0deg);
272
- }
273
- 100% {
274
- transform: rotate(360deg);
275
- }
276
- }
277
-
278
- .transform-modal {
279
- max-width: 90%;
280
- max-height: 90vh;
281
- overflow-y: auto;
282
- }
283
-
284
- .transform-preview {
285
- text-align: center;
286
- margin-top: 15px;
287
- }
288
-
289
- .transform-preview img {
290
- max-width: 100%;
291
- max-height: 60vh;
292
- border-radius: 4px;
293
- border: 1px solid #ddd;
294
- }
295
-
296
- @media (prefers-color-scheme: dark) {
297
- body {
298
- background-color: #181818;
299
- color: #e0e0e0;
300
- }
301
-
302
- .toolbar, .status-bar, .modal-content, .layer-panel {
303
- background-color: #292929;
304
- color: #e0e0e0;
305
- }
306
-
307
- .tool-group, .layer-item {
308
- background-color: #383838;
309
- }
310
-
311
- .layer-item.active {
312
- background-color: #3a4d64;
313
- }
314
-
315
- canvas {
316
- border-color: #444;
317
- }
318
-
319
- .palette-color {
320
- border-color: #444;
321
- }
322
- }
323
- </style>
324
  </head>
325
- <body>
326
- <div class="container">
327
- <div class="toolbar">
328
- <div class="tool-group">
329
- <button id="pencilTool" class="active" title="Pencil (P)">Pencil</button>
330
- <button id="brushTool" title="Brush (B)">Brush</button>
331
- <button id="eraserTool" title="Eraser (E)">Eraser</button>
332
- <button id="fillTool" title="Fill (F)">Fill</button>
333
- <button id="lineTool" title="Line (L)">Line</button>
334
- <button id="rectangleTool" title="Rectangle (R)">Rectangle</button>
335
- <button id="circleTool" title="Circle (C)">Circle</button>
336
  </div>
337
- <div class="tool-group">
338
- <div class="tool-option">
339
- <input type="color" id="colorPicker" value="#000000">
340
- <div id="currentColor" class="color-preview" style="background-color: #000000;"></div>
341
- </div>
342
- <div class="tool-option">
343
- <label for="brushSize">Size:</label>
344
- <input type="range" id="brushSize" min="1" max="50" value="5">
345
- <span id="sizeDisplay" class="size-display">5px</span>
346
- </div>
347
- <div class="tool-option">
348
- <label for="opacityRange">Opacity:</label>
349
- <input type="range" id="opacityRange" min="1" max="100" value="100">
350
- <span id="opacityDisplay" class="size-display">100%</span>
351
  </div>
352
  </div>
353
- </div>
354
 
355
- <div class="canvas-container">
356
- <canvas id="paintCanvas" width="800" height="600"></canvas>
357
- </div>
 
 
 
 
 
 
 
 
 
358
 
359
- <div class="toolbar">
360
- <div class="tool-group">
361
- <button id="undoButton" title="Undo (Ctrl+Z)">Undo</button>
362
- <button id="redoButton" title="Redo (Ctrl+Y)">Redo</button>
363
- <button id="clearButton" class="danger" title="Clear">Clear</button>
364
- </div>
365
- <div class="tool-group">
366
- <button id="saveButton" title="Save (Ctrl+S)">Save</button>
367
- <button id="loadButton" title="Load">Load</button>
368
- <button id="exportButton" title="Export as PNG">Export</button>
369
- <button id="downloadButton" title="Download Image">Download Image</button>
370
- <button id="transformButton" title="Transform with Image-Photo">Transform Image</button>
371
- </div>
372
- <div class="tool-group">
373
- <div id="colorPalette">
374
- <div class="palette-color" style="background-color: #000000;"></div>
375
- <div class="palette-color" style="background-color: #ffffff;"></div>
376
- <div class="palette-color" style="background-color: #ff0000;"></div>
377
- <div class="palette-color" style="background-color: #00ff00;"></div>
378
- <div class="palette-color" style="background-color: #0000ff;"></div>
379
- <div class="palette-color" style="background-color: #ffff00;"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  </div>
381
  </div>
382
- </div>
383
 
384
- <div class="status-bar">
385
- <span id="positionDisplay">Position: 0, 0</span>
386
- <span id="toolInfo">Pencil Tool | Size: 5px | Color: #000000</span>
387
- </div>
388
- </div>
389
-
390
- <div id="saveModal" class="modal">
391
- <div class="modal-content">
392
- <h3 class="modal-title">Save Drawing</h3>
393
- <div>
394
- <label for="saveFilename">Filename:</label>
395
- <input type="text" id="saveFilename" value="my-drawing" style="width: 100%; margin-top: 5px; padding: 5px;">
 
 
 
 
396
  </div>
397
- <div class="modal-footer">
398
- <button id="cancelSave" class="secondary">Cancel</button>
399
- <button id="confirmSave">Save</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  </div>
401
  </div>
402
- </div>
403
-
404
- <div id="transformModal" class="modal">
405
- <div class="modal-content transform-modal">
406
- <h3 class="modal-title">Transform Image</h3>
407
- <div>
408
- <label for="transformPrompt">Enter a prompt for @Image-Photo:</label>
409
- <input type="text" id="transformPrompt" placeholder="Describe how to transform the image..." style="width: 100%; margin-top: 5px; padding: 5px; font-size: 16px;">
410
- </div>
411
- <div id="transformLoading" class="loading" style="display: none;"></div>
412
- <div id="transformPreview" class="transform-preview"></div>
413
- <div class="modal-footer">
414
- <button id="cancelTransform" class="secondary">Cancel</button>
415
- <button id="confirmTransform">Send to Image-Photo</button>
416
- <button id="downloadTransformed" style="display: none;">Download Transformed</button>
417
  </div>
418
  </div>
419
- </div>
420
-
421
- <script>
422
- // Check if dark mode is preferred
423
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
424
- document.documentElement.classList.add('dark');
425
- }
426
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
427
- if (event.matches) {
428
- document.documentElement.classList.add('dark');
429
- } else {
430
- document.documentElement.classList.remove('dark');
431
- }
432
- });
433
-
434
- const canvas = document.getElementById('paintCanvas');
435
- const ctx = canvas.getContext('2d');
436
- const colorPicker = document.getElementById('colorPicker');
437
- const currentColor = document.getElementById('currentColor');
438
- const brushSize = document.getElementById('brushSize');
439
- const sizeDisplay = document.getElementById('sizeDisplay');
440
- const opacityRange = document.getElementById('opacityRange');
441
- const opacityDisplay = document.getElementById('opacityDisplay');
442
- const positionDisplay = document.getElementById('positionDisplay');
443
- const toolInfo = document.getElementById('toolInfo');
444
- const pencilTool = document.getElementById('pencilTool');
445
- const brushTool = document.getElementById('brushTool');
446
- const eraserTool = document.getElementById('eraserTool');
447
- const fillTool = document.getElementById('fillTool');
448
- const lineTool = document.getElementById('lineTool');
449
- const rectangleTool = document.getElementById('rectangleTool');
450
- const circleTool = document.getElementById('circleTool');
451
- const undoButton = document.getElementById('undoButton');
452
- const redoButton = document.getElementById('redoButton');
453
- const clearButton = document.getElementById('clearButton');
454
- const saveButton = document.getElementById('saveButton');
455
- const loadButton = document.getElementById('loadButton');
456
- const exportButton = document.getElementById('exportButton');
457
- const downloadButton = document.getElementById('downloadButton');
458
- const transformButton = document.getElementById('transformButton');
459
-
460
- const saveModal = document.getElementById('saveModal');
461
- const saveFilename = document.getElementById('saveFilename');
462
- const cancelSave = document.getElementById('cancelSave');
463
- const confirmSave = document.getElementById('confirmSave');
464
-
465
- const transformModal = document.getElementById('transformModal');
466
- const transformPrompt = document.getElementById('transformPrompt');
467
- const transformLoading = document.getElementById('transformLoading');
468
- const transformPreview = document.getElementById('transformPreview');
469
- const cancelTransform = document.getElementById('cancelTransform');
470
- const confirmTransform = document.getElementById('confirmTransform');
471
- const downloadTransformed = document.getElementById('downloadTransformed');
472
-
473
- let isDrawing = false;
474
- let lastX = 0;
475
- let lastY = 0;
476
- let currentTool = 'pencil';
477
- let undoStack = [];
478
- let redoStack = [];
479
- let startX = 0;
480
- let startY = 0;
481
- let transformedImageUrl = null;
482
-
483
- function initCanvas() {
484
- ctx.fillStyle = 'white';
485
- ctx.fillRect(0, 0, canvas.width, canvas.height);
486
- saveState();
487
- }
488
-
489
- function saveState() {
490
- redoStack = [];
491
- undoStack.push(canvas.toDataURL());
492
- updateButtons();
493
- }
494
-
495
- function updateButtons() {
496
- undoButton.disabled = undoStack.length <= 1;
497
- redoButton.disabled = redoStack.length === 0;
498
- }
499
-
500
- function undo() {
501
- if (undoStack.length <= 1) return;
502
- redoStack.push(undoStack.pop());
503
- const img = new Image();
504
- img.src = undoStack[undoStack.length - 1];
505
- img.onload = () => {
506
- ctx.clearRect(0, 0, canvas.width, canvas.height);
507
- ctx.drawImage(img, 0, 0);
508
- updateButtons();
509
- };
510
- }
511
-
512
- function redo() {
513
- if (redoStack.length === 0) return;
514
- const img = new Image();
515
- img.src = redoStack.pop();
516
- img.onload = () => {
517
- undoStack.push(img.src);
518
- ctx.clearRect(0, 0, canvas.width, canvas.height);
519
- ctx.drawImage(img, 0, 0);
520
- updateButtons();
521
- };
522
- }
523
-
524
- function clearCanvas() {
525
- saveState();
526
- ctx.fillStyle = 'white';
527
- ctx.fillRect(0, 0, canvas.width, canvas.height);
528
- }
529
-
530
- function startDrawing(e) {
531
- isDrawing = true;
532
- const rect = canvas.getBoundingClientRect();
533
- lastX = e.clientX - rect.left;
534
- lastY = e.clientY - rect.top;
535
- startX = lastX;
536
- startY = lastY;
537
-
538
- if (['line', 'rectangle', 'circle'].includes(currentTool)) {
539
- saveState();
540
- return;
541
- }
542
-
543
- ctx.beginPath();
544
- if (currentTool === 'fill') {
545
- floodFill(Math.floor(lastX), Math.floor(lastY), hexToRgb(colorPicker.value));
546
- isDrawing = false;
547
- saveState();
548
- return;
549
- }
550
-
551
- setupContext();
552
- if (['pencil', 'brush'].includes(currentTool)) {
553
- ctx.arc(lastX, lastY, 0.5, 0, Math.PI * 2);
554
- ctx.fillStyle = ctx.strokeStyle;
555
- ctx.fill();
556
- }
557
- saveState();
558
- }
559
-
560
- function draw(e) {
561
- if (!isDrawing) return;
562
- const rect = canvas.getBoundingClientRect();
563
- const currentX = e.clientX - rect.left;
564
- const currentY = e.clientY - rect.top;
565
- positionDisplay.textContent = `Position: ${Math.floor(currentX)}, ${Math.floor(currentY)}`;
566
-
567
- if (currentTool === 'pencil' || currentTool === 'brush' || currentTool === 'eraser') {
568
- ctx.beginPath();
569
- ctx.moveTo(lastX, lastY);
570
- ctx.lineTo(currentX, currentY);
571
- ctx.stroke();
572
- lastX = currentX;
573
- lastY = currentY;
574
- } else if (['line', 'rectangle', 'circle'].includes(currentTool)) {
575
- const img = new Image();
576
- img.src = undoStack[undoStack.length - 1];
577
- ctx.clearRect(0, 0, canvas.width, canvas.height);
578
- ctx.drawImage(img, 0, 0);
579
- setupContext();
580
-
581
- if (currentTool === 'line') {
582
- ctx.beginPath();
583
- ctx.moveTo(startX, startY);
584
- ctx.lineTo(currentX, currentY);
585
- ctx.stroke();
586
- } else if (currentTool === 'rectangle') {
587
- const width = currentX - startX;
588
- const height = currentY - startY;
589
- ctx.strokeRect(startX, startY, width, height);
590
- } else if (currentTool === 'circle') {
591
- const radius = Math.sqrt(Math.pow(currentX - startX, 2) + Math.pow(currentY - startY, 2));
592
- ctx.beginPath();
593
- ctx.arc(startX, startY, radius, 0, Math.PI * 2);
594
- ctx.stroke();
595
  }
596
- }
597
- }
598
-
599
- function endDrawing() {
600
- if (!isDrawing) return;
601
- isDrawing = false;
602
- if (['line', 'rectangle', 'circle'].includes(currentTool)) {
603
- saveState();
604
- }
605
- }
606
-
607
- function setupContext() {
608
- const size = parseInt(brushSize.value);
609
- const opacity = parseInt(opacityRange.value) / 100;
610
- ctx.lineWidth = size;
611
- ctx.lineCap = 'round';
612
- ctx.lineJoin = 'round';
613
-
614
- if (currentTool === 'eraser') {
615
- ctx.globalCompositeOperation = 'destination-out';
616
- ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
617
- } else {
618
- ctx.globalCompositeOperation = 'source-over';
619
- const color = colorPicker.value;
620
- ctx.strokeStyle = `${color}${Math.round(opacity * 255).toString(16).padStart(2, '0')}`;
621
- }
622
- }
623
-
624
- function floodFill(x, y, targetColor) {
625
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
626
- const data = imageData.data;
627
- const width = canvas.width;
628
- const height = canvas.height;
629
- const index = (y * width + x) * 4;
630
- const clickedColor = { r: data[index], g: data[index + 1], b: data[index + 2], a: data[index + 3] };
631
-
632
- if (colorMatch(clickedColor, targetColor)) return;
633
-
634
- const stack = [{x, y}];
635
- while (stack.length > 0) {
636
- const pixel = stack.pop();
637
- const px = pixel.x;
638
- const py = pixel.y;
639
-
640
- if (px < 0 || px >= width || py < 0 || py >= height) continue;
641
-
642
- const currentIndex = (py * width + px) * 4;
643
- const currentColor = {
644
- r: data[currentIndex],
645
- g: data[currentIndex + 1],
646
- b: data[currentIndex + 2],
647
- a: data[currentIndex + 3]
648
- };
649
-
650
- if (colorMatch(currentColor, clickedColor)) {
651
- data[currentIndex] = targetColor.r;
652
- data[currentIndex + 1] = targetColor.g;
653
- data[currentIndex + 2] = targetColor.b;
654
- data[currentIndex + 3] = 255;
655
-
656
- stack.push({x: px + 1, y: py});
657
- stack.push({x: px - 1, y: py});
658
- stack.push({x: px, y: py + 1});
659
- stack.push({x: px, y: py - 1});
660
  }
661
- }
662
-
663
- ctx.putImageData(imageData, 0, 0);
664
- }
665
-
666
- function colorMatch(color1, color2, tolerance = 10) {
667
- return Math.abs(color1.r - color2.r) <= tolerance &&
668
- Math.abs(color1.g - color2.g) <= tolerance &&
669
- Math.abs(color1.b - color2.b) <= tolerance;
670
- }
671
-
672
- function hexToRgb(hex) {
673
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
674
- return result ? {
675
- r: parseInt(result[1], 16),
676
- g: parseInt(result[2], 16),
677
- b: parseInt(result[3], 16)
678
- } : {r: 0, g: 0, b: 0};
679
- }
680
-
681
- function saveDrawing() {
682
- const filename = saveFilename.value.trim() || 'my-drawing';
683
- const dataURL = canvas.toDataURL();
684
- try {
685
- localStorage.setItem(`painting-${filename}`, dataURL);
686
- alert(`Drawing saved as "${filename}"`);
687
- } catch (e) {
688
- alert('Error saving drawing. Local storage might be full or disabled.');
689
- }
690
- saveModal.style.display = 'none';
691
- }
692
-
693
- function loadDrawing() {
694
- const savedDrawings = [];
695
- for (let i = 0; i < localStorage.length; i++) {
696
- const key = localStorage.key(i);
697
- if (key.startsWith('painting-')) {
698
- savedDrawings.push(key.replace('painting-', ''));
699
  }
700
- }
701
-
702
- if (savedDrawings.length === 0) {
703
- alert('No saved drawings found.');
704
- return;
705
- }
706
-
707
- const drawing = prompt(`Enter filename to load (available: ${savedDrawings.join(', ')})`);
708
- if (!drawing) return;
709
-
710
- const dataURL = localStorage.getItem(`painting-${drawing}`);
711
- if (!dataURL) {
712
- alert(`Drawing "${drawing}" not found.`);
713
- return;
714
- }
715
-
716
- const img = new Image();
717
- img.src = dataURL;
718
- img.onload = () => {
719
- ctx.clearRect(0, 0, canvas.width, canvas.height);
720
- ctx.drawImage(img, 0, 0);
721
- saveState();
722
- };
723
- }
724
-
725
- function exportDrawing() {
726
- const link = document.createElement('a');
727
- link.download = 'drawing.png';
728
- link.href = canvas.toDataURL('image/png');
729
- link.click();
730
- }
731
-
732
- // New function for downloading image
733
- function downloadImage() {
734
- const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
735
- const link = document.createElement('a');
736
- link.download = `drawing-${timestamp}.png`;
737
- link.href = canvas.toDataURL('image/png');
738
- link.click();
739
- }
740
-
741
- // New function to open transform modal
742
- function openTransformModal() {
743
- transformPrompt.value = '';
744
- transformPreview.innerHTML = '';
745
- downloadTransformed.style.display = 'none';
746
- transformModal.style.display = 'flex';
747
- }
748
-
749
- // New function to transform image with Image-Photo
750
- async function transformImage() {
751
- const prompt = transformPrompt.value.trim();
752
- if (!prompt) {
753
- alert('Please enter a prompt for the transformation');
754
- return;
755
- }
756
-
757
- // Show loading state
758
- transformLoading.style.display = 'block';
759
- confirmTransform.disabled = true;
760
- transformPreview.innerHTML = '';
761
- downloadTransformed.style.display = 'none';
762
- transformedImageUrl = null;
763
-
764
- try {
765
- // Convert canvas to File
766
- const dataUrl = canvas.toDataURL('image/png');
767
- const res = await fetch(dataUrl);
768
- const blob = await res.blob();
769
- const file = new File([blob], 'drawing.png', { type: 'image/png' });
770
-
771
- // Register handler for Image-Photo response
772
- const handlerId = `image-transform-${Date.now()}`;
773
- window.Poe.registerHandler(handlerId, (result) => {
774
- const response = result.responses[0];
775
-
776
- if (response.status === "error") {
777
- transformLoading.style.display = 'none';
778
- transformPreview.innerHTML = `<p style="color: red;">Error: ${response.statusText || 'Failed to transform image'}</p>`;
779
- confirmTransform.disabled = false;
780
- }
781
- else if (response.status === "complete") {
782
- transformLoading.style.display = 'none';
783
- confirmTransform.disabled = false;
784
 
785
- if (response.attachments?.length > 0) {
786
- const imageAttachment = response.attachments[0];
787
- transformedImageUrl = imageAttachment.url;
788
 
789
- transformPreview.innerHTML = `
790
- <img src="${imageAttachment.url}" alt="Transformed image">
791
- <p>${response.content}</p>
792
- `;
793
-
794
- downloadTransformed.style.display = 'inline-block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  } else {
796
- transformPreview.innerHTML = `<p>${response.content}</p>
797
- <p style="color: #f59e0b;">No image was returned. Try a different prompt.</p>`;
798
  }
 
799
  }
800
  });
801
 
802
- // Send message to Image-Photo
803
- await window.Poe.sendUserMessage(
804
- "@Image-Photo " + prompt,
805
- {
806
- handler: handlerId,
807
- stream: false,
808
- openChat: false,
809
- attachments: [file]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
  }
811
- );
812
- await window.Poe.sendUserMessage(
813
- "@Free-Thinking-Server " + prompt,
814
- {
815
- handler: handlerId,
816
- stream: false,
817
- openChat: false,
818
- attachments: [file]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
  }
820
- );
821
- } catch (err) {
822
- transformLoading.style.display = 'none';
823
- confirmTransform.disabled = false;
824
- transformPreview.innerHTML = `<p style="color: red;">Error: ${err.message || 'Failed to send image'}</p>`;
825
- console.error("Error:", err);
826
- }
827
- }
828
-
829
- // Function to download transformed image
830
- function downloadTransformedImage() {
831
- if (!transformedImageUrl) return;
832
-
833
- const link = document.createElement('a');
834
- link.href = transformedImageUrl;
835
- link.download = `transformed-${Date.now()}.png`;
836
- link.click();
837
- }
838
-
839
- function setCurrentTool(tool) {
840
- currentTool = tool;
841
- document.querySelectorAll('.tool-group button').forEach(btn => {
842
- btn.classList.remove('active');
843
- });
844
- document.getElementById(`${tool}Tool`).classList.add('active');
845
- updateToolInfo();
846
- }
847
-
848
- function updateToolInfo() {
849
- const size = brushSize.value;
850
- const color = colorPicker.value;
851
- const opacity = opacityRange.value;
852
- let toolName;
853
-
854
- switch (currentTool) {
855
- case 'pencil': toolName = 'Pencil'; break;
856
- case 'brush': toolName = 'Brush'; break;
857
- case 'eraser': toolName = 'Eraser'; break;
858
- case 'fill': toolName = 'Fill'; break;
859
- case 'line': toolName = 'Line'; break;
860
- case 'rectangle': toolName = 'Rectangle'; break;
861
- case 'circle': toolName = 'Circle'; break;
862
- default: toolName = 'Unknown';
863
- }
864
-
865
- toolInfo.textContent = `${toolName} Tool | Size: ${size}px | Color: ${color} | Opacity: ${opacity}%`;
866
- }
867
-
868
- function handleKeyDown(e) {
869
- if (document.activeElement.tagName === 'INPUT') return;
870
-
871
- if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
872
- e.preventDefault();
873
- undo();
874
- } else if (e.key === 'y' && (e.ctrlKey || e.metaKey)) {
875
- e.preventDefault();
876
- redo();
877
- } else if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
878
- e.preventDefault();
879
- saveModal.style.display = 'flex';
880
- } else if (e.key === 'p' || e.key === 'b' || e.key === 'e' || e.key === 'f' || e.key === 'l' || e.key === 'r' || e.key === 'c') {
881
- switch (e.key) {
882
- case 'p': setCurrentTool('pencil'); break;
883
- case 'b': setCurrentTool('brush'); break;
884
- case 'e': setCurrentTool('eraser'); break;
885
- case 'f': setCurrentTool('fill'); break;
886
- case 'l': setCurrentTool('line'); break;
887
- case 'r': setCurrentTool('rectangle'); break;
888
- case 'c': setCurrentTool('circle'); break;
889
  }
890
- }
891
- }
892
-
893
- function trackMousePosition(e) {
894
- const rect = canvas.getBoundingClientRect();
895
- const x = e.clientX - rect.left;
896
- const y = e.clientY - rect.top;
897
- positionDisplay.textContent = `Position: ${Math.floor(x)}, ${Math.floor(y)}`;
898
- }
899
-
900
- function rgbToHex(rgb) {
901
- const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
902
- if (match) {
903
- return "#" + ((1 << 24) + (parseInt(match[1]) << 16) + (parseInt(match[2]) << 8) + parseInt(match[3])).toString(16).slice(1);
904
- }
905
- return rgb;
906
- }
907
-
908
- // Event listeners
909
- canvas.addEventListener('mousedown', startDrawing);
910
- canvas.addEventListener('mousemove', draw);
911
- canvas.addEventListener('mouseup', endDrawing);
912
- canvas.addEventListener('mouseout', endDrawing);
913
- canvas.addEventListener('mousemove', trackMousePosition);
914
-
915
- pencilTool.addEventListener('click', () => setCurrentTool('pencil'));
916
- brushTool.addEventListener('click', () => setCurrentTool('brush'));
917
- eraserTool.addEventListener('click', () => setCurrentTool('eraser'));
918
- fillTool.addEventListener('click', () => setCurrentTool('fill'));
919
- lineTool.addEventListener('click', () => setCurrentTool('line'));
920
- rectangleTool.addEventListener('click', () => setCurrentTool('rectangle'));
921
- circleTool.addEventListener('click', () => setCurrentTool('circle'));
922
-
923
- colorPicker.addEventListener('input', () => {
924
- currentColor.style.backgroundColor = colorPicker.value;
925
- updateToolInfo();
926
- });
927
-
928
- brushSize.addEventListener('input', () => {
929
- sizeDisplay.textContent = `${brushSize.value}px`;
930
- updateToolInfo();
931
- });
932
-
933
- opacityRange.addEventListener('input', () => {
934
- opacityDisplay.textContent = `${opacityRange.value}%`;
935
- updateToolInfo();
936
- });
937
-
938
- undoButton.addEventListener('click', undo);
939
- redoButton.addEventListener('click', redo);
940
- clearButton.addEventListener('click', clearCanvas);
941
- saveButton.addEventListener('click', () => saveModal.style.display = 'flex');
942
- loadButton.addEventListener('click', loadDrawing);
943
- exportButton.addEventListener('click', exportDrawing);
944
-
945
- // New button event listeners
946
- downloadButton.addEventListener('click', downloadImage);
947
- transformButton.addEventListener('click', openTransformModal);
948
- cancelTransform.addEventListener('click', () => transformModal.style.display = 'none');
949
- confirmTransform.addEventListener('click', transformImage);
950
- downloadTransformed.addEventListener('click', downloadTransformedImage);
951
-
952
- cancelSave.addEventListener('click', () => saveModal.style.display = 'none');
953
- confirmSave.addEventListener('click', saveDrawing);
954
-
955
- document.querySelectorAll('.palette-color').forEach(colorEl => {
956
- colorEl.addEventListener('click', () => {
957
- const color = colorEl.style.backgroundColor;
958
- const hex = rgbToHex(color);
959
- colorPicker.value = hex;
960
- currentColor.style.backgroundColor = color;
961
- updateToolInfo();
962
- });
963
- });
964
-
965
- document.addEventListener('keydown', handleKeyDown);
966
-
967
- initCanvas();
968
- updateButtons();
969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
970
  </html>
 
1
+ <!-- https://poe.com/AddVideoWatermark-01
2
+ https://huggingface.co/spaces/Selfit/Remove-Watermark
3
+ https://huggingface.co/spaces/abdul9999/NoWatermark
4
+ -->
5
  <html>
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <script>
8
+ tailwind.config = {
9
+ darkMode: 'class',
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ primary: '#5D5CDE'
14
+ }
15
+ }
16
+ }
17
+ }
18
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </head>
20
+ <body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
21
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
22
+ <div class="text-center mb-8">
23
+ <h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
24
+ Add-Video-Watermark
25
+ </h1>
26
+ <p class="text-gray-600 dark:text-gray-400">Add custom text or image watermarks to your videos using canvas technology</p>
 
 
 
 
27
  </div>
28
+
29
+ <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6">
30
+ <h2 class="text-xl font-semibold mb-4">1. Upload Video</h2>
31
+ <div class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center">
32
+ <input type="file" id="videoInput" accept="video/*" class="hidden">
33
+ <button onclick="document.getElementById('videoInput').click()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-purple-600 transition-colors mb-2">
34
+ Choose Video File
35
+ </button>
36
+ <p class="text-sm text-gray-500 dark:text-gray-400">Supports MP4, WebM and other web-compatible formats</p>
37
+ <div id="videoInfo" class="mt-4 hidden">
38
+ <p class="text-sm font-medium text-green-600 dark:text-green-400"></p>
39
+ </div>
 
 
40
  </div>
41
  </div>
 
42
 
43
+ <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6" id="watermarkSection" style="display: none;">
44
+ <h2 class="text-xl font-semibold mb-4">2. Watermark Type</h2>
45
+ <div class="flex flex-wrap gap-4 mb-4">
46
+ <label class="flex items-center space-x-2 cursor-pointer">
47
+ <input type="radio" name="watermarkType" value="text" class="text-primary" checked>
48
+ <span>Text Watermark</span>
49
+ </label>
50
+ <label class="flex items-center space-x-2 cursor-pointer">
51
+ <input type="radio" name="watermarkType" value="image" class="text-primary">
52
+ <span>Image Watermark</span>
53
+ </label>
54
+ </div>
55
 
56
+ <div id="textOptions">
57
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
58
+ <div>
59
+ <label class="block text-sm font-medium mb-2">Watermark Text</label>
60
+ <input type="text" id="watermarkText" placeholder="Enter your watermark text" value="https://poe.com/Add-Video-Watermark" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 focus:ring-2 focus:ring-primary focus:border-primary">
61
+ </div>
62
+ <div>
63
+ <label class="block text-sm font-medium mb-2">Font Size</label>
64
+ <input type="range" id="fontSize" min="12" max="72" value="24" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
65
+ <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
66
+ <span id="fontSizeValue">24</span>px
67
+ </div>
68
+ </div>
69
+ <div>
70
+ <label class="block text-sm font-medium mb-2">Text Color</label>
71
+ <input type="color" id="textColor" value="#ffffff" class="w-16 h-10 border border-gray-300 dark:border-gray-600 rounded cursor-pointer">
72
+ </div>
73
+ <div>
74
+ <label class="block text-sm font-medium mb-2">Opacity</label>
75
+ <input type="range" id="textOpacity" min="0.1" max="1" step="0.1" value="0.8" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
76
+ <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
77
+ <span id="textOpacityValue">80</span>%
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <div id="imageOptions" class="hidden">
84
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
85
+ <div>
86
+ <label class="block text-sm font-medium mb-2">Watermark Image</label>
87
+ <input type="file" id="imageInput" accept="image/png,image/jpg,image/jpeg" class="w-full px-3 py-2 text-base border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 focus:ring-2 focus:ring-primary focus:border-primary">
88
+ <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">PNG recommended for transparency</p>
89
+ </div>
90
+ <div>
91
+ <label class="block text-sm font-medium mb-2">Size (%)</label>
92
+ <input type="range" id="imageSize" min="5" max="50" value="15" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
93
+ <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
94
+ <span id="imageSizeValue">15</span>%
95
+ </div>
96
+ </div>
97
+ <div>
98
+ <label class="block text-sm font-medium mb-2">Opacity</label>
99
+ <input type="range" id="imageOpacity" min="0.1" max="1" step="0.1" value="0.8" class="w-full h-2 bg-gray-200 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer">
100
+ <div class="text-sm text-gray-500 dark:text-gray-400 text-center mt-1">
101
+ <span id="imageOpacityValue">80</span>%
102
+ </div>
103
+ </div>
104
+ </div>
105
  </div>
106
  </div>
 
107
 
108
+ <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 mb-6" id="previewSection" style="display: none;">
109
+ <h2 class="text-xl font-semibold mb-4">3. Position Preview</h2>
110
+ <div class="relative max-w-4xl mx-auto">
111
+ <video id="previewVideo" class="w-full rounded-lg shadow-lg" controls crossorigin="anonymous"></video>
112
+ <canvas id="previewCanvas" class="absolute top-0 left-0 w-full h-full pointer-events-none rounded-lg"></canvas>
113
+ </div>
114
+ <div class="mt-4 text-center">
115
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Click on the video to position your watermark</p>
116
+ <div class="flex flex-wrap justify-center gap-2">
117
+ <button onclick="setPosition('top-left')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Top Left</button>
118
+ <button onclick="setPosition('top-right')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Top Right</button>
119
+ <button onclick="setPosition('bottom-left')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Bottom Left</button>
120
+ <button onclick="setPosition('bottom-right')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Bottom Right</button>
121
+ <button onclick="setPosition('center')" class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">Center</button>
122
+ </div>
123
+ </div>
124
  </div>
125
+
126
+ <div class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6" id="processSection" style="display: none;">
127
+ <h2 class="text-xl font-semibold mb-4">4. Process Video</h2>
128
+
129
+ <!-- Export Format Selection -->
130
+ <div class="mb-6">
131
+ <h3 class="text-lg font-medium mb-3">Export Format</h3>
132
+ <div class="flex flex-wrap gap-4">
133
+ <label class="flex items-center space-x-2 cursor-pointer">
134
+ <input type="radio" name="exportFormat" value="webm" class="text-primary" checked>
135
+ <span>WebM (Free)</span>
136
+ </label>
137
+ <label class="flex items-center space-x-2 cursor-pointer" id="mp4Option">
138
+ <input type="radio" name="exportFormat" value="mp4" class="text-primary" disabled>
139
+ <span class="text-gray-400">MP4 (Premium - Unlock Required)</span>
140
+ </label>
141
+ </div>
142
+ <div id="unlockSection" class="mt-4 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
143
+ <div class="flex items-center justify-between">
144
+ <div>
145
+ <p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">Unlock MP4 Export</p>
146
+ <p class="text-xs text-yellow-600 dark:text-yellow-300">Get premium features with @Cheap-Unlock-Bot</p>
147
+ </div>
148
+ <button id="unlockBtn" onclick="unlockMP4Feature()" class="bg-yellow-600 text-white px-4 py-2 rounded hover:bg-yellow-700 transition-colors text-sm">
149
+ Unlock MP4
150
+ </button>
151
+ </div>
152
+ <div id="unlockStatus" class="mt-2 hidden">
153
+ <p class="text-sm text-yellow-700 dark:text-yellow-300">Contacting @Cheap-Unlock-Bot...</p>
154
+ </div>
155
+ </div>
156
+ <div id="unlockedSection" class="mt-4 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg hidden">
157
+ <div class="flex items-center">
158
+ <svg class="w-5 h-5 text-green-600 dark:text-green-400 mr-2" fill="currentColor" viewBox="0 0 20 20">
159
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
160
+ </svg>
161
+ <p class="text-sm font-medium text-green-800 dark:text-green-200">MP4 Export Unlocked!</p>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <div class="text-center">
167
+ <button id="processBtn" onclick="processVideo()" class="bg-primary text-white px-8 py-3 rounded-lg hover:bg-purple-600 transition-colors text-lg font-medium">
168
+ Add Watermark for free
169
+ </button>
170
+ <div id="progressContainer" class="mt-4 hidden">
171
+ <div class="bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
172
+ <div id="progressBar" class="bg-primary h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
173
+ </div>
174
+ <p id="progressText" class="text-sm text-gray-600 dark:text-gray-400">Starting...</p>
175
+ </div>
176
+ <div id="downloadContainer" class="mt-4 hidden">
177
+ <a id="downloadLink" class="inline-block bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors">
178
+ Download Watermarked Video
179
+ </a>
180
+ </div>
181
+ </div>
182
  </div>
183
  </div>
184
+
185
+ <div id="modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
186
+ <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full mx-4">
187
+ <p id="modalText" class="text-gray-700 dark:text-gray-300 mb-4"></p>
188
+ <div class="flex justify-end">
189
+ <button onclick="closeModal()" class="px-4 py-2 bg-primary text-white hover:bg-purple-600 rounded">OK</button>
190
+ </div>
 
 
 
 
 
 
 
 
191
  </div>
192
  </div>
193
+
194
+ <script>
195
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
196
+ document.documentElement.classList.add('dark');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
198
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
199
+ if (event.matches) {
200
+ document.documentElement.classList.add('dark');
201
+ } else {
202
+ document.documentElement.classList.remove('dark');
203
+ }
204
+ });
205
+
206
+ let currentVideo = null;
207
+ let watermarkPosition = { x: 50, y: 50 };
208
+ let watermarkImage = null;
209
+ let previewCanvas = null;
210
+ let previewCtx = null;
211
+ let mp4Unlocked = false;
212
+
213
+ function showModal(message) {
214
+ document.getElementById('modalText').textContent = message;
215
+ document.getElementById('modal').classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  }
217
+
218
+ function closeModal() {
219
+ document.getElementById('modal').classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
221
+
222
+ // Register handler for unlock bot response
223
+ if (window.Poe && window.Poe.registerHandler) {
224
+ window.Poe.registerHandler("unlock-handler", (result, context) => {
225
+ const unlockStatus = document.getElementById('unlockStatus');
226
+ const unlockBtn = document.getElementById('unlockBtn');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ if (result.responses && result.responses.length > 0) {
229
+ const response = result.responses[0];
 
230
 
231
+ if (response.status === "complete") {
232
+ const responseText = response.content.toLowerCase();
233
+
234
+ if (responseText.includes('cheapunl0ck')) {
235
+ // Unlock successful
236
+ mp4Unlocked = true;
237
+ document.getElementById('unlockSection').classList.add('hidden');
238
+ document.getElementById('unlockedSection').classList.remove('hidden');
239
+
240
+ const mp4Radio = document.querySelector('input[name="exportFormat"][value="mp4"]');
241
+ const mp4Label = mp4Radio.parentElement.querySelector('span');
242
+ mp4Radio.disabled = false;
243
+ mp4Label.textContent = 'MP4 (Premium)';
244
+ mp4Label.classList.remove('text-gray-400');
245
+ mp4Label.classList.add('text-gray-900', 'dark:text-gray-100');
246
+
247
+ unlockStatus.innerHTML = '<p class="text-sm text-green-700 dark:text-green-300">✅ Successfully unlocked MP4 export!</p>';
248
+ } else {
249
+ // Unlock failed
250
+ unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Unlock failed. Please try again.</p>';
251
+ unlockBtn.disabled = false;
252
+ unlockBtn.textContent = 'Unlock MP4';
253
+ }
254
+ } else if (response.status === "error") {
255
+ unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Error contacting unlock bot.</p>';
256
+ unlockBtn.disabled = false;
257
+ unlockBtn.textContent = 'Unlock MP4';
258
+ }
259
+ } else {
260
+ unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ No response from unlock bot.</p>';
261
+ unlockBtn.disabled = false;
262
+ unlockBtn.textContent = 'Unlock MP4';
263
+ }
264
+ });
265
+ }
266
+
267
+ async function unlockMP4Feature() {
268
+ const unlockBtn = document.getElementById('unlockBtn');
269
+ const unlockStatus = document.getElementById('unlockStatus');
270
+
271
+ if (!window.Poe || !window.Poe.sendUserMessage) {
272
+ showModal('Poe API not available. MP4 unlock feature requires the Poe environment.');
273
+ return;
274
+ }
275
+
276
+ unlockBtn.disabled = true;
277
+ unlockBtn.textContent = 'Unlocking...';
278
+ unlockStatus.classList.remove('hidden');
279
+
280
+ try {
281
+ await window.Poe.sendUserMessage(
282
+ "@Cheap-Unlock-Bot I want to unlock MP4 export feature for Video Watermarker",
283
+ {
284
+ handler: "unlock-handler",
285
+ stream: false,
286
+ openChat: false
287
+ }
288
+ );
289
+ } catch (error) {
290
+ console.error('Error contacting unlock bot:', error);
291
+ unlockStatus.innerHTML = '<p class="text-sm text-red-700 dark:text-red-300">❌ Failed to contact unlock bot.</p>';
292
+ unlockBtn.disabled = false;
293
+ unlockBtn.textContent = 'Unlock MP4';
294
+ }
295
+ }
296
+
297
+ document.getElementById('videoInput').addEventListener('change', function(e) {
298
+ const file = e.target.files[0];
299
+ if (file) {
300
+ currentVideo = file;
301
+ const url = URL.createObjectURL(file);
302
+ const video = document.getElementById('previewVideo');
303
+ video.src = url;
304
+
305
+ document.getElementById('videoInfo').classList.remove('hidden');
306
+ document.getElementById('videoInfo').querySelector('p').textContent =
307
+ `Selected: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`;
308
+
309
+ document.getElementById('watermarkSection').style.display = 'block';
310
+ document.getElementById('previewSection').style.display = 'block';
311
+ document.getElementById('processSection').style.display = 'block';
312
+
313
+ previewCanvas = document.getElementById('previewCanvas');
314
+ previewCtx = previewCanvas.getContext('2d');
315
+
316
+ video.addEventListener('click', function(e) {
317
+ const rect = video.getBoundingClientRect();
318
+ const x = ((e.clientX - rect.left) / rect.width) * 100;
319
+ const y = ((e.clientY - rect.top) / rect.height) * 100;
320
+ watermarkPosition.x = Math.max(0, Math.min(95, x));
321
+ watermarkPosition.y = Math.max(0, Math.min(95, y));
322
+ drawWatermarkPreview();
323
+ });
324
+
325
+ video.addEventListener('loadedmetadata', function() {
326
+ resizeCanvas();
327
+ drawWatermarkPreview();
328
+ });
329
+
330
+ video.addEventListener('timeupdate', drawWatermarkPreview);
331
+ }
332
+ });
333
+
334
+ function resizeCanvas() {
335
+ const video = document.getElementById('previewVideo');
336
+ const canvas = document.getElementById('previewCanvas');
337
+ const rect = video.getBoundingClientRect();
338
+ canvas.width = rect.width;
339
+ canvas.height = rect.height;
340
+ canvas.style.width = rect.width + 'px';
341
+ canvas.style.height = rect.height + 'px';
342
+ }
343
+
344
+ document.addEventListener('change', function(e) {
345
+ if (e.target.name === 'watermarkType') {
346
+ const textOptions = document.getElementById('textOptions');
347
+ const imageOptions = document.getElementById('imageOptions');
348
+ if (e.target.value === 'text') {
349
+ textOptions.classList.remove('hidden');
350
+ imageOptions.classList.add('hidden');
351
  } else {
352
+ textOptions.classList.add('hidden');
353
+ imageOptions.classList.remove('hidden');
354
  }
355
+ drawWatermarkPreview();
356
  }
357
  });
358
 
359
+ document.getElementById('imageInput').addEventListener('change', function(e) {
360
+ const file = e.target.files[0];
361
+ if (file) {
362
+ const reader = new FileReader();
363
+ reader.onload = function(e) {
364
+ const img = new Image();
365
+ img.onload = function() {
366
+ watermarkImage = img;
367
+ drawWatermarkPreview();
368
+ };
369
+ img.src = e.target.result;
370
+ };
371
+ reader.readAsDataURL(file);
372
+ }
373
+ });
374
+
375
+ document.getElementById('fontSize').addEventListener('input', function(e) {
376
+ document.getElementById('fontSizeValue').textContent = e.target.value;
377
+ drawWatermarkPreview();
378
+ });
379
+
380
+ document.getElementById('textOpacity').addEventListener('input', function(e) {
381
+ document.getElementById('textOpacityValue').textContent = Math.round(e.target.value * 100);
382
+ drawWatermarkPreview();
383
+ });
384
+
385
+ document.getElementById('imageSize').addEventListener('input', function(e) {
386
+ document.getElementById('imageSizeValue').textContent = e.target.value;
387
+ drawWatermarkPreview();
388
+ });
389
+
390
+ document.getElementById('imageOpacity').addEventListener('input', function(e) {
391
+ document.getElementById('imageOpacityValue').textContent = Math.round(e.target.value * 100);
392
+ drawWatermarkPreview();
393
+ });
394
+
395
+ document.getElementById('watermarkText').addEventListener('input', drawWatermarkPreview);
396
+ document.getElementById('textColor').addEventListener('input', drawWatermarkPreview);
397
+
398
+ function setPosition(position) {
399
+ switch(position) {
400
+ case 'top-left':
401
+ watermarkPosition = { x: 5, y: 5 };
402
+ break;
403
+ case 'top-right':
404
+ watermarkPosition = { x: 85, y: 5 };
405
+ break;
406
+ case 'bottom-left':
407
+ watermarkPosition = { x: 5, y: 85 };
408
+ break;
409
+ case 'bottom-right':
410
+ watermarkPosition = { x: 85, y: 85 };
411
+ break;
412
+ case 'center':
413
+ watermarkPosition = { x: 45, y: 45 };
414
+ break;
415
  }
416
+ drawWatermarkPreview();
417
+ }
418
+
419
+ function drawWatermarkPreview() {
420
+ if (!previewCtx) return;
421
+
422
+ const canvas = previewCanvas;
423
+ const ctx = previewCtx;
424
+ const watermarkType = document.querySelector('input[name="watermarkType"]:checked');
425
+
426
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
427
+
428
+ if (!watermarkType) return;
429
+
430
+ const x = (watermarkPosition.x / 100) * canvas.width;
431
+ const y = (watermarkPosition.y / 100) * canvas.height;
432
+
433
+ if (watermarkType.value === 'text') {
434
+ const text = document.getElementById('watermarkText').value || 'Sample Text';
435
+ const fontSize = parseInt(document.getElementById('fontSize').value);
436
+ const color = document.getElementById('textColor').value;
437
+ const opacity = parseFloat(document.getElementById('textOpacity').value);
438
+
439
+ const scaledFontSize = fontSize * (canvas.width / 800);
440
+ ctx.font = `bold ${scaledFontSize}px Arial`;
441
+ ctx.fillStyle = color;
442
+ ctx.globalAlpha = opacity;
443
+ ctx.shadowColor = 'black';
444
+ ctx.shadowBlur = 4;
445
+ ctx.shadowOffsetX = 2;
446
+ ctx.shadowOffsetY = 2;
447
+ ctx.fillText(text, x, y + scaledFontSize);
448
+ ctx.shadowColor = 'transparent';
449
+ ctx.shadowBlur = 0;
450
+ ctx.shadowOffsetX = 0;
451
+ ctx.shadowOffsetY = 0;
452
+ ctx.globalAlpha = 1;
453
+ } else if (watermarkType.value === 'image' && watermarkImage) {
454
+ const size = parseInt(document.getElementById('imageSize').value);
455
+ const opacity = parseFloat(document.getElementById('imageOpacity').value);
456
+
457
+ const imgWidth = (size / 100) * canvas.width;
458
+ const imgHeight = (watermarkImage.height / watermarkImage.width) * imgWidth;
459
+
460
+ ctx.globalAlpha = opacity;
461
+ ctx.drawImage(watermarkImage, x, y, imgWidth, imgHeight);
462
+ ctx.globalAlpha = 1;
463
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
 
466
+ async function processVideo() {
467
+ if (!currentVideo) {
468
+ showModal('Please select a video file first.');
469
+ return;
470
+ }
471
+
472
+ const watermarkType = document.querySelector('input[name="watermarkType"]:checked');
473
+ if (!watermarkType) {
474
+ showModal('Please select a watermark type.');
475
+ return;
476
+ }
477
+
478
+ if (watermarkType.value === 'text' && !document.getElementById('watermarkText').value) {
479
+ showModal('Please enter watermark text.');
480
+ return;
481
+ }
482
+
483
+ if (watermarkType.value === 'image' && !watermarkImage) {
484
+ showModal('Please select a watermark image.');
485
+ return;
486
+ }
487
+
488
+ const exportFormat = document.querySelector('input[name="exportFormat"]:checked');
489
+ if (!exportFormat) {
490
+ showModal('Please select an export format.');
491
+ return;
492
+ }
493
+
494
+ if (exportFormat.value === 'mp4' && !mp4Unlocked) {
495
+ showModal('Please unlock MP4 export first using the unlock button.');
496
+ return;
497
+ }
498
+
499
+ const processBtn = document.getElementById('processBtn');
500
+ const progressContainer = document.getElementById('progressContainer');
501
+ const progressBar = document.getElementById('progressBar');
502
+ const progressText = document.getElementById('progressText');
503
+
504
+ processBtn.disabled = true;
505
+ processBtn.textContent = 'Processing...';
506
+ progressContainer.classList.remove('hidden');
507
+
508
+ try {
509
+ progressText.textContent = 'Preparing video...';
510
+ progressBar.style.width = '10%';
511
+
512
+ const video = document.createElement('video');
513
+ video.src = URL.createObjectURL(currentVideo);
514
+ video.crossOrigin = 'anonymous';
515
+
516
+ await new Promise((resolve) => {
517
+ video.addEventListener('loadedmetadata', resolve);
518
+ video.load();
519
+ });
520
+
521
+ progressText.textContent = 'Setting up recording...';
522
+ progressBar.style.width = '20%';
523
+
524
+ const canvas = document.createElement('canvas');
525
+ const ctx = canvas.getContext('2d');
526
+ canvas.width = video.videoWidth;
527
+ canvas.height = video.videoHeight;
528
+
529
+ const videoStream = canvas.captureStream(30);
530
+ let combinedStream = videoStream;
531
+
532
+ try {
533
+ const originalStream = video.captureStream();
534
+ const audioTracks = originalStream.getAudioTracks();
535
+ if (audioTracks.length > 0) {
536
+ combinedStream = new MediaStream([
537
+ ...videoStream.getVideoTracks(),
538
+ ...audioTracks
539
+ ]);
540
+ progressText.textContent = 'Recording with audio...';
541
+ } else {
542
+ progressText.textContent = 'Recording video (no audio track found)...';
543
+ }
544
+ } catch (audioError) {
545
+ console.warn('Could not capture audio:', audioError);
546
+ progressText.textContent = 'Recording video (audio capture failed)...';
547
+ }
548
+
549
+ // Determine MIME type based on selected format
550
+ let mimeType;
551
+ let fileExtension;
552
+
553
+ if (exportFormat.value === 'mp4') {
554
+ // Try MP4 format if unlocked
555
+ if (MediaRecorder.isTypeSupported('video/mp4;codecs=h264')) {
556
+ mimeType = 'video/mp4;codecs=h264';
557
+ fileExtension = 'mp4';
558
+ } else if (MediaRecorder.isTypeSupported('video/mp4')) {
559
+ mimeType = 'video/mp4';
560
+ fileExtension = 'mp4';
561
+ } else {
562
+ // Fall back to WebM if MP4 not supported
563
+ mimeType = 'video/webm;codecs=vp9';
564
+ fileExtension = 'webm';
565
+ progressText.textContent = 'MP4 not supported by browser, using WebM...';
566
+ }
567
+ } else {
568
+ // Default to WebM
569
+ mimeType = 'video/webm;codecs=vp9';
570
+ fileExtension = 'webm';
571
+ }
572
+
573
+ const recorder = new MediaRecorder(combinedStream, { mimeType });
574
+ const chunks = [];
575
+ recorder.ondataavailable = (e) => chunks.push(e.data);
576
+
577
+ progressBar.style.width = '40%';
578
+ recorder.start();
579
+ video.play();
580
+
581
+ const drawFrame = () => {
582
+ if (video.ended || video.paused) {
583
+ recorder.stop();
584
+ return;
585
+ }
586
+
587
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
588
+
589
+ const x = (watermarkPosition.x / 100) * canvas.width;
590
+ const y = (watermarkPosition.y / 100) * canvas.height;
591
+
592
+ if (watermarkType.value === 'text') {
593
+ const text = document.getElementById('watermarkText').value;
594
+ const fontSize = parseInt(document.getElementById('fontSize').value);
595
+ const color = document.getElementById('textColor').value;
596
+ const opacity = parseFloat(document.getElementById('textOpacity').value);
597
+
598
+ const scaledFontSize = fontSize * (canvas.width / 800);
599
+ ctx.font = `bold ${scaledFontSize}px Arial`;
600
+ ctx.fillStyle = color;
601
+ ctx.globalAlpha = opacity;
602
+ ctx.shadowColor = 'black';
603
+ ctx.shadowBlur = 4;
604
+ ctx.shadowOffsetX = 2;
605
+ ctx.shadowOffsetY = 2;
606
+ ctx.fillText(text, x, y + scaledFontSize);
607
+ ctx.shadowColor = 'transparent';
608
+ ctx.shadowBlur = 0;
609
+ ctx.shadowOffsetX = 0;
610
+ ctx.shadowOffsetY = 0;
611
+ ctx.globalAlpha = 1;
612
+ } else if (watermarkType.value === 'image' && watermarkImage) {
613
+ const size = parseInt(document.getElementById('imageSize').value);
614
+ const opacity = parseFloat(document.getElementById('imageOpacity').value);
615
+
616
+ const imgWidth = (size / 100) * canvas.width;
617
+ const imgHeight = (watermarkImage.height / watermarkImage.width) * imgWidth;
618
+
619
+ ctx.globalAlpha = opacity;
620
+ ctx.drawImage(watermarkImage, x, y, imgWidth, imgHeight);
621
+ ctx.globalAlpha = 1;
622
+ }
623
+
624
+ const progress = (video.currentTime / video.duration) * 50;
625
+ progressBar.style.width = (40 + progress) + '%';
626
+ requestAnimationFrame(drawFrame);
627
+ };
628
+
629
+ drawFrame();
630
+
631
+ recorder.onstop = () => {
632
+ progressText.textContent = 'Finalizing video...';
633
+ progressBar.style.width = '95%';
634
+
635
+ const blob = new Blob(chunks, { type: mimeType });
636
+ const url = URL.createObjectURL(blob);
637
+
638
+ progressText.textContent = 'Complete!';
639
+ progressBar.style.width = '100%';
640
+
641
+ const downloadLink = document.getElementById('downloadLink');
642
+ downloadLink.href = url;
643
+ downloadLink.download = `watermarked_${currentVideo.name.replace(/\.[^/.]+$/, '')}.${fileExtension}`;
644
+ document.getElementById('downloadContainer').classList.remove('hidden');
645
+ };
646
+
647
+ } catch (error) {
648
+ console.error('Processing error:', error);
649
+ showModal('Error processing video: ' + error.message);
650
+ } finally {
651
+ processBtn.disabled = false;
652
+ processBtn.textContent = 'Add Watermark & Download';
653
+ }
654
+ }
655
+
656
+ window.addEventListener('resize', () => {
657
+ if (previewCanvas) {
658
+ setTimeout(() => {
659
+ resizeCanvas();
660
+ drawWatermarkPreview();
661
+ }, 100);
662
+ }
663
+ });
664
+ </script>
665
  </html>