1PROMTAI.RU

Мои промты для генерации изображений, музыки и т.д

Статьи

Теги

Вход

Новые записи

Общение

Архивы

Рубрики

Drawn Picture 2 — онлайн-рисовалка анимации улучшенная

Drawn Picture 2 — PRO-версия рисовалки

Это расширенная версия плагина Drawn Picture. Он ставится рядом с первым и не конфликтует с ним: у него отдельный класс, отдельный шорткод и отдельный префикс CSS. Активируешь оба — и на сайте можно использовать две рисовалки одновременно.

Что добавилось по сравнению с первым плагином

Кисти. Вместо одной универсальной кисти теперь четыре стиля: карандаш, маркер (полупрозрачная толстая линия), аэрозоль (разбросанные точки по контуру) и каллиграфия (толщина зависит от угла линии).

Иконки. Готовые пиктограммы, которые можно ставить на холст: смайл, дом, солнце, облако, ёлка, снежинка, сердечко, звезда и машина. Тянешь мышкой от угла к углу — иконка растягивается до нужного размера.

Градиенты. Для фигур, иконок и текста можно выбрать не один цвет, а переход от одного к другому. Два пикера рядом с палитрой, кнопка «Градиент», превью под ними.

Тени и свечение. Две кнопки в верхней панели. Если включить тень — у каждого элемента появляется тень с небольшим смещением. Если включить свечение — элементы начинают светиться текущим цветом.

Выделение и перемещение. Инструмент «Выделение» позволяет кликнуть по объекту и перетащить его мышкой. Так можно подвинуть штрих, фигуру, иконку или надпись, не перерисовывая.

Заливка по клику. Инструмент «Заливка» работает как в Paint: кликаешь внутри замкнутого контура — область закрашивается текущим цветом. Допуск цвета настроен так, чтобы линии не обязательно были идеально замкнуты.

Пипетка. Инструмент «Пипетка». Кликаешь по любому месту холста — цвет из этого пикселя становится текущим.

Текст с настройками. Вместо одного размера теперь выбор шрифта (системный, serif, моно, рукописный, округлый), кнопки жирный, курсив, подчёркнутый, обводка другим цветом, поворот на угол от минус 180 до 180, многострочный ввод через Shift+Enter.

Экспорт. Кроме PNG теперь ещё JPG, WebP, SVG и data-URL. SVG сохраняет векторное качество — фигуры и текст в нём редактируются, разрешение не теряется при увеличении. data-URL удобно вставлять прямо в HTML как источник картинки.

Иконки в копируемом скрипте. В первой версии копируемый скрипт не умел рисовать иконки — они попадали только в PNG и SVG. Здесь иконки добавлены в скрипт, так что если ты нарисовал что-то с иконками, при экспорте «Скопировать скрипт» всё перенесётся на другой сайт целиком.




<?php
/**
 * Plugin Name: Drawn Picture 2 — рисовалка PRO
 * Description: Шорткод [ drawn_picture2] выводит расширенную рисовалку: кисть, ластик, фигуры, иконки, текст, градиенты, тени, выделение и перемещение, заливка по клику, пипетка. Экспорт PNG/JPG/WebP/SVG/data-URL и копирование скрипта.
 * Version:     2.0.0
 * Author:      Ведерников Сергей и ИИ Дэп
 * License:     GPL-2.0-or-later
 */

if (!defined('ABSPATH')) exit;

class DrawnPicture2Plugin {

    const VERSION = '2.0.0';

    public function __construct() {
        add_shortcode('drawn_picture2',   [$this, 'renderShortcode']);
        add_action('wp_footer',           [$this, 'printAssets'], 99);
    }

    public function renderShortcode($atts) {
        $atts = shortcode_atts([
            'height' => 560,
            'class'  => '',
        ], $atts, 'drawn_picture2');

        $height = max(200, min(2000, intval($atts['height'])));
        $GLOBALS['drawn_picture2_used'] = true;

        ob_start();
        ?>
        <div class="drawn-picture2-app <?php echo esc_attr($atts['class']); ?>"
             data-height="<?php echo esc_attr($height); ?>"></div>
        <?php
        return ob_get_clean();
    }

