Spaces:
Paused
Paused
Update index.html
Browse files- index.html +44 -115
index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
| 9 |
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;900&display=swap" rel="stylesheet">
|
| 10 |
|
| 11 |
<style>
|
| 12 |
-
/*
|
| 13 |
* {
|
| 14 |
font-family: 'Nunito', sans-serif;
|
| 15 |
box-sizing: border-box;
|
|
@@ -61,6 +61,7 @@
|
|
| 61 |
overflow-y: auto;
|
| 62 |
}
|
| 63 |
|
|
|
|
| 64 |
.controls::-webkit-scrollbar { width: 8px; }
|
| 65 |
.controls::-webkit-scrollbar-track { background: #222; border-radius: 4px; }
|
| 66 |
.controls::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }
|
|
@@ -148,6 +149,7 @@
|
|
| 148 |
margin-top: 5px;
|
| 149 |
}
|
| 150 |
|
|
|
|
| 151 |
.disclaimer-link {
|
| 152 |
text-align: center;
|
| 153 |
margin-top: 10px;
|
|
@@ -165,8 +167,9 @@
|
|
| 165 |
color: #999;
|
| 166 |
}
|
| 167 |
|
|
|
|
| 168 |
.modal-overlay {
|
| 169 |
-
display: none;
|
| 170 |
position: fixed;
|
| 171 |
top: 0;
|
| 172 |
left: 0;
|
|
@@ -228,6 +231,7 @@
|
|
| 228 |
font-weight: 900;
|
| 229 |
}
|
| 230 |
|
|
|
|
| 231 |
.legal-text::-webkit-scrollbar { width: 6px; }
|
| 232 |
.legal-text::-webkit-scrollbar-track { background: #222; }
|
| 233 |
.legal-text::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; }
|
|
@@ -250,28 +254,32 @@
|
|
| 250 |
</head>
|
| 251 |
<body>
|
| 252 |
|
| 253 |
-
<!-- 2. Убрано PRO -->
|
| 254 |
<h1>MandreIcon Creator</h1>
|
| 255 |
|
| 256 |
<div class="container">
|
|
|
|
| 257 |
<div class="preview-area">
|
| 258 |
<canvas id="mainCanvas" width="512" height="512"></canvas>
|
| 259 |
<p class="hint">Кликни на стикер для выделения. <br>Delete = удалить.</p>
|
| 260 |
</div>
|
| 261 |
|
|
|
|
| 262 |
<div class="controls">
|
| 263 |
|
| 264 |
<div class="section-title">Фон (Base Icon)</div>
|
| 265 |
|
|
|
|
| 266 |
<div class="drop-zone" id="dropZoneBase">
|
| 267 |
<p><b>SVG</b> (Градиентный фон)<br>Клик или Drop</p>
|
| 268 |
<input type="file" id="fileInputBase" class="hidden-input" accept=".svg">
|
| 269 |
</div>
|
| 270 |
|
|
|
|
| 271 |
<div>
|
| 272 |
<input type="text" id="textInput" placeholder="Или текст (A)">
|
| 273 |
</div>
|
| 274 |
|
|
|
|
| 275 |
<div>
|
| 276 |
<label>Размер хуйни этой: <span id="scaleVal">100%</span></label>
|
| 277 |
<input type="range" id="scaleRange" min="10" max="200" value="100">
|
|
@@ -294,6 +302,7 @@
|
|
| 294 |
|
| 295 |
<button class="btn-primary" id="downloadBtn">Скачать PNG</button>
|
| 296 |
|
|
|
|
| 297 |
<div class="disclaimer-link">
|
| 298 |
<span id="openDisclaimerBtn">Отказ от ответственности</span>
|
| 299 |
</div>
|
|
@@ -319,7 +328,6 @@
|
|
| 319 |
<p><strong>4. Технические ограничения</strong><br>
|
| 320 |
Разработчик не гарантирует бесперебойную работу сайта, отсутствие программных ошибок или полную совместимость с конкретными устройствами и браузерами.</p>
|
| 321 |
|
| 322 |
-
<!-- 3. Добавлен пункт о законодательстве стран -->
|
| 323 |
<p><strong>5. Различия в законодательстве стран</strong><br>
|
| 324 |
Сайт доступен пользователям по всему миру. Пользователь признает, что законы, регулирующие авторское право, использование символики и распространение контента, могут существенно различаться в зависимости от юрисдикции. Пользователь самостоятельно несет ответственность за соблюдение законодательства страны своего проживания, а также законодательства стран, на территории которых планируется использование созданных материалов. Разработчик не несет ответственности за нарушение пользователем локальных законов.</p>
|
| 325 |
|
|
@@ -333,6 +341,7 @@
|
|
| 333 |
const canvas = document.getElementById('mainCanvas');
|
| 334 |
const ctx = canvas.getContext('2d');
|
| 335 |
|
|
|
|
| 336 |
const dropZoneBase = document.getElementById('dropZoneBase');
|
| 337 |
const fileInputBase = document.getElementById('fileInputBase');
|
| 338 |
const textInput = document.getElementById('textInput');
|
|
@@ -340,17 +349,29 @@
|
|
| 340 |
const offsetYRange = document.getElementById('offsetYRange');
|
| 341 |
const offsetXRange = document.getElementById('offsetXRange');
|
| 342 |
const downloadBtn = document.getElementById('downloadBtn');
|
|
|
|
| 343 |
const addStickerBtn = document.getElementById('addStickerBtn');
|
| 344 |
const stickerInput = document.getElementById('stickerInput');
|
| 345 |
|
|
|
|
| 346 |
const BG_COLOR = '#E13839';
|
| 347 |
const GRADIENT_START = '#FFFFFF';
|
| 348 |
const GRADIENT_END = '#FFACC7';
|
| 349 |
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
let stickers = [];
|
| 352 |
let selectedStickerIndex = -1;
|
| 353 |
|
|
|
|
| 354 |
let isDragging = false;
|
| 355 |
let isRotating = false;
|
| 356 |
let isResizing = false;
|
|
@@ -359,111 +380,13 @@
|
|
| 359 |
let initialDistance = 0;
|
| 360 |
let initialScale = 1;
|
| 361 |
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
// ==========================================
|
| 367 |
-
|
| 368 |
-
// Таблица для CRC32
|
| 369 |
-
const crcTable = [];
|
| 370 |
-
for (let n = 0; n < 256; n++) {
|
| 371 |
-
let c = n;
|
| 372 |
-
for (let k = 0; k < 8; k++) {
|
| 373 |
-
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
| 374 |
-
}
|
| 375 |
-
crcTable[n] = c;
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
function crc32(buf) {
|
| 379 |
-
let crc = 0xFFFFFFFF;
|
| 380 |
-
for (let i = 0; i < buf.length; i++) {
|
| 381 |
-
crc = (crc >>> 8) ^ crcTable[(crc ^ buf[i]) & 0xFF];
|
| 382 |
-
}
|
| 383 |
-
return crc ^ 0xFFFFFFFF;
|
| 384 |
-
}
|
| 385 |
-
|
| 386 |
-
// Функция создания чанка
|
| 387 |
-
function createChunk(type, data) {
|
| 388 |
-
const len = data.length;
|
| 389 |
-
const buf = new Uint8Array(4 + 4 + len + 4);
|
| 390 |
-
const view = new DataView(buf.buffer);
|
| 391 |
-
|
| 392 |
-
view.setUint32(0, len); // Length
|
| 393 |
-
|
| 394 |
-
// Type
|
| 395 |
-
buf[4] = type.charCodeAt(0);
|
| 396 |
-
buf[5] = type.charCodeAt(1);
|
| 397 |
-
buf[6] = type.charCodeAt(2);
|
| 398 |
-
buf[7] = type.charCodeAt(3);
|
| 399 |
-
|
| 400 |
-
// Data
|
| 401 |
-
buf.set(data, 8);
|
| 402 |
-
|
| 403 |
-
// CRC (Type + Data)
|
| 404 |
-
const crc = crc32(buf.subarray(4, 8 + len));
|
| 405 |
-
view.setUint32(8 + len, crc);
|
| 406 |
-
|
| 407 |
-
return buf;
|
| 408 |
-
}
|
| 409 |
-
|
| 410 |
-
function createTextChunk(keyword, text) {
|
| 411 |
-
// tEXt поддерживает Latin1, но современные вьюеры едят UTF8
|
| 412 |
-
const keywordArr = new TextEncoder().encode(keyword);
|
| 413 |
-
const textArr = new TextEncoder().encode(text);
|
| 414 |
-
|
| 415 |
-
// Keyword + Null separator + Text
|
| 416 |
-
const data = new Uint8Array(keywordArr.length + 1 + textArr.length);
|
| 417 |
-
data.set(keywordArr, 0);
|
| 418 |
-
data[keywordArr.length] = 0; // Null separator
|
| 419 |
-
data.set(textArr, keywordArr.length + 1);
|
| 420 |
-
|
| 421 |
-
return createChunk("tEXt", data);
|
| 422 |
-
}
|
| 423 |
-
|
| 424 |
-
async function downloadWithMetadata(filename) {
|
| 425 |
-
// 1. Получаем сырой Blob из канваса
|
| 426 |
-
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
|
| 427 |
-
const buffer = await blob.arrayBuffer();
|
| 428 |
-
const uint8 = new Uint8Array(buffer);
|
| 429 |
-
|
| 430 |
-
// 2. PNG структура: Signature (8 bytes) + IHDR Chunk (25 bytes) + ...
|
| 431 |
-
// Мы вставим метаданные сразу после IHDR (индекс 33)
|
| 432 |
-
const insertPos = 33;
|
| 433 |
-
|
| 434 |
-
// 3. Генерируем чанки метаданных
|
| 435 |
-
const chunkAuthor = createTextChunk("Author", "@sterepando");
|
| 436 |
-
const chunkComment = createTextChunk("Comment", "сделанно плагином @swagnonher");
|
| 437 |
-
// Для надежности дублируем в Description
|
| 438 |
-
const chunkDesc = createTextChunk("Description", "сделанно плагином @swagnonher & @sterepando");
|
| 439 |
-
|
| 440 |
-
// 4. Собираем новый файл
|
| 441 |
-
const newFileSize = uint8.length + chunkAuthor.length + chunkComment.length + chunkDesc.length;
|
| 442 |
-
const newBuf = new Uint8Array(newFileSize);
|
| 443 |
-
|
| 444 |
-
// Копируем заголовок (Sig + IHDR)
|
| 445 |
-
newBuf.set(uint8.subarray(0, insertPos), 0);
|
| 446 |
-
|
| 447 |
-
// Вставляем метаданные
|
| 448 |
-
let offset = insertPos;
|
| 449 |
-
newBuf.set(chunkAuthor, offset); offset += chunkAuthor.length;
|
| 450 |
-
newBuf.set(chunkComment, offset); offset += chunkComment.length;
|
| 451 |
-
newBuf.set(chunkDesc, offset); offset += chunkDesc.length;
|
| 452 |
-
|
| 453 |
-
// Копируем остаток файла
|
| 454 |
-
newBuf.set(uint8.subarray(insertPos), offset);
|
| 455 |
-
|
| 456 |
-
// 5. Скачиваем
|
| 457 |
-
const newBlob = new Blob([newBuf], { type: 'image/png' });
|
| 458 |
-
const link = document.createElement('a');
|
| 459 |
-
link.download = filename;
|
| 460 |
-
link.href = URL.createObjectURL(newBlob);
|
| 461 |
-
link.click();
|
| 462 |
-
URL.revokeObjectURL(link.href);
|
| 463 |
-
}
|
| 464 |
|
| 465 |
// ==========================================
|
| 466 |
-
//
|
| 467 |
// ==========================================
|
| 468 |
const modalOverlay = document.getElementById('modalOverlay');
|
| 469 |
const openDisclaimerBtn = document.getElementById('openDisclaimerBtn');
|
|
@@ -490,7 +413,7 @@
|
|
| 490 |
|
| 491 |
|
| 492 |
// ==========================================
|
| 493 |
-
// ЛОГИКА
|
| 494 |
// ==========================================
|
| 495 |
|
| 496 |
function updateBaseLayerParams() {
|
|
@@ -548,7 +471,7 @@
|
|
| 548 |
|
| 549 |
|
| 550 |
// ==========================================
|
| 551 |
-
// ЛОГИКА СТИКЕРОВ
|
| 552 |
// ==========================================
|
| 553 |
|
| 554 |
addStickerBtn.addEventListener('click', () => stickerInput.click());
|
|
@@ -602,19 +525,22 @@
|
|
| 602 |
|
| 603 |
|
| 604 |
// ==========================================
|
| 605 |
-
// ОТРИСОВКА
|
| 606 |
// ==========================================
|
| 607 |
|
| 608 |
function drawAll() {
|
| 609 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 610 |
|
|
|
|
| 611 |
ctx.fillStyle = BG_COLOR;
|
| 612 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 613 |
|
|
|
|
| 614 |
if (baseLayer.type !== 'none') {
|
| 615 |
drawBaseLayer();
|
| 616 |
}
|
| 617 |
|
|
|
|
| 618 |
stickers.forEach((sticker, index) => {
|
| 619 |
ctx.save();
|
| 620 |
ctx.translate(sticker.x, sticker.y);
|
|
@@ -623,6 +549,7 @@
|
|
| 623 |
ctx.restore();
|
| 624 |
});
|
| 625 |
|
|
|
|
| 626 |
if (selectedStickerIndex !== -1) {
|
| 627 |
drawControls(stickers[selectedStickerIndex]);
|
| 628 |
}
|
|
@@ -701,7 +628,7 @@
|
|
| 701 |
|
| 702 |
|
| 703 |
// ==========================================
|
| 704 |
-
//
|
| 705 |
// ==========================================
|
| 706 |
|
| 707 |
function getMousePos(evt) {
|
|
@@ -852,15 +779,17 @@
|
|
| 852 |
});
|
| 853 |
|
| 854 |
// ==========================================
|
| 855 |
-
// СКАЧИВАНИЕ
|
| 856 |
// ==========================================
|
| 857 |
downloadBtn.addEventListener('click', () => {
|
| 858 |
const prevSelection = selectedStickerIndex;
|
| 859 |
selectedStickerIndex = -1;
|
| 860 |
drawAll();
|
| 861 |
|
| 862 |
-
|
| 863 |
-
|
|
|
|
|
|
|
| 864 |
|
| 865 |
selectedStickerIndex = prevSelection;
|
| 866 |
drawAll();
|
|
|
|
| 9 |
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;900&display=swap" rel="stylesheet">
|
| 10 |
|
| 11 |
<style>
|
| 12 |
+
/* Глобальное применение шрифта ко всем элементам */
|
| 13 |
* {
|
| 14 |
font-family: 'Nunito', sans-serif;
|
| 15 |
box-sizing: border-box;
|
|
|
|
| 61 |
overflow-y: auto;
|
| 62 |
}
|
| 63 |
|
| 64 |
+
/* Скроллбар для контролов */
|
| 65 |
.controls::-webkit-scrollbar { width: 8px; }
|
| 66 |
.controls::-webkit-scrollbar-track { background: #222; border-radius: 4px; }
|
| 67 |
.controls::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }
|
|
|
|
| 149 |
margin-top: 5px;
|
| 150 |
}
|
| 151 |
|
| 152 |
+
/* Стили для ссылки Disclaimer */
|
| 153 |
.disclaimer-link {
|
| 154 |
text-align: center;
|
| 155 |
margin-top: 10px;
|
|
|
|
| 167 |
color: #999;
|
| 168 |
}
|
| 169 |
|
| 170 |
+
/* Стили модального окна */
|
| 171 |
.modal-overlay {
|
| 172 |
+
display: none; /* Скрыто по умолчанию */
|
| 173 |
position: fixed;
|
| 174 |
top: 0;
|
| 175 |
left: 0;
|
|
|
|
| 231 |
font-weight: 900;
|
| 232 |
}
|
| 233 |
|
| 234 |
+
/* Скроллбар внутри модалки */
|
| 235 |
.legal-text::-webkit-scrollbar { width: 6px; }
|
| 236 |
.legal-text::-webkit-scrollbar-track { background: #222; }
|
| 237 |
.legal-text::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; }
|
|
|
|
| 254 |
</head>
|
| 255 |
<body>
|
| 256 |
|
|
|
|
| 257 |
<h1>MandreIcon Creator</h1>
|
| 258 |
|
| 259 |
<div class="container">
|
| 260 |
+
<!-- Область превью -->
|
| 261 |
<div class="preview-area">
|
| 262 |
<canvas id="mainCanvas" width="512" height="512"></canvas>
|
| 263 |
<p class="hint">Кликни на стикер для выделения. <br>Delete = удалить.</p>
|
| 264 |
</div>
|
| 265 |
|
| 266 |
+
<!-- Панель управления -->
|
| 267 |
<div class="controls">
|
| 268 |
|
| 269 |
<div class="section-title">Фон (Base Icon)</div>
|
| 270 |
|
| 271 |
+
<!-- Драг-н-дроп SVG -->
|
| 272 |
<div class="drop-zone" id="dropZoneBase">
|
| 273 |
<p><b>SVG</b> (Градиентный фон)<br>Клик или Drop</p>
|
| 274 |
<input type="file" id="fileInputBase" class="hidden-input" accept=".svg">
|
| 275 |
</div>
|
| 276 |
|
| 277 |
+
<!-- Текстовые настройки -->
|
| 278 |
<div>
|
| 279 |
<input type="text" id="textInput" placeholder="Или текст (A)">
|
| 280 |
</div>
|
| 281 |
|
| 282 |
+
<!-- Ползунки настроек фона -->
|
| 283 |
<div>
|
| 284 |
<label>Размер хуйни этой: <span id="scaleVal">100%</span></label>
|
| 285 |
<input type="range" id="scaleRange" min="10" max="200" value="100">
|
|
|
|
| 302 |
|
| 303 |
<button class="btn-primary" id="downloadBtn">Скачать PNG</button>
|
| 304 |
|
| 305 |
+
<!-- Кнопка вызова Disclaimer -->
|
| 306 |
<div class="disclaimer-link">
|
| 307 |
<span id="openDisclaimerBtn">Отказ от ответственности</span>
|
| 308 |
</div>
|
|
|
|
| 328 |
<p><strong>4. Технические ограничения</strong><br>
|
| 329 |
Разработчик не гарантирует бесперебойную работу сайта, отсутствие программных ошибок или полную совместимость с конкретными устройствами и браузерами.</p>
|
| 330 |
|
|
|
|
| 331 |
<p><strong>5. Различия в законодательстве стран</strong><br>
|
| 332 |
Сайт доступен пользователям по всему миру. Пользователь признает, что законы, регулирующие авторское право, использование символики и распространение контента, могут существенно различаться в зависимости от юрисдикции. Пользователь самостоятельно несет ответственность за соблюдение законодательства страны своего проживания, а также законодательства стран, на территории которых планируется использование созданных материалов. Разработчик не несет ответственности за нарушение пользователем локальных законов.</p>
|
| 333 |
|
|
|
|
| 341 |
const canvas = document.getElementById('mainCanvas');
|
| 342 |
const ctx = canvas.getContext('2d');
|
| 343 |
|
| 344 |
+
// Элементы UI
|
| 345 |
const dropZoneBase = document.getElementById('dropZoneBase');
|
| 346 |
const fileInputBase = document.getElementById('fileInputBase');
|
| 347 |
const textInput = document.getElementById('textInput');
|
|
|
|
| 349 |
const offsetYRange = document.getElementById('offsetYRange');
|
| 350 |
const offsetXRange = document.getElementById('offsetXRange');
|
| 351 |
const downloadBtn = document.getElementById('downloadBtn');
|
| 352 |
+
|
| 353 |
const addStickerBtn = document.getElementById('addStickerBtn');
|
| 354 |
const stickerInput = document.getElementById('stickerInput');
|
| 355 |
|
| 356 |
+
// Константы
|
| 357 |
const BG_COLOR = '#E13839';
|
| 358 |
const GRADIENT_START = '#FFFFFF';
|
| 359 |
const GRADIENT_END = '#FFACC7';
|
| 360 |
|
| 361 |
+
// Состояние "Базового слоя" (градиент)
|
| 362 |
+
let baseLayer = {
|
| 363 |
+
type: 'none', // 'image' | 'text' | 'none'
|
| 364 |
+
object: null,
|
| 365 |
+
scale: 1,
|
| 366 |
+
x: 0,
|
| 367 |
+
y: 0
|
| 368 |
+
};
|
| 369 |
+
|
| 370 |
+
// Состояние "Стикеров" (PNG поверх)
|
| 371 |
let stickers = [];
|
| 372 |
let selectedStickerIndex = -1;
|
| 373 |
|
| 374 |
+
// Переменные для манипуляции мышью
|
| 375 |
let isDragging = false;
|
| 376 |
let isRotating = false;
|
| 377 |
let isResizing = false;
|
|
|
|
| 380 |
let initialDistance = 0;
|
| 381 |
let initialScale = 1;
|
| 382 |
|
| 383 |
+
// Инициализация
|
| 384 |
+
window.onload = () => {
|
| 385 |
+
drawAll();
|
| 386 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
|
| 388 |
// ==========================================
|
| 389 |
+
// ЛОГИКА МОДАЛЬНОГО ОКНА
|
| 390 |
// ==========================================
|
| 391 |
const modalOverlay = document.getElementById('modalOverlay');
|
| 392 |
const openDisclaimerBtn = document.getElementById('openDisclaimerBtn');
|
|
|
|
| 413 |
|
| 414 |
|
| 415 |
// ==========================================
|
| 416 |
+
// 1. ЛОГИКА БАЗОВОГО СЛОЯ (SVG/TEXT)
|
| 417 |
// ==========================================
|
| 418 |
|
| 419 |
function updateBaseLayerParams() {
|
|
|
|
| 471 |
|
| 472 |
|
| 473 |
// ==========================================
|
| 474 |
+
// 2. ЛОГИКА СТИКЕРОВ (PNG)
|
| 475 |
// ==========================================
|
| 476 |
|
| 477 |
addStickerBtn.addEventListener('click', () => stickerInput.click());
|
|
|
|
| 525 |
|
| 526 |
|
| 527 |
// ==========================================
|
| 528 |
+
// 3. ГЛАВНАЯ ОТРИСОВКА
|
| 529 |
// ==========================================
|
| 530 |
|
| 531 |
function drawAll() {
|
| 532 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 533 |
|
| 534 |
+
// 1. Фон
|
| 535 |
ctx.fillStyle = BG_COLOR;
|
| 536 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 537 |
|
| 538 |
+
// 2. Базовый градиентный слой
|
| 539 |
if (baseLayer.type !== 'none') {
|
| 540 |
drawBaseLayer();
|
| 541 |
}
|
| 542 |
|
| 543 |
+
// 3. Стикеры
|
| 544 |
stickers.forEach((sticker, index) => {
|
| 545 |
ctx.save();
|
| 546 |
ctx.translate(sticker.x, sticker.y);
|
|
|
|
| 549 |
ctx.restore();
|
| 550 |
});
|
| 551 |
|
| 552 |
+
// 4. Интерфейс управления
|
| 553 |
if (selectedStickerIndex !== -1) {
|
| 554 |
drawControls(stickers[selectedStickerIndex]);
|
| 555 |
}
|
|
|
|
| 628 |
|
| 629 |
|
| 630 |
// ==========================================
|
| 631 |
+
// 4. ОБРАБОТКА МЫШИ (Canvas)
|
| 632 |
// ==========================================
|
| 633 |
|
| 634 |
function getMousePos(evt) {
|
|
|
|
| 779 |
});
|
| 780 |
|
| 781 |
// ==========================================
|
| 782 |
+
// 5. СКАЧИВАНИЕ
|
| 783 |
// ==========================================
|
| 784 |
downloadBtn.addEventListener('click', () => {
|
| 785 |
const prevSelection = selectedStickerIndex;
|
| 786 |
selectedStickerIndex = -1;
|
| 787 |
drawAll();
|
| 788 |
|
| 789 |
+
const link = document.createElement('a');
|
| 790 |
+
link.download = 'SWAGA_ICON.png'; // Убрано PRO
|
| 791 |
+
link.href = canvas.toDataURL('image/png');
|
| 792 |
+
link.click();
|
| 793 |
|
| 794 |
selectedStickerIndex = prevSelection;
|
| 795 |
drawAll();
|