    public function printAssets() {
        if (empty($GLOBALS['drawn_picture2_used'])) return;
        ?>
        <style>
            .drawn-picture2-app {
                width: 100%;
                max-width: 1200px;
                margin: 0 auto;
                padding: 14px;
                background: #fff;
                border: 1px solid #e2e4ea;
                border-radius: 14px;
                box-shadow: 0 6px 24px rgba(15, 23, 42, .06);
                font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
                color: #1f2937;
            }
            .drawn-picture2-app * { box-sizing: border-box; }

            .dp2-toolbar {
                display: flex;
                flex-wrap: wrap;
                gap: 10px 14px;
                align-items: center;
                padding: 10px 12px;
                border: 1px solid #eef0f5;
                border-radius: 10px;
                background: #fafbfd;
                margin-bottom: 10px;
            }
            .dp2-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
            .dp2-group label { font-size: 12px; color: #6b7280; }

            .dp2-color {
                width: 34px; height: 30px;
                border: 1px solid #d1d5db; border-radius: 8px;
                padding: 2px; background: #fff; cursor: pointer;
            }
            .dp2-swatches { display: flex; flex-wrap: wrap; gap: 4px; max-width: 220px; }
            .dp2-swatch {
                width: 20px; height: 20px;
                border-radius: 5px; border: 2px solid transparent;
                cursor: pointer; padding: 0;
            }
            .dp2-swatch.active { border-color: #2b6cff; }

            .dp2-range { width: 100px; }
            .dp2-select, .dp2-number, .dp2-text {
                padding: 5px 8px;
                border: 1px solid #d1d5db;
                border-radius: 8px;
                font: inherit;
                font-size: 13px;
                background: #fff;
            }
            .dp2-number { width: 62px; }

            .dp2-btn {
                font: inherit;
                font-size: 13px;
                padding: 6px 10px;
                border-radius: 8px;
                border: 1px solid transparent;
                background: #2b6cff;
                color: #fff;
                cursor: pointer;
            }
            .dp2-btn:hover { background: #1c56d6; }
            .dp2-btn.secondary { background: #fff; color: #1f2937; border-color: #d1d5db; }
            .dp2-btn.secondary:hover { background: #f3f4f6; }
            .dp2-btn.danger { background: #fff; color: #b91c1c; border-color: #fecaca; }
            .dp2-btn.danger:hover { background: #fef2f2; }
            .dp2-btn.active { background: #e3edff; border-color: #2b6cff; color: #1c4dbf; }

            .dp2-tool-row {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                padding: 8px 12px;
                border: 1px solid #eef0f5;
                border-radius: 10px;
                background: #fff;
                margin-bottom: 10px;
                align-items: center;
            }
            .dp2-tool {
                font: inherit; font-size: 12px;
                padding: 6px 10px;
                border-radius: 8px;
                border: 1px solid #d1d5db;
                background: #fff; color: #1f2937;
                cursor: pointer;
                display: inline-flex; align-items: center; gap: 4px;
            }
            .dp2-tool.active { background: #e3edff; border-color: #2b6cff; color: #1c4dbf; }
            .dp2-tool .dp2-ico { font-size: 16px; line-height: 1; }

            .dp2-stage {
                position: relative;
                width: 100%;
                border-radius: 12px;
                overflow: hidden;
                background:
                    linear-gradient(45deg, #f3f4f6 25%, transparent 25%) 0 0/20px 20px,
                    linear-gradient(-45deg, #f3f4f6 25%, transparent 25%) 0 10px/20px 20px,
                    linear-gradient(45deg, transparent 75%, #f3f4f6 75%) 10px -10px/20px 20px,
                    linear-gradient(-45deg, transparent 75%, #f3f4f6 75%) -10px 0/20px 20px,
                    #fff;
                border: 1px solid #e5e7eb;
                touch-action: none;
            }
            .dp2-stage canvas {
                display: block; width: 100%; height: 100%;
                cursor: crosshair; touch-action: none;
            }
            .dp2-status { font-size: 12px; color: #6b7280; padding: 6px 2px 0; min-height: 16px; }
            .dp2-hint   { font-size: 12px; color: #6b7280; padding: 6px 2px 0; }

            .dp2-text-inline {
                position: absolute;
                background: #fff;
                border: 1px solid #2b6cff;
                border-radius: 6px;
                padding: 6px 8px;
                font: inherit; font-size: 14px;
                box-shadow: 0 6px 24px rgba(0,0,0,.12);
                z-index: 5; min-width: 140px;
                resize: vertical;
            }

            .dp2-modal {
                position: fixed; inset: 0;
                background: rgba(15, 23, 42, .55);
                display: none; align-items: center; justify-content: center;
                z-index: 99999; padding: 20px;
            }
            .dp2-modal.open { display: flex; }
            .dp2-modal-box {
                background: #fff; border-radius: 12px; padding: 16px;
                width: min(880px, 100%); max-height: 85vh;
                display: flex; flex-direction: column; gap: 10px;
            }
            .dp2-modal-box h3 { margin: 0; font-size: 16px; }
            .dp2-modal-box textarea {
                flex: 1; min-height: 260px;
                font-family: ui-monospace, Menlo, Consolas, monospace;
                font-size: 12px; padding: 10px;
                border: 1px solid #e5e7eb; border-radius: 8px;
                resize: vertical; white-space: pre; overflow: auto;
            }
            .dp2-modal-actions { display: flex; gap: 8px; flex-wrap: wrap; }

            .dp2-gradient-preview {
                width: 60px; height: 22px; border-radius: 6px;
                border: 1px solid #d1d5db;
                background: linear-gradient(90deg, #2b6cff, #ec4899);
            }

            @media (max-width: 700px) {
                .drawn-picture2-app { padding: 10px; }
                .dp2-toolbar, .dp2-tool-row { gap: 6px 8px; padding: 8px; }
                .dp2-range { width: 70px; }
                .dp2-swatches { max-width: 140px; }
            }
        </style>
        <script>
        (function () {
          'use strict';

          console.log('[DrawnPicture2] Скрипт загружен');

          function initApp(root) {
            if (!root) return;
            if (root.dataset.dp2Init === '1') return;
            root.dataset.dp2Init = '1';
            root.innerHTML = '';

            var HEIGHT = parseInt(root.dataset.height || '560', 10);

            var toolbar  = document.createElement('div'); toolbar.className  = 'dp2-toolbar';
            var toolRow  = document.createElement('div'); toolRow.className  = 'dp2-tool-row';
            var shapeRow = document.createElement('div'); shapeRow.className = 'dp2-tool-row';
            var iconRow  = document.createElement('div'); iconRow.className  = 'dp2-tool-row';
            var brushRow = document.createElement('div'); brushRow.className = 'dp2-tool-row';
            var textRow  = document.createElement('div'); textRow.className  = 'dp2-tool-row';
            var stage    = document.createElement('div'); stage.className    = 'dp2-stage';
            stage.style.height = HEIGHT + 'px';
            var canvas   = document.createElement('canvas');
            stage.appendChild(canvas);
            var status   = document.createElement('div'); status.className = 'dp2-status';
            var hint     = document.createElement('div'); hint.className   = 'dp2-hint';
            hint.textContent = 'Инструменты: кисть, фигуры, иконки, текст, выделение, пипетка, заливка. Экспорт: PNG/JPG/WebP/SVG/data-URL.';

            root.appendChild(toolbar);
            root.appendChild(toolRow);
            root.appendChild(brushRow);
            root.appendChild(shapeRow);
            root.appendChild(iconRow);
            root.appendChild(textRow);
            root.appendChild(stage);
            root.appendChild(status);
            root.appendChild(hint);

            var ctx = canvas.getContext('2d');
            var W = 0, H = 0, dpr = Math.min(2, window.devicePixelRatio || 1);

            var items = [];
            var redo  = [];
            var selected = null;
            var dragStart = null;
            var drawing = false;
            var current = null;

            var color    = '#2b6cff';
            var gradient = null;
            var brush    = 4;
            var bgFill   = 'transparent';
            var tool     = 'brush';
            var shape    = 'rect';
            var icon     = 'star';
            var shapeFill = false;
            var fontSize = 24;
            var fontFamily = 'system';
            var fontBold = false, fontItalic = false, fontUnderline = false;
            var strokeColor = '';
            var textRotate = 0;
            var shadowOn = false;
            var glowOn   = false;
            var brushStyle = 'pencil';

            function toNorm(x, y) { return [x / W, y / H]; }
            function fromNorm(p) { return [p[0] * W, p[1] * H]; }

            function resolveFill(c, box) {
              if (!gradient) return color;
              var g = c.createLinearGradient(box.x0, box.y0, box.x1, box.y1);
              g.addColorStop(0, gradient.from);
              g.addColorStop(1, gradient.to);
              return g;
            }

            function pathStar(c, cx, cy, spikes, outer, inner) {
              var rot = -Math.PI / 2, step = Math.PI / spikes;
              c.beginPath();
              c.moveTo(cx + Math.cos(rot) * outer, cy + Math.sin(rot) * outer);
              for (var i = 0; i < spikes; i++) {
                rot += step; c.lineTo(cx + Math.cos(rot) * inner, cy + Math.sin(rot) * inner);
                rot += step; c.lineTo(cx + Math.cos(rot) * outer, cy + Math.sin(rot) * outer);
              }
              c.closePath();
            }
            function pathHeart(c, cx, cy, w, h) {
              var hw = w / 2, hh = h / 2;
              c.beginPath();
              c.moveTo(cx, cy + hh);
              c.bezierCurveTo(cx - hw*2, cy - hh*0.2, cx - hw*0.6, cy - hh*1.6, cx, cy - hh*0.6);
              c.bezierCurveTo(cx + hw*0.6, cy - hh*1.6, cx + hw*2, cy - hh*0.2, cx, cy + hh);
              c.closePath();
            }
            function pathArrow(c, ax, ay, bx, by, w) {
              var ang = Math.atan2(by - ay, bx - ax);
              var hl = Math.max(10, w * 4);
              c.beginPath();
              c.moveTo(bx - hl*Math.cos(ang - Math.PI/6), by - hl*Math.sin(ang - Math.PI/6));
              c.lineTo(bx, by);
              c.lineTo(bx - hl*Math.cos(ang + Math.PI/6), by - hl*Math.sin(ang + Math.PI/6));
            }
            function pathTriangle(c, x0, y0, x1, y1) {
              c.beginPath();
              c.moveTo((x0 + x1) / 2, y0);
              c.lineTo(x1, y1);
              c.lineTo(x0, y1);
              c.closePath();
            }

            function iconPath(c, name, x0, y0, x1, y1) {
              var cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
              var r = Math.min(Math.abs(x1 - x0), Math.abs(y1 - y0)) / 2;
              switch (name) {
                case 'smile': c.beginPath(); c.arc(cx, cy, r, 0, Math.PI*2); break;
                case 'home':
                  c.beginPath();
                  c.moveTo(cx, cy - r); c.lineTo(cx + r, cy);
                  c.lineTo(cx + r*0.7, cy); c.lineTo(cx + r*0.7, cy + r);
                  c.lineTo(cx - r*0.7, cy + r); c.lineTo(cx - r*0.7, cy);
                  c.lineTo(cx - r, cy); c.closePath();
                  break;
                case 'sun':
                  c.beginPath(); c.arc(cx, cy, r*0.5, 0, Math.PI*2);
                  for (var i = 0; i < 8; i++) {
                    var a = i * Math.PI/4;
                    c.moveTo(cx + Math.cos(a)*r*0.72, cy + Math.sin(a)*r*0.72);
                    c.lineTo(cx + Math.cos(a)*r, cy + Math.sin(a)*r);
                  }
                  break;
                case 'cloud':
                  c.beginPath();
                  c.arc(cx - r*0.4, cy, r*0.55, 0, Math.PI*2);
                  c.arc(cx, cy - r*0.2, r*0.7, 0, Math.PI*2);
                  c.arc(cx + r*0.4, cy, r*0.55, 0, Math.PI*2);
                  break;
                case 'tree':
                  c.beginPath();
                  c.moveTo(cx, cy - r);
                  c.lineTo(cx + r*0.8, cy + r*0.2);
                  c.lineTo(cx + r*0.4, cy + r*0.2);
                  c.lineTo(cx + r*0.9, cy + r*0.7);
                  c.lineTo(cx - r*0.9, cy + r*0.7);
                  c.lineTo(cx - r*0.4, cy + r*0.2);
                  c.lineTo(cx - r*0.8, cy + r*0.2);
                  c.closePath();
                  c.rect(cx - r*0.15, cy + r*0.7, r*0.3, r*0.3);
                  break;
                case 'snowflake':
                  c.beginPath();
                  for (var k = 0; k < 6; k++) {
                    var ang = k * Math.PI/3;
                    c.moveTo(cx, cy);
                    c.lineTo(cx + Math.cos(ang)*r, cy + Math.sin(ang)*r);
                    var mx = cx + Math.cos(ang)*r*0.65;
                    var my = cy + Math.sin(ang)*r*0.65;
                    c.moveTo(mx, my);
                    c.lineTo(mx + Math.cos(ang + Math.PI/4)*r*0.3, my + Math.sin(ang + Math.PI/4)*r*0.3);
                    c.moveTo(mx, my);
                    c.lineTo(mx + Math.cos(ang - Math.PI/4)*r*0.3, my + Math.sin(ang - Math.PI/4)*r*0.3);
                  }
                  break;
                case 'heart': pathHeart(c, cx, cy, r*1.6, r*1.6); break;
                case 'star':  pathStar(c, cx, cy, 5, r, r*0.45); break;
                case 'car':
                  c.beginPath();
                  c.moveTo(cx - r, cy + r*0.2);
                  c.lineTo(cx - r*0.7, cy + r*0.2);
                  c.lineTo(cx - r*0.5, cy - r*0.4);
                  c.lineTo(cx + r*0.5, cy - r*0.4);
                  c.lineTo(cx + r*0.7, cy + r*0.2);
                  c.lineTo(cx + r, cy + r*0.2);
                  c.lineTo(cx + r, cy + r*0.6);
                  c.lineTo(cx - r, cy + r*0.6);
                  c.closePath();
                  c.moveTo(cx - r*0.5, cy + r*0.6);
                  c.arc(cx - r*0.5, cy + r*0.6, r*0.22, 0, Math.PI*2);
                  c.moveTo(cx + r*0.5, cy + r*0.6);
                  c.arc(cx + r*0.5, cy + r*0.6, r*0.22, 0, Math.PI*2);
                  break;
              }
            }
            function drawIcon(c, name, x0, y0, x1, y1, fillStyle) {
              c.save();
              var cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
              var r = Math.min(Math.abs(x1 - x0), Math.abs(y1 - y0)) / 2;
              c.lineWidth = Math.max(2, r * 0.12);
              c.lineJoin = 'round'; c.lineCap = 'round';
              iconPath(c, name, x0, y0, x1, y1);
              var useFill = !/sun|snowflake|smile/.test(name);
              if (name === 'smile') {
                c.fillStyle = fillStyle; c.fill();
                c.save(); c.globalCompositeOperation = 'destination-out';
                c.beginPath();
                c.arc(cx - r*0.35, cy - r*0.25, r*0.12, 0, Math.PI*2);
                c.arc(cx + r*0.35, cy - r*0.25, r*0.12, 0, Math.PI*2);
                c.fill(); c.restore();
                c.beginPath();
                c.arc(cx, cy + r*0.1, r*0.55, Math.PI*0.1, Math.PI*0.9);
                c.lineWidth = r*0.14; c.strokeStyle = fillStyle; c.stroke();
              } else if (useFill) { c.fillStyle = fillStyle; c.fill(); }
              else { c.strokeStyle = fillStyle; c.stroke(); }
              c.restore();
            }

            function applyShadow(c) {
              if (shadowOn) {
                c.shadowColor = 'rgba(0,0,0,0.35)';
                c.shadowBlur = 10; c.shadowOffsetX = 3; c.shadowOffsetY = 3;
              } else if (glowOn) {
                c.shadowColor = color; c.shadowBlur = 18;
              }
            }

            function drawStroke(s) {
              var pts = s.p;
              if (!pts.length) return;
              ctx.save();
              ctx.lineWidth = s.w;
              ctx.lineCap = 'round'; ctx.lineJoin = 'round';
              applyShadow(ctx);
              if (s.m === 1) {
                ctx.globalCompositeOperation = 'destination-out';
                ctx.strokeStyle = 'rgba(0,0,0,1)';
              } else ctx.strokeStyle = s.c;

              if (s.style === 'spray') {
                for (var i = 0; i < pts.length; i++) {
                  var p = fromNorm(pts[i]);
                  for (var k = 0; k < 6; k++) {
                    var ang = Math.random() * Math.PI * 2;
                    var rad = Math.random() * s.w;
                    ctx.beginPath();
                    ctx.arc(p[0] + Math.cos(ang)*rad, p[1] + Math.sin(ang)*rad, 1.2, 0, Math.PI*2);
                    ctx.fillStyle = ctx.strokeStyle; ctx.fill();
                  }
                }
              } else if (s.style === 'marker') {
                ctx.lineWidth = s.w * 1.8; ctx.globalAlpha = 0.35;
                strokePolyline(pts); ctx.globalAlpha = 1; strokePolyline(pts);
              } else if (s.style === 'calligraphy') {
                for (var j = 1; j < pts.length; j++) {
                  var a = fromNorm(pts[j - 1]), b = fromNorm(pts[j]);
                  var ang = Math.atan2(b[1] - a[1], b[0] - a[0]);
                  ctx.lineWidth = s.w * (0.4 + Math.abs(Math.sin(ang)) * 1.2);
                  ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
                }
              } else strokePolyline(pts);
              ctx.restore();
            }
            function strokePolyline(pts) {
              if (pts.length === 1) {
                var p = fromNorm(pts[0]);
                ctx.beginPath();
                ctx.arc(p[0], p[1], ctx.lineWidth / 2, 0, Math.PI*2);
                ctx.fillStyle = ctx.strokeStyle; ctx.fill();
                return;
              }
              ctx.beginPath();
              var f = fromNorm(pts[0]);
              ctx.moveTo(f[0], f[1]);
              for (var i = 1; i < pts.length; i++) {
                var q = fromNorm(pts[i]);
                ctx.lineTo(q[0], q[1]);
              }
              ctx.stroke();
            }

            function drawShapeItem(s) {
              var a = fromNorm(s.p[0]), b = fromNorm(s.p[1]);
              var x0 = Math.min(a[0], b[0]), y0 = Math.min(a[1], b[1]);
              var x1 = Math.max(a[0], b[0]), y1 = Math.max(a[1], b[1]);
              var w = x1 - x0, h = y1 - y0;
              ctx.save();
              ctx.lineWidth = s.w; ctx.lineJoin = 'round'; ctx.lineCap = 'round';
              applyShadow(ctx);
              var fillStyle = s.fill ? resolveFill(ctx, { x0: x0, y0: y0, x1: x1, y1: y1 }) : null;
              ctx.strokeStyle = s.c;
              if (fillStyle) ctx.fillStyle = fillStyle;
              if (s.shape === 'line') { ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke(); }
              else if (s.shape === 'rect') { if (s.fill) ctx.fillRect(x0,y0,w,h); else ctx.strokeRect(x0,y0,w,h); }
              else if (s.shape === 'circle') { ctx.beginPath(); ctx.ellipse((x0+x1)/2,(y0+y1)/2,w/2,h/2,0,0,Math.PI*2); if (s.fill) ctx.fill(); else ctx.stroke(); }
              else if (s.shape === 'triangle') { pathTriangle(ctx,x0,y0,x1,y1); if (s.fill) ctx.fill(); else ctx.stroke(); }
              else if (s.shape === 'star') { pathStar(ctx,(x0+x1)/2,(y0+y1)/2,5,Math.min(w,h)/2,Math.min(w,h)/4); if (s.fill) ctx.fill(); else ctx.stroke(); }
              else if (s.shape === 'arrow') {
                ctx.beginPath(); ctx.moveTo(a[0],a[1]); ctx.lineTo(b[0],b[1]); ctx.stroke();
                pathArrow(ctx,a[0],a[1],b[0],b[1],s.w); ctx.fill();
              }
              else if (s.shape === 'heart') { pathHeart(ctx,(x0+x1)/2,(y0+y1)/2,w,h); if (s.fill) ctx.fill(); else ctx.stroke(); }
              ctx.restore();
            }

            function drawIconItem(it) {
              var a = fromNorm(it.p[0]), b = fromNorm(it.p[1]);
              ctx.save();
              applyShadow(ctx);
              var x0 = Math.min(a[0],b[0]), y0 = Math.min(a[1],b[1]);
              var x1 = Math.max(a[0],b[0]), y1 = Math.max(a[1],b[1]);
              var fillStyle = resolveFill(ctx, { x0: x0, y0: y0, x1: x1, y1: y1 });
              drawIcon(ctx, it.icon, a[0], a[1], b[0], b[1], fillStyle);
              ctx.restore();
            }

            function fontString(size, family, bold, italic) {
              var fam = ({
                'system': '-apple-system, "Segoe UI", Roboto, Arial, sans-serif',
                'serif':  'Georgia, "Times New Roman", serif',
                'mono':   'ui-monospace, Menlo, Consolas, monospace',
                'cursive':'cursive',
                'rounded':'"Comic Sans MS", cursive'
              })[family] || 'sans-serif';
              return (italic ? 'italic ' : '') + (bold ? 'bold ' : '') + size + 'px ' + fam;
            }

            function drawTextItem(t) {
              var p = fromNorm(t.p);
              ctx.save();
              applyShadow(ctx);
              ctx.font = fontString(t.size, t.fontFamily || 'system', t.bold, t.italic);
              ctx.textBaseline = 'top';
              ctx.translate(p[0], p[1]);
              if (t.rotate) ctx.rotate(t.rotate * Math.PI / 180);
              var lines = String(t.text).split('\n');
              var lh = t.size * 1.2;
              for (var i = 0; i < lines.length; i++) {
                var y = i * lh;
                if (t.strokeColor && t.strokeWidth > 0) {
                  ctx.lineWidth = t.strokeWidth;
                  ctx.strokeStyle = t.strokeColor;
                  ctx.strokeText(lines[i], 0, y);
                }
                if (t.gradient) {
                  var g = ctx.createLinearGradient(0, y, ctx.measureText(lines[i]).width, y + t.size);
                  g.addColorStop(0, t.gradient.from);
                  g.addColorStop(1, t.gradient.to);
                  ctx.fillStyle = g;
                } else {
                  ctx.fillStyle = t.c;
                }
                ctx.fillText(lines[i], 0, y);
                if (t.underline) {
                  var mw = ctx.measureText(lines[i]).width;
                  ctx.strokeStyle = t.gradient ? t.gradient.to : t.c;
                  ctx.lineWidth = Math.max(1, t.size * 0.06);
                  ctx.beginPath();
                  ctx.moveTo(0, y + t.size * 1.05);
                  ctx.lineTo(mw, y + t.size * 1.05);
                  ctx.stroke();
                }
              }
              ctx.restore();
            }

            function redraw() {
              ctx.clearRect(0, 0, W, H);
              if (bgFill !== 'transparent') {
                ctx.save();
                ctx.globalCompositeOperation = 'source-over';
                ctx.fillStyle = bgFill;
                ctx.fillRect(0, 0, W, H);
                ctx.restore();
              }
              for (var i = 0; i < items.length; i++) {
                var it = items[i];
                if (it.type === 'stroke') drawStroke(it);
                else if (it.type === 'shape') drawShapeItem(it);
                else if (it.type === 'icon') drawIconItem(it);
                else if (it.type === 'text') drawTextItem(it);
              }
              if (selected) drawSelectionOutline(selected);
              if (dragStart && current && current.type === 'shape') {
                drawShapeItem({ shape: shape, c: color, w: brush, fill: shapeFill, p: [dragStart, current.p[1]] });
              }
              if (dragStart && current && current.type === 'icon') {
                var a = fromNorm(dragStart), b = fromNorm(current.p[1]);
                ctx.save(); applyShadow(ctx);
                drawIcon(ctx, icon, a[0], a[1], b[0], b[1], resolveFill(ctx, {x0:Math.min(a[0],b[0]),y0:Math.min(a[1],b[1]),x1:Math.max(a[0],b[0]),y1:Math.max(a[1],b[1])}));
                ctx.restore();
              }
            }

            function drawSelectionOutline(item) {
              var box = itemBBox(item);
              if (!box) return;
              ctx.save();
              ctx.setLineDash([6, 4]);
              ctx.lineWidth = 1.5;
              ctx.strokeStyle = '#2b6cff';
              ctx.strokeRect(box.x, box.y, box.w, box.h);
              ctx.restore();
            }

            function itemBBox(item) {
              if (item.type === 'stroke') {
                if (!item.p.length) return null;
                var xs = [], ys = [];
                for (var i = 0; i < item.p.length; i++) { xs.push(item.p[i][0]); ys.push(item.p[i][1]); }
                var x0 = Math.min.apply(null, xs) * W, y0 = Math.min.apply(null, ys) * H;
                var x1 = Math.max.apply(null, xs) * W, y1 = Math.max.apply(null, ys) * H;
                var pad = item.w;
                return { x: x0 - pad, y: y0 - pad, w: x1 - x0 + pad*2, h: y1 - y0 + pad*2 };
              }
              if (item.type === 'shape' || item.type === 'icon') {
                var a = fromNorm(item.p[0]), b = fromNorm(item.p[1]);
                var xx0 = Math.min(a[0],b[0]), yy0 = Math.min(a[1],b[1]);
                var xx1 = Math.max(a[0],b[0]), yy1 = Math.max(a[1],b[1]);
                return { x: xx0, y: yy0, w: xx1 - xx0, h: yy1 - yy0 };
              }
              if (item.type === 'text') {
                ctx.save();
                ctx.font = fontString(item.size, item.fontFamily || 'system', item.bold, item.italic);
                var lines = String(item.text).split('\n');
                var maxW = 0;
                for (var j = 0; j < lines.length; j++) maxW = Math.max(maxW, ctx.measureText(lines[j]).width);
                ctx.restore();
                var p = fromNorm(item.p);
                return { x: p[0], y: p[1], w: maxW, h: lines.length * item.size * 1.2 };
              }
              return null;
            }

            function pos(e) {
              var r = canvas.getBoundingClientRect();
              if (e.touches && e.touches.length) {
                return [e.touches[0].clientX - r.left, e.touches[0].clientY - r.top];
              }
              return [e.clientX - r.left, e.clientY - r.top];
            }
            function pointInBBox(x, y, box) {
              return x >= box.x && x <= box.x + box.w && y >= box.y && y <= box.y + box.h;
            }
            function findTopItem(x, y) {
              for (var i = items.length - 1; i >= 0; i--) {
                var box = itemBBox(items[i]);
                if (box && pointInBBox(x, y, box)) return items[i];
              }
              return null;
            }

            function begin(x, y) {
              if (stage.querySelector('.dp2-text-inline')) return;
              drawing = true;
              var p = toNorm(x, y);

              if (tool === 'brush' || tool === 'erase') {
                current = { type: 'stroke', c: color, w: brush, m: tool === 'erase' ? 1 : 0,
                            style: tool === 'erase' ? 'pencil' : brushStyle, p: [p] };
                items.push(current); redo = []; drawStroke(current);
              } else if (tool === 'shape') {
                dragStart = p;
                current = { type: 'shape', shape: shape, c: color, w: brush, fill: shapeFill, p: [p, p] };
              } else if (tool === 'icon') {
                dragStart = p;
                current = { type: 'icon', icon: icon, p: [p, p] };
              } else if (tool === 'text') {
                openTextInput(x, y); drawing = false;
              } else if (tool === 'select') {
                var it = findTopItem(x, y);
                selected = it;
                if (it) dragStart = { item: it, p: p, origPts: JSON.parse(JSON.stringify(it.p)) };
                redraw(); drawing = false;
              } else if (tool === 'picker') {
                try {
                  var px = ctx.getImageData(x * dpr, y * dpr, 1, 1).data;
                  var hex = '#' + [px[0], px[1], px[2]].map(function (v) {
                    var s = v.toString(16); return s.length === 1 ? '0' + s : s;
                  }).join('');
                  color = hex; colorInput.value = hex; updateSwatches();
                  setStatus('Взял цвет ' + hex);
                } catch (e) { setStatus('Не удалось взять цвет.'); }
                drawing = false;
              } else if (tool === 'fill') {
                floodFill(x, y); drawing = false;
              }
            }

            function extend(x, y) {
              if (!drawing) return;
              var p = toNorm(x, y);
              if (current && current.type === 'stroke') {
                var last = current.p[current.p.length - 1];
                var dx = (p[0]-last[0]) * W, dy = (p[1]-last[1]) * H;
                if (dx*dx + dy*dy < 3) return;
                current.p.push(p); drawStroke(current);
              } else if (current && (current.type === 'shape' || current.type === 'icon')) {
                current.p[1] = p; redraw();
              } else if (tool === 'select' && dragStart && dragStart.item) {
                var ddx = p[0] - dragStart.p[0], ddy = p[1] - dragStart.p[1];
                var it = dragStart.item;
                if (it.type === 'stroke') {
                  it.p = dragStart.origPts.map(function (q) { return [q[0] + ddx, q[1] + ddy]; });
                } else if (it.type === 'shape' || it.type === 'icon') {
                  it.p = [
                    [dragStart.origPts[0][0] + ddx, dragStart.origPts[0][1] + ddy],
                    [dragStart.origPts[1][0] + ddx, dragStart.origPts[1][1] + ddy]
                  ];
                } else if (it.type === 'text') {
                  it.p = [dragStart.origPts[0] + ddx, dragStart.origPts[1] + ddy];
                }
                redraw();
              }
            }

            function end() {
              if (drawing && current && (current.type === 'shape' || current.type === 'icon')) {
                items.push(current); redo = [];
              }
              drawing = false; current = null; dragStart = null; redraw();
            }

            canvas.addEventListener('mousedown', function (e) {
              if (e.button !== 0) return;
              if (stage.querySelector('.dp2-text-inline')) return;
              var p = pos(e); begin(p[0], p[1]);
            });
            canvas.addEventListener('mousemove', function (e) {
              if (stage.querySelector('.dp2-text-inline')) return;
              var p = pos(e); extend(p[0], p[1]);
            });
            window.addEventListener('mouseup', end);
            canvas.addEventListener('touchstart', function (e) {
              if (stage.querySelector('.dp2-text-inline')) return;
              e.preventDefault();
              var p = pos(e); begin(p[0], p[1]);
            }, { passive: false });
            canvas.addEventListener('touchmove', function (e) {
              if (stage.querySelector('.dp2-text-inline')) return;
              e.preventDefault();
              var p = pos(e); extend(p[0], p[1]);
            }, { passive: false });
            window.addEventListener('touchend', end);
            window.addEventListener('touchcancel', end);

            function openTextInput(x, y) {
              var existing = stage.querySelector('.dp2-text-inline');
              if (existing) existing.blur();

              var inp = document.createElement('textarea');
              inp.className = 'dp2-text-inline';
              inp.placeholder = 'Введите текст (Shift+Enter — новая строка)';
              inp.rows = 2;
              inp.style.left = x + 'px';
              inp.style.top  = y + 'px';
              inp.style.color = color;
              inp.style.fontSize = fontSize + 'px';
              if (fontBold) inp.style.fontWeight = 'bold';
              if (fontItalic) inp.style.fontStyle = 'italic';
              stage.appendChild(inp);

              setTimeout(function () { inp.focus(); }, 0);

              var committed = false;
              function commit(save) {
                if (committed) return;
                committed = true;
                if (save) {
                  var val = inp.value;
                  if (val.trim()) {
                    items.push({
                      type: 'text', c: color,
                      gradient: gradient ? { from: gradient.from, to: gradient.to } : null,
                      strokeColor: strokeColor,
                      strokeWidth: strokeColor ? Math.max(1, fontSize * 0.08) : 0,
                      size: fontSize, text: val, fontFamily: fontFamily,
                      bold: fontBold, italic: fontItalic, underline: fontUnderline,
                      rotate: textRotate, p: toNorm(x, y)
                    });
                    redo = []; redraw();
                    setStatus('Текст добавлен.');
                  }
                }
                if (inp.parentNode) inp.parentNode.removeChild(inp);
              }

              inp.addEventListener('blur', function () { commit(true); });
              inp.addEventListener('keydown', function (e) {
                if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); commit(true); }
                if (e.key === 'Escape') { e.preventDefault(); commit(false); }
              });
              inp.addEventListener('mousedown', function (e) { e.stopPropagation(); });
              inp.addEventListener('touchstart', function (e) { e.stopPropagation(); });
            }

            function floodFill(x, y) {
              var w = canvas.width, h = canvas.height;
              var img = ctx.getImageData(0, 0, w, h);
              var data = img.data;
              var idx = (Math.floor(y * dpr) * w + Math.floor(x * dpr)) * 4;
              var target = [data[idx], data[idx+1], data[idx+2], data[idx+3]];
              var fillRGB = hexToRgb(color);
              if (!fillRGB) return;
              if (Math.abs(target[0]-fillRGB.r)<4 && Math.abs(target[1]-fillRGB.g)<4 &&
                  Math.abs(target[2]-fillRGB.b)<4 && target[3]===255) return;
              var tol = 32;
              var stack = [[Math.floor(x*dpr), Math.floor(y*dpr)]];
              var visited = new Uint8Array(w*h);
              var changed = false;
              function match(i) {
                return Math.abs(data[i]-target[0])<=tol && Math.abs(data[i+1]-target[1])<=tol &&
                       Math.abs(data[i+2]-target[2])<=tol && Math.abs(data[i+3]-target[3])<=tol;
              }
              while (stack.length) {
                var p = stack.pop(); var px = p[0], py = p[1];
                if (px<0||py<0||px>=w||py>=h) continue;
                var vi = py*w+px; if (visited[vi]) continue; visited[vi]=1;
                var i = vi*4; if (!match(i)) continue;
                data[i]=fillRGB.r; data[i+1]=fillRGB.g; data[i+2]=fillRGB.b; data[i+3]=255;
                changed = true;
                stack.push([px+1,py]); stack.push([px-1,py]); stack.push([px,py+1]); stack.push([px,py-1]);
              }
              if (changed) { ctx.putImageData(img, 0, 0); setStatus('Залито.'); }
              else setStatus('Нечего заливать.');
            }
            function hexToRgb(hex) {
              var m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
              return m ? { r: parseInt(m[1],16), g: parseInt(m[2],16), b: parseInt(m[3],16) } : null;
            }

            function group() { var g = document.createElement('div'); g.className = 'dp2-group'; return g; }
            function mkBtn(text, cls, handler) {
              var b = document.createElement('button');
              b.type = 'button';
              b.className = 'dp2-btn' + (cls ? ' ' + cls : '');
              b.textContent = text;
              b.addEventListener('click', handler);
              return b;
            }
            function mkTool(label, iconChar, name) {
              var b = document.createElement('button');
              b.type = 'button';
              b.className = 'dp2-tool';
              b.dataset.tool = name;
              b.innerHTML = (iconChar ? '<span class="dp2-ico">' + iconChar + '</span>' : '') + label;
              b.addEventListener('click', function () { tool = name; updateToolButtons(); });
              return b;
            }
            var toolButtons = {};

            var gColor = group();
            var colorLabel = document.createElement('label'); colorLabel.textContent = 'Цвет';
            var colorInput = document.createElement('input');
            colorInput.type = 'color'; colorInput.className = 'dp2-color'; colorInput.value = color;
            colorInput.addEventListener('input', function () { color = colorInput.value; updateSwatches(); });
            gColor.appendChild(colorLabel); gColor.appendChild(colorInput);

            var PALETTE = [
              '#000000','#475569','#94a3b8','#ffffff',
              '#2b6cff','#0ea5e9','#06b6d4','#10b981',
              '#22c55e','#84cc16','#eab308','#f97316',
              '#ef4444','#d62828','#ec4899','#a855f7'
            ];
            var swatches = document.createElement('div'); swatches.className = 'dp2-swatches';
            PALETTE.forEach(function (hex) {
              var b = document.createElement('button');
              b.type = 'button'; b.className = 'dp2-swatch';
              b.style.background = hex; b.dataset.color = hex;
              b.addEventListener('click', function () { color = hex; colorInput.value = hex; updateSwatches(); });
              swatches.appendChild(b);
            });
            function updateSwatches() {
              Array.prototype.forEach.call(swatches.children, function (el) {
                el.classList.toggle('active', el.dataset.color.toLowerCase() === color.toLowerCase());
              });
            }
            updateSwatches();
            gColor.appendChild(swatches);
            toolbar.appendChild(gColor);

            var gGrad = group();
            var gradFrom = document.createElement('input'); gradFrom.type = 'color'; gradFrom.className = 'dp2-color'; gradFrom.value = '#2b6cff';
            var gradTo   = document.createElement('input'); gradTo.type   = 'color'; gradTo.className   = 'dp2-color'; gradTo.value   = '#ec4899';
            var gradPreview = document.createElement('span'); gradPreview.className = 'dp2-gradient-preview';
            var gradBtn = mkBtn('Градиент', 'secondary', function () {
              if (gradient) { gradient = null; gradBtn.classList.remove('active'); }
              else { gradient = { from: gradFrom.value, to: gradTo.value }; gradBtn.classList.add('active'); }
            });
            function updGrad() {
              gradPreview.style.background = 'linear-gradient(90deg, '+gradFrom.value+', '+gradTo.value+')';
              if (gradient) gradient = { from: gradFrom.value, to: gradTo.value };
            }
            gradFrom.addEventListener('input', updGrad);
            gradTo.addEventListener('input', updGrad);
            updGrad();
            gGrad.appendChild(gradFrom); gGrad.appendChild(gradTo); gGrad.appendChild(gradPreview); gGrad.appendChild(gradBtn);
            toolbar.appendChild(gGrad);

            var gW = group();
            var wLabel = document.createElement('label'); wLabel.textContent = 'Толщина';
            var wInput = document.createElement('input');
            wInput.type = 'range'; wInput.className = 'dp2-range';
            wInput.min = '1'; wInput.max = '60'; wInput.value = brush;
            wInput.addEventListener('input', function () { brush = parseInt(wInput.value, 10); });
            gW.appendChild(wLabel); gW.appendChild(wInput);
            toolbar.appendChild(gW);

            var gBg = group();
            var bgLabel = document.createElement('label'); bgLabel.textContent = 'Фон';
            var bgSelect = document.createElement('select'); bgSelect.className = 'dp2-select';
            [
              ['transparent','Прозрачный'],
              ['#ffffff','Белый'],
              ['#000000','Чёрный'],
              ['#f8fafc','Светло-серый'],
              ['#0f172a','Тёмно-синий'],
              ['#7dd3fc','Голубой'],
              ['#fde68a','Жёлтый'],
              ['#bbf7d0','Зелёный'],
              ['#fecaca','Красный'],
              ['#e9d5ff','Сиреневый']
            ].forEach(function (p) {
              var o = document.createElement('option'); o.value = p[0]; o.textContent = p[1];
              bgSelect.appendChild(o);
            });
            var bgCustom = document.createElement('input');
            bgCustom.type = 'color'; bgCustom.className = 'dp2-color'; bgCustom.value = '#ffffff';
            bgCustom.addEventListener('input', function () { bgFill = bgCustom.value; redraw(); });
            bgSelect.addEventListener('change', function () { bgFill = bgSelect.value; redraw(); });
            gBg.appendChild(bgLabel); gBg.appendChild(bgSelect); gBg.appendChild(bgCustom);
            toolbar.appendChild(gBg);

            var gFx = group();
            var shadowBtn = mkBtn('Тень','secondary',function () {
              shadowOn=!shadowOn; if (shadowOn) glowOn=false;
              shadowBtn.classList.toggle('active',shadowOn);
              glowBtn.classList.toggle('active',glowOn);
            });
            var glowBtn = mkBtn('Свечение','secondary',function () {
              glowOn=!glowOn; if (glowOn) shadowOn=false;
              glowBtn.classList.toggle('active',glowOn);
              shadowBtn.classList.toggle('active',shadowOn);
            });
            gFx.appendChild(shadowBtn); gFx.appendChild(glowBtn);
            toolbar.appendChild(gFx);

            var gHist = group();
            var undoBtn = mkBtn('Отменить','secondary',function () { if (items.length) { redo.push(items.pop()); selected=null; redraw(); } });
            var redoBtn = mkBtn('Вернуть','secondary',function () { if (redo.length) { items.push(redo.pop()); redraw(); } });
            var clearBtn = mkBtn('Очистить','danger',function () {
              if (!items.length || confirm('Очистить всё?')) { items=[]; redo=[]; selected=null; redraw(); }
            });
            gHist.appendChild(undoBtn); gHist.appendChild(redoBtn); gHist.appendChild(clearBtn);
            toolbar.appendChild(gHist);

            var gExp = group();
            var expSelect = document.createElement('select'); expSelect.className = 'dp2-select';
            ['PNG','JPG','WebP'].forEach(function (f) {
              var o = document.createElement('option'); o.value = f; o.textContent = 'Скачать ' + f;
              expSelect.appendChild(o);
            });
            var expBtn = mkBtn('Скачать','',function () { downloadRaster(expSelect.value); });
            var svgBtn = mkBtn('SVG','secondary',function () { downloadSvg(); });
            var codeBtn = mkBtn('Скопировать скрипт','secondary',function () { openScriptModal(); });
            var dataBtn = mkBtn('data-URL','secondary',function () { copyDataUrl(); });
            var openBtn = mkBtn('Открыть','secondary',function () { openPreview(); });
            gExp.appendChild(expSelect); gExp.appendChild(expBtn);
            gExp.appendChild(svgBtn); gExp.appendChild(codeBtn); gExp.appendChild(dataBtn); gExp.appendChild(openBtn);
            toolbar.appendChild(gExp);

            [
              ['brush','Кисть','🖌'],
              ['erase','Ластик','🧽'],
              ['shape','Фигуры','◻'],
              ['icon','Иконки','★'],
              ['text','Текст','T'],
              ['select','Выделение','↖'],
              ['picker','Пипетка','💧'],
              ['fill','Заливка','🪣']
            ].forEach(function (t) {
              var b = mkTool(t[1], t[2], t[0]);
              toolButtons[t[0]] = b;
              toolRow.appendChild(b);
            });

            function updateToolButtons() {
              Object.keys(toolButtons).forEach(function (k) {
                toolButtons[k].classList.toggle('active', k === tool);
              });
              shapeRow.style.display = tool === 'shape' ? 'flex' : 'none';
              iconRow.style.display  = tool === 'icon'  ? 'flex' : 'none';
              textRow.style.display  = tool === 'text'  ? 'flex' : 'none';
              brushRow.style.display = (tool === 'brush' || tool === 'erase') ? 'flex' : 'none';
            }

            [
              ['pencil','Карандаш'],
              ['marker','Маркер'],
              ['spray','Аэрозоль'],
              ['calligraphy','Каллиграфия']
            ].forEach(function (b) {
              var btn = document.createElement('button');
              btn.type = 'button';
              btn.className = 'dp2-tool' + (brushStyle === b[0] ? ' active' : '');
              btn.textContent = b[1];
              btn.dataset.style = b[0];
              btn.addEventListener('click', function () {
                brushStyle = b[0];
                Array.prototype.forEach.call(brushRow.children, function (el) {
                  el.classList.toggle('active', el.dataset.style === brushStyle);
                });
              });
              brushRow.appendChild(btn);
            });

            [
              ['line','Линия'],['rect','Прямоугольник'],['circle','Круг'],
              ['triangle','Треугольник'],['star','Звезда'],['arrow','Стрелка'],['heart','Сердце']
            ].forEach(function (s) {
              var b = document.createElement('button');
              b.type = 'button';
              b.className = 'dp2-tool' + (shape === s[0] ? ' active' : '');
              b.textContent = s[1]; b.dataset.shape = s[0];
              b.addEventListener('click', function () {
                shape = s[0];
                Array.prototype.forEach.call(shapeRow.children, function (el) {
                  if (el.dataset && el.dataset.shape) el.classList.toggle('active', el.dataset.shape === shape);
                });
              });
              shapeRow.appendChild(b);
            });
            var fillBtn = document.createElement('button');
            fillBtn.type='button'; fillBtn.className='dp2-tool'; fillBtn.textContent='Заливка фигур';
            fillBtn.addEventListener('click', function(){ shapeFill=!shapeFill; fillBtn.classList.toggle('active', shapeFill); });
            shapeRow.appendChild(fillBtn);

            [
              ['smile','😀 Смайл'],['home','🏠 Дом'],['sun','☀ Солнце'],['cloud','☁ Облако'],
              ['tree','🎄 Ёлка'],['snowflake','❄ Снежинка'],['heart','❤ Сердечко'],
              ['star','★ Звезда'],['car','🚗 Машина']
            ].forEach(function (it) {
              var b = document.createElement('button');
              b.type = 'button';
              b.className = 'dp2-tool' + (icon === it[0] ? ' active' : '');
              b.textContent = it[1]; b.dataset.icon = it[0];
              b.addEventListener('click', function () {
                icon = it[0];
                Array.prototype.forEach.call(iconRow.children, function (el) {
                  if (el.dataset && el.dataset.icon) el.classList.toggle('active', el.dataset.icon === icon);
                });
              });
              iconRow.appendChild(b);
            });

            var tSize = document.createElement('input');
            tSize.type='number'; tSize.className='dp2-number';
            tSize.min='8'; tSize.max='300'; tSize.value=fontSize;
            tSize.addEventListener('input', function(){ fontSize = Math.max(8, Math.min(300, parseInt(tSize.value,10)||24)); });
            textRow.appendChild(tSize);

            var famSelect = document.createElement('select'); famSelect.className='dp2-select';
            [['system','Системный'],['serif','Serif'],['mono','Моно'],['cursive','Рукописный'],['rounded','Округлый']].forEach(function(p){
              var o=document.createElement('option'); o.value=p[0]; o.textContent=p[1]; famSelect.appendChild(o);
            });
            famSelect.addEventListener('change', function(){ fontFamily = famSelect.value; });
            textRow.appendChild(famSelect);

            var bBtn = document.createElement('button'); bBtn.type='button'; bBtn.className='dp2-tool'; bBtn.textContent='B'; bBtn.style.fontWeight='bold';
            bBtn.addEventListener('click', function(){ fontBold=!fontBold; bBtn.classList.toggle('active', fontBold); });
            var iBtn = document.createElement('button'); iBtn.type='button'; iBtn.className='dp2-tool'; iBtn.textContent='I'; iBtn.style.fontStyle='italic';
            iBtn.addEventListener('click', function(){ fontItalic=!fontItalic; iBtn.classList.toggle('active', fontItalic); });
            var uBtn = document.createElement('button'); uBtn.type='button'; uBtn.className='dp2-tool'; uBtn.textContent='U'; uBtn.style.textDecoration='underline';
            uBtn.addEventListener('click', function(){ fontUnderline=!fontUnderline; uBtn.classList.toggle('active', fontUnderline); });
            textRow.appendChild(bBtn); textRow.appendChild(iBtn); textRow.appendChild(uBtn);

            var strokeCol = document.createElement('input'); strokeCol.type='color'; strokeCol.className='dp2-color'; strokeCol.value='#000000';
            var strokeToggle = document.createElement('button'); strokeToggle.type='button'; strokeToggle.className='dp2-tool'; strokeToggle.textContent='Обводка';
            strokeToggle.addEventListener('click', function(){
              if (strokeColor) { strokeColor=''; strokeToggle.classList.remove('active'); }
              else { strokeColor = strokeCol.value; strokeToggle.classList.add('active'); }
            });
            strokeCol.addEventListener('input', function(){ if (strokeColor) strokeColor = strokeCol.value; });
            textRow.appendChild(strokeToggle); textRow.appendChild(strokeCol);

            var rotIn = document.createElement('input'); rotIn.type='number'; rotIn.className='dp2-number';
            rotIn.min='-180'; rotIn.max='180'; rotIn.value=textRotate;
            rotIn.addEventListener('input', function(){ textRotate = parseInt(rotIn.value,10)||0; });
            textRow.appendChild(rotIn);

            function buildRasterCanvas() {
              var out = document.createElement('canvas');
              out.width = canvas.width; out.height = canvas.height;
              var octx = out.getContext('2d');
              if (bgFill !== 'transparent') { octx.fillStyle = bgFill; octx.fillRect(0,0,out.width,out.height); }
              octx.drawImage(canvas, 0, 0);
              return out;
            }
            function downloadRaster(format) {
              var out = buildRasterCanvas();
              var mime = 'image/png'; if (format==='JPG') mime='image/jpeg'; if (format==='WebP') mime='image/webp';
              var ext = format.toLowerCase();
              if (format === 'JPG' && bgFill === 'transparent') {
                var white = document.createElement('canvas');
                white.width = out.width; white.height = out.height;
                var wctx = white.getContext('2d');
                wctx.fillStyle='#fff'; wctx.fillRect(0,0,white.width,white.height);
                wctx.drawImage(out,0,0); out = white;
              }
              var url = out.toDataURL(mime, 0.92);
              var a = document.createElement('a');
              a.href = url; a.download = 'drawing-' + Date.now() + '.' + ext;
              document.body.appendChild(a); a.click(); document.body.removeChild(a);
              setStatus('Скачано: ' + format);
            }
            function copyDataUrl() {
              var out = buildRasterCanvas();
              copyText(out.toDataURL('image/png'));
              setStatus('data-URL скопирован.');
            }
            function downloadSvg() {
              var svg = buildSvg();
              var blob = new Blob([svg], { type:'image/svg+xml' });
              var url = URL.createObjectURL(blob);
              var a = document.createElement('a');
              a.href = url; a.download = 'drawing-' + Date.now() + '.svg';
              document.body.appendChild(a); a.click();
              setTimeout(function(){ URL.revokeObjectURL(url); document.body.removeChild(a); }, 0);
              setStatus('Скачано: SVG');
            }
            function buildSvg() {
              var parts = [];
              parts.push('<?xml version="1.0" encoding="UTF-8"?>');
              parts.push('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 '+W+' '+H+'" width="'+W+'" height="'+H+'">');
              if (bgFill !== 'transparent') parts.push('<rect width="100%" height="100%" fill="'+bgFill+'"/>');
              for (var i=0;i<items.length;i++) {
                var it = items[i];
                if (it.type === 'stroke') parts.push('<path d="'+strokeToPath(it.p)+'" fill="none" stroke="'+it.c+'" stroke-width="'+it.w+'" stroke-linecap="round" stroke-linejoin="round"/>');
                else if (it.type === 'shape') parts.push(shapeToSvg(it));
                else if (it.type === 'icon') parts.push(iconToSvg(it));
                else if (it.type === 'text') parts.push(textToSvg(it));
              }
              parts.push('</svg>');
              return parts.join('\n');
            }
            function strokeToPath(pts) {
              if (!pts.length) return '';
              var d = 'M ' + (pts[0][0]*W) + ' ' + (pts[0][1]*H);
              for (var i=1;i<pts.length;i++) d += ' L ' + (pts[i][0]*W) + ' ' + (pts[i][1]*H);
              return d;
            }
            function shapeToSvg(s) {
              var a=[s.p[0][0]*W,s.p[0][1]*H], b=[s.p[1][0]*W,s.p[1][1]*H];
              var x0=Math.min(a[0],b[0]), y0=Math.min(a[1],b[1]), x1=Math.max(a[0],b[0]), y1=Math.max(a[1],b[1]);
              var w=x1-x0, h=y1-y0;
              var stroke=s.c, fill=s.fill?s.c:'none';
              var common = 'stroke="'+stroke+'" stroke-width="'+s.w+'" fill="'+fill+'" stroke-linejoin="round" stroke-linecap="round"';
              if (s.shape==='line') return '<line x1="'+a[0]+'" y1="'+a[1]+'" x2="'+b[0]+'" y2="'+b[1]+'" stroke="'+stroke+'" stroke-width="'+s.w+'" stroke-linecap="round"/>';
              if (s.shape==='rect') return '<rect x="'+x0+'" y="'+y0+'" width="'+w+'" height="'+h+'" '+common+'/>';
              if (s.shape==='circle') return '<ellipse cx="'+((x0+x1)/2)+'" cy="'+((y0+y1)/2)+'" rx="'+(w/2)+'" ry="'+(h/2)+'" '+common+'/>';
              if (s.shape==='triangle') return '<polygon points="'+((x0+x1)/2)+','+y0+' '+x1+','+y1+' '+x0+','+y1+'" '+common+'/>';
              if (s.shape==='star') return '<polygon points="'+starPoints((x0+x1)/2,(y0+y1)/2,5,Math.min(w,h)/2,Math.min(w,h)/4)+'" '+common+'/>';
              if (s.shape==='arrow') {
                var ang = Math.atan2(b[1]-a[1], b[0]-a[0]);
                var hl = Math.max(10, s.w*4);
                var p1=[b[0]-hl*Math.cos(ang-Math.PI/6), b[1]-hl*Math.sin(ang-Math.PI/6)];
                var p2=[b[0]-hl*Math.cos(ang+Math.PI/6), b[1]-hl*Math.sin(ang+Math.PI/6)];
                return '<line x1="'+a[0]+'" y1="'+a[1]+'" x2="'+b[0]+'" y2="'+b[1]+'" stroke="'+stroke+'" stroke-width="'+s.w+'" stroke-linecap="round"/>'+
                       '<polygon points="'+b[0]+','+b[1]+' '+p1[0]+','+p1[1]+' '+p2[0]+','+p2[1]+'" fill="'+stroke+'"/>';
              }
              if (s.shape==='heart') {
                var cx=(x0+x1)/2, cy=(y0+y1)/2, hw=w/2, hh=h/2;
                var d='M '+cx+' '+(cy+hh)+
                  ' C '+(cx-hw*2)+' '+(cy-hh*0.2)+' '+(cx-hw*0.6)+' '+(cy-hh*1.6)+' '+cx+' '+(cy-hh*0.6)+
                  ' C '+(cx+hw*0.6)+' '+(cy-hh*1.6)+' '+(cx+hw*2)+' '+(cy-hh*0.2)+' '+cx+' '+(cy+hh)+' Z';
                return '<path d="'+d+'" '+common+'/>';
              }
              return '';
            }
            function starPoints(cx,cy,spikes,outer,inner) {
              var rot=-Math.PI/2, step=Math.PI/spikes, pts=[];
              pts.push([cx+Math.cos(rot)*outer, cy+Math.sin(rot)*outer]);
              for (var i=0;i<spikes;i++) {
                rot+=step; pts.push([cx+Math.cos(rot)*inner, cy+Math.sin(rot)*inner]);
                rot+=step; pts.push([cx+Math.cos(rot)*outer, cy+Math.sin(rot)*outer]);
              }
              return pts.map(function(p){ return p[0].toFixed(2)+','+p[1].toFixed(2); }).join(' ');
            }
            function iconToSvg(it) {
              var a=[it.p[0][0]*W,it.p[0][1]*H], b=[it.p[1][0]*W,it.p[1][1]*H];
              var x0=Math.min(a[0],b[0]), y0=Math.min(a[1],b[1]);
              var w=Math.abs(b[0]-a[0]), h=Math.abs(b[1]-a[1]);
              return '<rect x="'+x0+'" y="'+y0+'" width="'+w+'" height="'+h+'" fill="none" stroke="'+color+'" stroke-dasharray="2 3" data-icon="'+it.icon+'"/>';
            }
            function textToSvg(t) {
              var p = [t.p[0]*W, t.p[1]*H];
              var lines = String(t.text).split('\n');
              var fam = ({system:'-apple-system, "Segoe UI", Roboto, Arial, sans-serif',serif:'Georgia, "Times New Roman", serif',mono:'ui-monospace, Menlo, Consolas, monospace',cursive:'cursive',rounded:'"Comic Sans MS", cursive'})[t.fontFamily || 'system'];
              var style = (t.italic?'font-style="italic" ':'') + (t.bold?'font-weight="bold" ':'');
              var strokeAttr = '';
              if (t.strokeColor) strokeAttr = ' stroke="'+t.strokeColor+'" stroke-width="'+t.strokeWidth+'" paint-order="stroke"';
              var parts = [];
              for (var i=0;i<lines.length;i++) {
                var y = p[1] + i * t.size * 1.2 + t.size;
                var tr = t.rotate ? ' transform="rotate('+t.rotate+' '+p[0]+' '+y+')"' : '';
                parts.push('<text x="'+p[0]+'" y="'+y+'" font-family="'+fam+'" font-size="'+t.size+'" '+style+' fill="'+t.c+'"'+strokeAttr+tr+'>'+escapeXml(lines[i])+'</text>');
              }
              return parts.join('\n');
            }
            function escapeXml(s) {
              return String(s).replace(/[<>&'"]/g, function(c){
                return {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&apos;','"':'&quot;'}[c];
              });
            }

            var modal = document.createElement('div'); modal.className = 'dp2-modal';
            var box = document.createElement('div'); box.className = 'dp2-modal-box';
            var h3 = document.createElement('h3'); h3.textContent = 'Готовый скрипт';
            var mh = document.createElement('div'); mh.className = 'dp2-hint';
            mh.textContent = 'Скопируй и вставь на свой сайт в блок «Произвольный HTML».';
            var ta = document.createElement('textarea'); ta.readOnly = true;
            var actions = document.createElement('div'); actions.className = 'dp2-modal-actions';
            var copyBtn = mkBtn('Скопировать','',function(){
              copyText(ta.value);
              copyBtn.textContent='Скопировано!';
              setTimeout(function(){ copyBtn.textContent='Скопировать'; }, 1400);
            });
            var closeBtn = mkBtn('Закрыть','secondary',function(){ modal.classList.remove('open'); });
            actions.appendChild(copyBtn); actions.appendChild(closeBtn);
            box.appendChild(h3); box.appendChild(mh); box.appendChild(ta); box.appendChild(actions);
            modal.appendChild(box);
            root.appendChild(modal);
            modal.addEventListener('click', function(e){ if (e.target===modal) modal.classList.remove('open'); });

            function openScriptModal() {
              if (!items.length) { setStatus('Сначала что-нибудь нарисуй.'); return; }
              ta.value = buildScript(packData());
              modal.classList.add('open');
            }
            function packData() {
              return {
                bg: bgFill,
                items: items.map(function (it) { return JSON.parse(JSON.stringify(it)); })
              };
            }
            function buildScript(data) {
              var json = JSON.stringify(data);
              return [
                '<!-- Drawn Picture 2 -->',
                '<div id="drawn-picture2-host" style="position:relative;width:100%;max-width:1200px;height:'+HEIGHT+'px;margin:0 auto;"></div>',
                '<script>',
                '(function(){',
                "  'use strict';",
                '  var DATA = ' + json + ';',
                '  var HOST = document.getElementById("drawn-picture2-host");',
                '  var canvas = document.createElement("canvas");',
                '  canvas.style.cssText = "display:block;width:100%;height:100%;";',
                '  HOST.appendChild(canvas);',
                '  var ctx = canvas.getContext("2d");',
                '  var W=0,H=0,dpr=Math.min(2,window.devicePixelRatio||1);',
                '  function pathStar(c,cx,cy,sp,o,i){var r=-Math.PI/2,s=Math.PI/sp;c.beginPath();c.moveTo(cx+Math.cos(r)*o,cy+Math.sin(r)*o);for(var k=0;k<sp;k++){r+=s;c.lineTo(cx+Math.cos(r)*i,cy+Math.sin(r)*i);r+=s;c.lineTo(cx+Math.cos(r)*o,cy+Math.sin(r)*o);}c.closePath();}',
                '  function pathHeart(c,cx,cy,w,h){var hw=w/2,hh=h/2;c.beginPath();c.moveTo(cx,cy+hh);c.bezierCurveTo(cx-hw*2,cy-hh*0.2,cx-hw*0.6,cy-hh*1.6,cx,cy-hh*0.6);c.bezierCurveTo(cx+hw*0.6,cy-hh*1.6,cx+hw*2,cy-hh*0.2,cx,cy+hh);c.closePath();}',
                '  function iconPath(c,name,x0,y0,x1,y1){var cx=(x0+x1)/2,cy=(y0+y1)/2,r=Math.min(Math.abs(x1-x0),Math.abs(y1-y0))/2;switch(name){',
                '    case "smile": c.beginPath(); c.arc(cx,cy,r,0,Math.PI*2); break;',
                '    case "home": c.beginPath(); c.moveTo(cx,cy-r); c.lineTo(cx+r,cy); c.lineTo(cx+r*0.7,cy); c.lineTo(cx+r*0.7,cy+r); c.lineTo(cx-r*0.7,cy+r); c.lineTo(cx-r*0.7,cy); c.lineTo(cx-r,cy); c.closePath(); break;',
                '    case "sun": c.beginPath(); c.arc(cx,cy,r*0.5,0,Math.PI*2); for(var i=0;i<8;i++){var a=i*Math.PI/4; c.moveTo(cx+Math.cos(a)*r*0.72,cy+Math.sin(a)*r*0.72); c.lineTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r);} break;',
                '    case "cloud": c.beginPath(); c.arc(cx-r*0.4,cy,r*0.55,0,Math.PI*2); c.arc(cx,cy-r*0.2,r*0.7,0,Math.PI*2); c.arc(cx+r*0.4,cy,r*0.55,0,Math.PI*2); break;',
                '    case "tree": c.beginPath(); c.moveTo(cx,cy-r); c.lineTo(cx+r*0.8,cy+r*0.2); c.lineTo(cx+r*0.4,cy+r*0.2); c.lineTo(cx+r*0.9,cy+r*0.7); c.lineTo(cx-r*0.9,cy+r*0.7); c.lineTo(cx-r*0.4,cy+r*0.2); c.lineTo(cx-r*0.8,cy+r*0.2); c.closePath(); c.rect(cx-r*0.15,cy+r*0.7,r*0.3,r*0.3); break;',
                '    case "snowflake": c.beginPath(); for(var k=0;k<6;k++){var ang=k*Math.PI/3; c.moveTo(cx,cy); c.lineTo(cx+Math.cos(ang)*r,cy+Math.sin(ang)*r); var mx=cx+Math.cos(ang)*r*0.65; var my=cy+Math.sin(ang)*r*0.65; c.moveTo(mx,my); c.lineTo(mx+Math.cos(ang+Math.PI/4)*r*0.3,my+Math.sin(ang+Math.PI/4)*r*0.3); c.moveTo(mx,my); c.lineTo(mx+Math.cos(ang-Math.PI/4)*r*0.3,my+Math.sin(ang-Math.PI/4)*r*0.3);} break;',
                '    case "heart": pathHeart(c,cx,cy,r*1.6,r*1.6); break;',
                '    case "star": pathStar(c,cx,cy,5,r,r*0.45); break;',
                '    case "car": c.beginPath(); c.moveTo(cx-r,cy+r*0.2); c.lineTo(cx-r*0.7,cy+r*0.2); c.lineTo(cx-r*0.5,cy-r*0.4); c.lineTo(cx+r*0.5,cy-r*0.4); c.lineTo(cx+r*0.7,cy+r*0.2); c.lineTo(cx+r,cy+r*0.2); c.lineTo(cx+r,cy+r*0.6); c.lineTo(cx-r,cy+r*0.6); c.closePath(); c.moveTo(cx-r*0.5,cy+r*0.6); c.arc(cx-r*0.5,cy+r*0.6,r*0.22,0,Math.PI*2); c.moveTo(cx+r*0.5,cy+r*0.6); c.arc(cx+r*0.5,cy+r*0.6,r*0.22,0,Math.PI*2); break;',
                '  }}',
                '  function drawIcon(c,name,x0,y0,x1,y1,fs){c.save();var cx=(x0+x1)/2,cy=(y0+y1)/2,r=Math.min(Math.abs(x1-x0),Math.abs(y1-y0))/2;c.lineWidth=Math.max(2,r*0.12);c.lineJoin="round";c.lineCap="round";iconPath(c,name,x0,y0,x1,y1);var useFill=!/sun|snowflake|smile/.test(name);if(name==="smile"){c.fillStyle=fs;c.fill();c.save();c.globalCompositeOperation="destination-out";c.beginPath();c.arc(cx-r*0.35,cy-r*0.25,r*0.12,0,Math.PI*2);c.arc(cx+r*0.35,cy-r*0.25,r*0.12,0,Math.PI*2);c.fill();c.restore();c.beginPath();c.arc(cx,cy+r*0.1,r*0.55,Math.PI*0.1,Math.PI*0.9);c.lineWidth=r*0.14;c.strokeStyle=fs;c.stroke();}else if(useFill){c.fillStyle=fs;c.fill();}else{c.strokeStyle=fs;c.stroke();}c.restore();}',
                '  function fontStr(size,fam,bold,italic){var f=({system:"-apple-system,\\"Segoe UI\\",Roboto,Arial,sans-serif",serif:"Georgia,\\"Times New Roman\\",serif",mono:"ui-monospace,Menlo,Consolas,monospace",cursive:"cursive",rounded:"\\"Comic Sans MS\\",cursive"})[fam]||"sans-serif";return (italic?"italic ":"")+(bold?"bold ":"")+size+"px "+f;}',
                '  function drawItem(it){',
                '    if(it.type==="stroke"){',
                '      var pts=it.p;if(!pts.length)return;',
                '      ctx.save();ctx.lineWidth=it.w;ctx.lineCap="round";ctx.lineJoin="round";',
                '      if(it.m===1){ctx.globalCompositeOperation="destination-out";ctx.strokeStyle="rgba(0,0,0,1)";}',
                '      else{ctx.globalCompositeOperation="source-over";ctx.strokeStyle=it.c;}',
                '      if(pts.length===1){ctx.beginPath();ctx.arc(pts[0][0]*W,pts[0][1]*H,it.w/2,0,Math.PI*2);ctx.fillStyle=ctx.strokeStyle;ctx.fill();}',
                '      else{ctx.beginPath();ctx.moveTo(pts[0][0]*W,pts[0][1]*H);for(var i=1;i<pts.length;i++)ctx.lineTo(pts[i][0]*W,pts[i][1]*H);ctx.stroke();}',
                '      ctx.restore();',
                '    } else if(it.type==="shape"){',
                '      var a=[it.p[0][0]*W,it.p[0][1]*H],b=[it.p[1][0]*W,it.p[1][1]*H];',
                '      var x0=Math.min(a[0],b[0]),y0=Math.min(a[1],b[1]),x1=Math.max(a[0],b[0]),y1=Math.max(a[1],b[1]);',
                '      var w=x1-x0,h=y1-y0;',
                '      ctx.save();ctx.lineWidth=it.w;ctx.strokeStyle=it.c;ctx.fillStyle=it.c;ctx.lineJoin="round";ctx.lineCap="round";',
                '      if(it.shape==="line"){ctx.beginPath();ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);ctx.stroke();}',
                '      else if(it.shape==="rect"){if(it.fill)ctx.fillRect(x0,y0,w,h);else ctx.strokeRect(x0,y0,w,h);}',
                '      else if(it.shape==="circle"){ctx.beginPath();ctx.ellipse((x0+x1)/2,(y0+y1)/2,w/2,h/2,0,0,Math.PI*2);if(it.fill)ctx.fill();else ctx.stroke();}',
                '      else if(it.shape==="triangle"){ctx.beginPath();ctx.moveTo((x0+x1)/2,y0);ctx.lineTo(x1,y1);ctx.lineTo(x0,y1);ctx.closePath();if(it.fill)ctx.fill();else ctx.stroke();}',
                '      else if(it.shape==="star"){pathStar(ctx,(x0+x1)/2,(y0+y1)/2,5,Math.min(w,h)/2,Math.min(w,h)/4);if(it.fill)ctx.fill();else ctx.stroke();}',
                '      else if(it.shape==="arrow"){ctx.beginPath();ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);ctx.stroke();var ang=Math.atan2(b[1]-a[1],b[0]-a[0]);var hl=Math.max(10,it.w*4);ctx.beginPath();ctx.moveTo(b[0],b[1]);ctx.lineTo(b[0]-hl*Math.cos(ang-Math.PI/6),b[1]-hl*Math.sin(ang-Math.PI/6));ctx.lineTo(b[0]-hl*Math.cos(ang+Math.PI/6),b[1]-hl*Math.sin(ang+Math.PI/6));ctx.closePath();ctx.fill();}',
                '      else if(it.shape==="heart"){pathHeart(ctx,(x0+x1)/2,(y0+y1)/2,w,h);if(it.fill)ctx.fill();else ctx.stroke();}',
                '      ctx.restore();',
                '    } else if(it.type==="icon"){',
                '      var a=[it.p[0][0]*W,it.p[0][1]*H],b=[it.p[1][0]*W,it.p[1][1]*H];',
                '      drawIcon(ctx,it.icon,a[0],a[1],b[0],b[1],it.c||"#2b6cff");',
                '    } else if(it.type==="text"){',
                '      var p=[it.p[0]*W,it.p[1]*H];',
                '      ctx.save();ctx.fillStyle=it.c;ctx.font=fontStr(it.size,it.fontFamily||"system",it.bold,it.italic);ctx.textBaseline="top";ctx.translate(p[0],p[1]);if(it.rotate)ctx.rotate(it.rotate*Math.PI/180);var lines=String(it.text).split("\\n");for(var i=0;i<lines.length;i++){var y=i*it.size*1.2;if(it.strokeColor){ctx.lineWidth=it.strokeWidth;ctx.strokeStyle=it.strokeColor;ctx.strokeText(lines[i],0,y);}ctx.fillStyle=it.c;ctx.fillText(lines[i],0,y);}ctx.restore();',
                '    }',
                '  }',
                '  function render(){ctx.clearRect(0,0,W,H);if(DATA.bg && DATA.bg!=="transparent"){ctx.fillStyle=DATA.bg;ctx.fillRect(0,0,W,H);}for(var i=0;i<DATA.items.length;i++)drawItem(DATA.items[i]);}',
                '  function resize(){W=HOST.clientWidth;H=HOST.clientHeight;canvas.width=W*dpr;canvas.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);ctx.lineCap="round";ctx.lineJoin="round";render();}',
                '  window.addEventListener("resize",resize);resize();',
                '})();',
                '<\/script>'
              ].join('\n');
            }

            function openPreview() {
              var out = buildRasterCanvas();
              var url = out.toDataURL('image/png');
              var w = window.open('', '_blank');
              if (!w) { setStatus('Разреши всплывающие окна.'); return; }
              w.document.open();
              w.document.write('<!doctype html><html><head><meta charset="utf-8"><title>Рисунок</title><style>body{margin:0;background:#eef1f6;display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:sans-serif}img{max-width:100%;box-shadow:0 10px 40px rgba(0,0,0,.15);border-radius:12px;background:#fff}</style></head><body><img src="' + url + '" alt="Рисунок"></body></html>');
              w.document.close();
            }

            function setStatus(txt) {
              status.textContent = txt || '';
              if (!txt) return;
              clearTimeout(setStatus._t);
              setStatus._t = setTimeout(function () { status.textContent = ''; }, 2500);
            }
            function copyText(text) {
              if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(text).catch(function () { fallbackCopy(text); });
                return;
              }
              fallbackCopy(text);
            }
            function fallbackCopy(text) {
              var t = document.createElement('textarea');
              t.value = text; t.style.position = 'fixed'; t.style.left = '-9999px';
              document.body.appendChild(t); t.select();
              try { document.execCommand('copy'); } catch (e) {}
              document.body.removeChild(t);
            }

            function resize() {
              W = stage.clientWidth;
              H = stage.clientHeight;
              canvas.width = W * dpr;
              canvas.height = H * dpr;
              ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
              ctx.lineCap = 'round';
              ctx.lineJoin = 'round';
              redraw();
            }
            window.addEventListener('resize', resize);

            updateToolButtons();
            resize();
            console.log('[DrawnPicture2] Рисовалка инициализирована');
          }

          function initAll() {
            var list = document.querySelectorAll('.drawn-picture2-app');
            console.log('[DrawnPicture2] Найдено контейнеров:', list.length);
            for (var i = 0; i < list.length; i++) initApp(list[i]);
          }
          if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initAll);
          } else {
            initAll();
          }
        })();
        </script>
        <?php
    }
}

new DrawnPicture2Plugin();


Шорткод [ drawn_picture2]

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

🧮 Проверка безопасности
17 + 4 = ?

В Шедевруме


Смотреть все мои промты и картинки

Календарь

Сентябрь 2026
Пн Вт Ср Чт Пт Сб Вс
 123456
78910111213
14151617181920
21222324252627
282930  

Новые комментарии

Контакты