script.js

36.23 KB
06/07/2025 18:00
JS
script.js
class BannerDesigner {
  constructor() {
    this.canvas = document.getElementById('bannerCanvas');
    this.ctx = this.canvas.getContext('2d');
    this.overlay = document.getElementById('overlay');
    this.elements = [];
    this.selectedElement = null;
    this.isDragging = false;
    this.isResizing = false;
    this.resizeHandle = null;
    this.dragOffset = {x: 0, y: 0};
    this.backgroundImage = null;
    this.backgroundColor = '#ffffff';
    this.backgroundOpacity = 1;

    // เพิ่ม History สำหรับ Undo/Redo
    this.history = [];
    this.historyStep = -1;
    this.maxHistorySteps = 50;

    this.init();
  }

  init() {
    this.setupEventListeners();
    this.setupKeyboardShortcuts();
    this.updateCanvasSize(1200, 630);
    this.saveState(); // บันทึกสถานะเริ่มต้น
    this.render();
  }

  setupEventListeners() {
    // Size preset dropdown
    document.getElementById('sizePreset').addEventListener('change', (e) => {
      const value = e.target.value;
      const customInputs = document.getElementById('customSizeInputs');

      if (value === 'custom') {
        customInputs.style.display = 'flex';
      } else {
        customInputs.style.display = 'none';
        const [width, height] = value.split('x').map(num => parseInt(num));
        this.updateCanvasSize(width, height);
      }
    });

    // Custom size
    document.getElementById('applyCustomSize').addEventListener('click', () => {
      const width = parseInt(document.getElementById('customWidth').value);
      const height = parseInt(document.getElementById('customHeight').value);
      if (width > 0 && height > 0) {
        this.updateCanvasSize(width, height);
      }
    });

    // Add Enter key support for custom size inputs
    document.getElementById('customWidth').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        document.getElementById('applyCustomSize').click();
      }
    });

    document.getElementById('customHeight').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        document.getElementById('applyCustomSize').click();
      }
    });

    // Background controls
    document.getElementById('bgColor').addEventListener('change', (e) => {
      this.backgroundColor = e.target.value;
      this.render();
    });

    document.getElementById('bgImage').addEventListener('change', (e) => {
      this.handleBackgroundImage(e.target.files[0]);
    });

    document.getElementById('removeBgImage').addEventListener('click', () => {
      this.backgroundImage = null;
      this.render();
    });

    document.getElementById('bgOpacity').addEventListener('input', (e) => {
      this.backgroundOpacity = e.target.value / 100;
      document.getElementById('opacityValue').textContent = e.target.value + '%';
      this.render();
    });

    // Add text button
    document.getElementById('addText').addEventListener('click', () => {
      this.addTextElement();
    });

    // Add logo
    document.getElementById('logoImage').addEventListener('change', (e) => {
      if (e.target.files[0]) {
        this.addImageElement(e.target.files[0]);
      }
    });

    // Text controls
    this.setupTextControls();

    // Export
    document.getElementById('exportBtn').addEventListener('click', () => {
      this.exportBanner();
    });

    // Canvas mouse events
    this.overlay.style.pointerEvents = 'all';
    this.overlay.addEventListener('mousedown', (e) => this.handleMouseDown(e));
    this.overlay.addEventListener('mousemove', (e) => this.handleMouseMove(e));
    this.overlay.addEventListener('mouseup', (e) => this.handleMouseUp(e));

    // Prevent context menu
    this.overlay.addEventListener('contextmenu', (e) => e.preventDefault());
  }

  // เพิ่มฟังก์ชัน Keyboard Shortcuts
  setupKeyboardShortcuts() {
    document.addEventListener('keydown', (e) => {
      // ป้องกันไม่ให้ทำงานเมื่อพิมพ์ในช่อง input
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;

      if (e.ctrlKey || e.metaKey) {
        switch (e.key.toLowerCase()) {
          case 'z':
            e.preventDefault();
            if (e.shiftKey) {
              this.redo();
            } else {
              this.undo();
            }
            break;
          case 'y':
            e.preventDefault();
            this.redo();
            break;
          case 'c':
            e.preventDefault();
            this.copyElement();
            break;
          case 'v':
            e.preventDefault();
            this.pasteElement();
            break;
          case 'd':
            e.preventDefault();
            this.duplicateElement();
            break;
        }
      } else {
        switch (e.key) {
          case 'Delete':
          case 'Backspace':
            e.preventDefault();
            if (this.selectedElement) {
              this.deleteElement(this.selectedElement);
            }
            break;
          case 'Escape':
            this.selectedElement = null;
            this.updateTextControls();
            this.render();
            break;
        }
      }
    });
  }

  // ฟังก์ชันจัดการ History
  saveState() {
    // ลบ history ที่เกินขั้นตอนปัจจุบัน
    this.history = this.history.slice(0, this.historyStep + 1);

    // เพิ่มสถานะใหม่
    const state = {
      elements: JSON.parse(JSON.stringify(this.elements.map(el => {
        if (el.type === 'image') {
          // สำหรับรูปภาพ เก็บเฉพาะข้อมูลที่จำเป็น (ไม่รวม image object)
          return {
            ...el,
            imageData: el.image ? el.image.src : null,
            image: null
          };
        }
        return el;
      }))),
      backgroundColor: this.backgroundColor,
      backgroundOpacity: this.backgroundOpacity,
      backgroundImageData: this.backgroundImage ? this.backgroundImage.src : null,
      canvasWidth: this.canvas.width,
      canvasHeight: this.canvas.height
    };

    this.history.push(state);
    this.historyStep++;

    // จำกัดขนาด history
    if (this.history.length > this.maxHistorySteps) {
      this.history.shift();
      this.historyStep--;
    }
  }

  undo() {
    if (this.historyStep > 0) {
      this.historyStep--;
      this.restoreState();
    }
  }

  redo() {
    if (this.historyStep < this.history.length - 1) {
      this.historyStep++;
      this.restoreState();
    }
  }

  restoreState() {
    const state = this.history[this.historyStep];
    if (!state) return;

    this.backgroundColor = state.backgroundColor;
    this.backgroundOpacity = state.backgroundOpacity;

    // อัพเดต Canvas size
    if (state.canvasWidth && state.canvasHeight) {
      this.updateCanvasSize(state.canvasWidth, state.canvasHeight);
    }

    // กู้คืนพื้นหลัง
    if (state.backgroundImageData) {
      const img = new Image();
      img.onload = () => {
        this.backgroundImage = img;
        this.render();
      };
      img.src = state.backgroundImageData;
    } else {
      this.backgroundImage = null;
    }

    // กู้คืน Elements
    this.elements = [];
    state.elements.forEach(elementData => {
      if (elementData.type === 'image' && elementData.imageData) {
        const img = new Image();
        img.onload = () => {
          elementData.image = img;
          this.elements.push(elementData);
          this.render();
        };
        img.src = elementData.imageData;
      } else {
        this.elements.push(elementData);
      }
    });

    this.selectedElement = null;
    this.updateTextControls();
    this.render();
  }

  // ฟังก์ชัน Copy & Paste
  copyElement() {
    if (this.selectedElement) {
      this.copiedElement = JSON.parse(JSON.stringify(this.selectedElement));
      // สำหรับรูปภาพ เก็บ imageData แทน image object
      if (this.copiedElement.type === 'image' && this.selectedElement.image) {
        this.copiedElement.imageData = this.selectedElement.image.src;
        delete this.copiedElement.image;
      }
    }
  }

  pasteElement() {
    if (this.copiedElement) {
      const element = JSON.parse(JSON.stringify(this.copiedElement));
      element.id = Date.now();
      element.x += 20; // เลื่อนตำแหน่งนิดหน่อย
      element.y += 20;

      if (element.type === 'image' && element.imageData) {
        const img = new Image();
        img.onload = () => {
          element.image = img;
          delete element.imageData;
          this.elements.push(element);
          this.selectElement(element);
          this.saveState();
          this.render();
        };
        img.src = element.imageData;
      } else {
        this.elements.push(element);
        this.selectElement(element);
        this.saveState();
        this.render();
      }
    }
  }

  duplicateElement() {
    if (this.selectedElement) {
      this.copyElement();
      this.pasteElement();
    }
  }

  updateCanvasSize(width, height) {
    this.canvas.width = width;
    this.canvas.height = height;
    this.canvas.style.maxWidth = '100%';
    this.canvas.style.height = 'auto';
    document.getElementById('canvasSize').textContent = `${width} × ${height} px`;

    // Update custom size inputs
    document.getElementById('customWidth').value = width;
    document.getElementById('customHeight').value = height;

    // Update size preset dropdown
    const sizePreset = document.getElementById('sizePreset');
    const sizeValue = `${width}x${height}`;
    const customInputs = document.getElementById('customSizeInputs');

    // Check if this is a preset size
    const presetOption = Array.from(sizePreset.options).find(option => option.value === sizeValue);

    if (presetOption) {
      sizePreset.value = sizeValue;
      customInputs.style.display = 'none';
    } else {
      sizePreset.value = 'custom';
      customInputs.style.display = 'block';
    }

    this.render();
  }

  handleBackgroundImage(file) {
    if (!file) return;

    const reader = new FileReader();
    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        this.backgroundImage = img;
        this.render();
      };
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);
  }

  addTextElement() {
    const element = {
      type: 'text',
      id: Date.now(),
      x: this.canvas.width / 2 - 100,
      y: this.canvas.height / 2 - 15,
      text: 'ข้อความใหม่',
      fontSize: 24,
      fontFamily: 'Kanit',
      color: '#000000',
      textAlign: 'left',
      bold: false,
      italic: false,
      underline: false,
      width: 200,
      height: 30,
      manuallyResized: false,
      minWidth: 50,
      minHeight: 20,
      // เพิ่มคุณสมบัติใหม่
      opacity: 1,
      rotation: 0,
      shadow: {
        enabled: false,
        color: '#000000',
        blur: 5,
        offsetX: 2,
        offsetY: 2
      },
      stroke: {
        enabled: false,
        color: '#000000',
        width: 1
      }
    };

    // Calculate initial text dimensions
    this.ctx.font = `${element.fontSize}px ${element.fontFamily}`;
    const textMetrics = this.ctx.measureText(element.text);
    element.width = Math.max(textMetrics.width + 10, element.minWidth);
    element.height = Math.max(element.fontSize * 1.2, element.minHeight);

    this.elements.push(element);
    this.selectElement(element);
    this.saveState(); // บันทึกสถานะหลังเพิ่มองค์ประกอบ
    this.render();
  }

  addImageElement(file) {
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        const maxSize = 200;
        let width = Math.min(img.width, maxSize);
        let height = Math.min(img.height, maxSize);

        // Maintain aspect ratio
        const aspectRatio = img.width / img.height;
        if (width / height > aspectRatio) {
          width = height * aspectRatio;
        } else {
          height = width / aspectRatio;
        }

        const element = {
          type: 'image',
          id: Date.now(),
          x: this.canvas.width / 2 - width / 2,
          y: this.canvas.height / 2 - height / 2,
          image: img,
          width: width,
          height: height,
          originalWidth: img.width,
          originalHeight: img.height,
          aspectRatio: aspectRatio
        };

        this.elements.push(element);
        this.selectElement(element);
        this.render();
      };
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);
  }

  selectElement(element) {
    this.selectedElement = element;
    this.updateTextControls();
  }

  setupTextControls() {
    const textContent = document.getElementById('textContent');
    const fontFamily = document.getElementById('fontFamily');
    const fontSize = document.getElementById('fontSize');
    const textColor = document.getElementById('textColor');
    const textAlign = document.getElementById('textAlign');
    const boldBtn = document.getElementById('boldBtn');
    const italicBtn = document.getElementById('italicBtn');
    const underlineBtn = document.getElementById('underlineBtn');
    const deleteBtn = document.getElementById('deleteElement');

    textContent.addEventListener('input', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.text = textContent.value;
        // Reset manual resize flag when text changes
        if (!this.selectedElement.manuallyResized) {
          this.selectedElement.manuallyResized = false;
        }
        this.render();
      }
    });

    fontFamily.addEventListener('change', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.fontFamily = fontFamily.value;
        this.render();
      }
    });

    fontSize.addEventListener('input', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.fontSize = parseInt(fontSize.value);
        this.selectedElement.manuallyResized = true; // Mark as manually resized when font size changes
        document.getElementById('fontSizeValue').textContent = fontSize.value + 'px';
        this.render();
      }
    });

    textColor.addEventListener('change', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.color = textColor.value;
        this.render();
      }
    });

    textAlign.addEventListener('change', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.textAlign = textAlign.value;
        this.render();
      }
    });

    boldBtn.addEventListener('click', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.bold = !this.selectedElement.bold;
        boldBtn.classList.toggle('active', this.selectedElement.bold);
        this.render();
      }
    });

    italicBtn.addEventListener('click', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.italic = !this.selectedElement.italic;
        italicBtn.classList.toggle('active', this.selectedElement.italic);
        this.render();
      }
    });

    underlineBtn.addEventListener('click', () => {
      if (this.selectedElement && this.selectedElement.type === 'text') {
        this.selectedElement.underline = !this.selectedElement.underline;
        underlineBtn.classList.toggle('active', this.selectedElement.underline);
        this.render();
      }
    });

    deleteBtn.addEventListener('click', () => {
      if (this.selectedElement) {
        this.deleteElement(this.selectedElement);
      }
    });
  }

  updateTextControls() {
    const textControls = document.getElementById('textControls');

    if (this.selectedElement && this.selectedElement.type === 'text') {
      textControls.style.display = 'block';

      document.getElementById('textContent').value = this.selectedElement.text;
      document.getElementById('fontFamily').value = this.selectedElement.fontFamily;
      document.getElementById('fontSize').value = this.selectedElement.fontSize;
      document.getElementById('fontSizeValue').textContent = this.selectedElement.fontSize + 'px';
      document.getElementById('textColor').value = this.selectedElement.color;
      document.getElementById('textAlign').value = this.selectedElement.textAlign;

      document.getElementById('boldBtn').classList.toggle('active', this.selectedElement.bold);
      document.getElementById('italicBtn').classList.toggle('active', this.selectedElement.italic);
      document.getElementById('underlineBtn').classList.toggle('active', this.selectedElement.underline);
    } else {
      textControls.style.display = this.selectedElement ? 'block' : 'none';
      if (this.selectedElement) {
        // Show only delete button for non-text elements
        textControls.innerHTML = '<h3>✏️ แก้ไของค์ประกอบ</h3><button id="deleteElement" class="delete-btn">🗑️ ลบองค์ประกอบ</button>';
        document.getElementById('deleteElement').addEventListener('click', () => {
          if (this.selectedElement) {
            this.deleteElement(this.selectedElement);
          }
        });
      }
    }
  }

  deleteElement(element) {
    const index = this.elements.indexOf(element);
    if (index > -1) {
      this.elements.splice(index, 1);
      this.selectedElement = null;
      this.updateTextControls();
      this.render();
    }
  }

  getCanvasRect() {
    return this.canvas.getBoundingClientRect();
  }

  getMousePos(e) {
    const rect = this.getCanvasRect();
    const scaleX = this.canvas.width / rect.width;
    const scaleY = this.canvas.height / rect.height;

    return {
      x: (e.clientX - rect.left) * scaleX,
      y: (e.clientY - rect.top) * scaleY
    };
  }

  handleMouseDown(e) {
    const mousePos = this.getMousePos(e);
    let elementClicked = false;

    // Check for resize handle first
    if (this.selectedElement) {
      const handle = this.getResizeHandle(mousePos, this.selectedElement);
      if (handle) {
        this.isResizing = true;
        this.resizeHandle = handle;
        this.dragOffset = {x: mousePos.x, y: mousePos.y};
        return;
      }
    }

    // Check if clicking on an element (reverse order to check top elements first)
    for (let i = this.elements.length - 1; i >= 0; i--) {
      const element = this.elements[i];

      if (this.isPointInElement(mousePos, element)) {
        this.selectElement(element);
        this.isDragging = true;
        this.dragOffset = {
          x: mousePos.x - element.x,
          y: mousePos.y - element.y
        };
        elementClicked = true;
        break;
      }
    }

    if (!elementClicked) {
      this.selectedElement = null;
      this.updateTextControls();
      this.render();
    }
  }

  handleMouseMove(e) {
    const mousePos = this.getMousePos(e);

    if (this.isResizing && this.selectedElement) {
      this.handleResize(mousePos);
    } else if (this.isDragging && this.selectedElement) {
      this.selectedElement.x = mousePos.x - this.dragOffset.x;
      this.selectedElement.y = mousePos.y - this.dragOffset.y;

      // Keep elements within canvas bounds
      this.selectedElement.x = Math.max(0, Math.min(this.canvas.width - this.selectedElement.width, this.selectedElement.x));
      this.selectedElement.y = Math.max(0, Math.min(this.canvas.height - this.selectedElement.height, this.selectedElement.y));

      this.render();
    } else if (this.selectedElement) {
      // Update cursor based on resize handle hover
      const handle = this.getResizeHandle(mousePos, this.selectedElement);
      if (handle) {
        this.overlay.style.cursor = this.getResizeCursor(handle);
      } else if (this.isPointInElement(mousePos, this.selectedElement)) {
        this.overlay.style.cursor = 'move';
      } else {
        this.overlay.style.cursor = 'default';
      }
    }
  }

  handleMouseUp(e) {
    this.isDragging = false;
    this.isResizing = false;
    this.resizeHandle = null;
    this.overlay.style.cursor = 'default';
  }

  isPointInElement(point, element) {
    return point.x >= element.x &&
      point.x <= element.x + element.width &&
      point.y >= element.y &&
      point.y <= element.y + element.height;
  }

  render() {
    // Clear canvas
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

    // Draw background
    this.drawBackground();

    // Draw all elements
    this.elements.forEach(element => {
      this.drawElement(element);
    });

    // Draw selection border
    if (this.selectedElement) {
      this.drawSelectionBorder(this.selectedElement);
    }
  }

  drawBackground() {
    // Fill with background color
    this.ctx.save();
    this.ctx.globalAlpha = this.backgroundOpacity;
    this.ctx.fillStyle = this.backgroundColor;
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.restore();

    // Draw background image if exists
    if (this.backgroundImage) {
      this.ctx.save();
      this.ctx.globalAlpha = this.backgroundOpacity;

      // Calculate scale to cover entire canvas
      const scale = Math.max(
        this.canvas.width / this.backgroundImage.width,
        this.canvas.height / this.backgroundImage.height
      );

      const scaledWidth = this.backgroundImage.width * scale;
      const scaledHeight = this.backgroundImage.height * scale;

      const x = (this.canvas.width - scaledWidth) / 2;
      const y = (this.canvas.height - scaledHeight) / 2;

      this.ctx.drawImage(this.backgroundImage, x, y, scaledWidth, scaledHeight);
      this.ctx.restore();
    }
  }

  drawElement(element) {
    this.ctx.save();

    if (element.type === 'text') {
      this.drawTextElement(element);
    } else if (element.type === 'image') {
      this.drawImageElement(element);
    }

    this.ctx.restore();
  }

  drawTextElement(element) {
    // Set font properties
    let fontStyle = '';
    if (element.italic) fontStyle += 'italic ';
    if (element.bold) fontStyle += 'bold ';

    this.ctx.font = `${fontStyle}${element.fontSize}px ${element.fontFamily}`;
    this.ctx.fillStyle = element.color;
    this.ctx.textBaseline = 'top';

    // Calculate text metrics
    const textMetrics = this.ctx.measureText(element.text);
    const textWidth = textMetrics.width;
    const textHeight = element.fontSize * 1.2;

    // Update element dimensions if not manually resized
    if (!element.manuallyResized) {
      element.width = Math.max(textWidth + 10, element.minWidth || 50);
      element.height = Math.max(textHeight, element.minHeight || 20);
    }

    // Calculate text position based on alignment and bounding box
    let textX, textY;

    switch (element.textAlign) {
      case 'center':
        textX = element.x + element.width / 2;
        this.ctx.textAlign = 'center';
        break;
      case 'right':
        textX = element.x + element.width - 5;
        this.ctx.textAlign = 'right';
        break;
      default: // left
        textX = element.x + 5;
        this.ctx.textAlign = 'left';
        break;
    }

    // Center text vertically in the bounding box
    textY = element.y + (element.height - textHeight) / 2;

    // Draw text
    this.ctx.fillText(element.text, textX, textY);

    // Draw underline if needed
    if (element.underline) {
      this.ctx.beginPath();
      this.ctx.moveTo(element.x + 5, textY + textHeight - 2);
      this.ctx.lineTo(element.x + element.width - 5, textY + textHeight - 2);
      this.ctx.strokeStyle = element.color;
      this.ctx.lineWidth = 1;
      this.ctx.stroke();
    }
  }

  drawImageElement(element) {
    this.ctx.drawImage(
      element.image,
      element.x,
      element.y,
      element.width,
      element.height
    );
  } drawSelectionBorder(element) {
    this.ctx.save();
    this.ctx.strokeStyle = '#667eea';
    this.ctx.lineWidth = 2;
    this.ctx.setLineDash([5, 5]);

    // Draw different border styles for different element types
    if (element.type === 'text') {
      // Draw background for text elements to show the text area
      this.ctx.fillStyle = 'rgba(102, 126, 234, 0.1)';
      this.ctx.fillRect(element.x, element.y, element.width, element.height);
    }

    this.ctx.strokeRect(element.x - 2, element.y - 2, element.width + 4, element.height + 4);

    // Draw resize handles
    this.drawResizeHandles(element);

    this.ctx.restore();
  }

  drawResizeHandles(element) {
    const handleSize = 8;
    const handles = [
      {x: element.x - handleSize / 2, y: element.y - handleSize / 2},
      {x: element.x + element.width - handleSize / 2, y: element.y - handleSize / 2},
      {x: element.x - handleSize / 2, y: element.y + element.height - handleSize / 2},
      {x: element.x + element.width - handleSize / 2, y: element.y + element.height - handleSize / 2},
      {x: element.x + element.width / 2 - handleSize / 2, y: element.y - handleSize / 2},
      {x: element.x + element.width / 2 - handleSize / 2, y: element.y + element.height - handleSize / 2},
      {x: element.x - handleSize / 2, y: element.y + element.height / 2 - handleSize / 2},
      {x: element.x + element.width - handleSize / 2, y: element.y + element.height / 2 - handleSize / 2}
    ];

    this.ctx.fillStyle = '#667eea';
    this.ctx.strokeStyle = 'white';
    this.ctx.lineWidth = 2;
    this.ctx.setLineDash([]);

    handles.forEach(handle => {
      this.ctx.beginPath();
      this.ctx.arc(handle.x + handleSize / 2, handle.y + handleSize / 2, handleSize / 2, 0, 2 * Math.PI);
      this.ctx.fill();
      this.ctx.stroke();
    });
  }

  exportBanner() {
    const format = document.getElementById('exportFormat').value;
    let mimeType;
    let filename;

    switch (format) {
      case 'png':
        mimeType = 'image/png';
        filename = 'banner.png';
        break;
      case 'jpeg':
        mimeType = 'image/jpeg';
        filename = 'banner.jpg';
        break;
      case 'webp':
        mimeType = 'image/webp';
        filename = 'banner.webp';
        break;
    }

    // Create temporary canvas for export (without selection borders)
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = this.canvas.width;
    tempCanvas.height = this.canvas.height;
    const tempCtx = tempCanvas.getContext('2d');

    // Draw background
    tempCtx.save();
    tempCtx.globalAlpha = this.backgroundOpacity;
    tempCtx.fillStyle = this.backgroundColor;
    tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
    tempCtx.restore();

    if (this.backgroundImage) {
      tempCtx.save();
      tempCtx.globalAlpha = this.backgroundOpacity;

      const scale = Math.max(
        tempCanvas.width / this.backgroundImage.width,
        tempCanvas.height / this.backgroundImage.height
      );

      const scaledWidth = this.backgroundImage.width * scale;
      const scaledHeight = this.backgroundImage.height * scale;

      const x = (tempCanvas.width - scaledWidth) / 2;
      const y = (tempCanvas.height - scaledHeight) / 2;

      tempCtx.drawImage(this.backgroundImage, x, y, scaledWidth, scaledHeight);
      tempCtx.restore();
    }

    // Draw elements
    this.elements.forEach(element => {
      tempCtx.save();

      if (element.type === 'text') {
        let fontStyle = '';
        if (element.italic) fontStyle += 'italic ';
        if (element.bold) fontStyle += 'bold ';

        tempCtx.font = `${fontStyle}${element.fontSize}px ${element.fontFamily}`;
        tempCtx.fillStyle = element.color;
        tempCtx.textAlign = element.textAlign;
        tempCtx.textBaseline = 'top';

        let textX = element.x;
        if (element.textAlign === 'center') {
          textX = element.x + element.width / 2;
        } else if (element.textAlign === 'right') {
          textX = element.x + element.width;
        }

        tempCtx.fillText(element.text, textX, element.y);

        if (element.underline) {
          tempCtx.beginPath();
          tempCtx.moveTo(element.x, element.y + element.height - 2);
          tempCtx.lineTo(element.x + element.width, element.y + element.height - 2);
          tempCtx.strokeStyle = element.color;
          tempCtx.lineWidth = 1;
          tempCtx.stroke();
        }
      } else if (element.type === 'image') {
        tempCtx.drawImage(
          element.image,
          element.x,
          element.y,
          element.width,
          element.height
        );
      }

      tempCtx.restore();
    });

    // Convert to blob and download
    tempCanvas.toBlob((blob) => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }, mimeType, 0.9);
  }

  getResizeHandle(mousePos, element) {
    const handleSize = 8;
    const handles = [
      {name: 'nw', x: element.x - handleSize / 2, y: element.y - handleSize / 2},
      {name: 'ne', x: element.x + element.width - handleSize / 2, y: element.y - handleSize / 2},
      {name: 'sw', x: element.x - handleSize / 2, y: element.y + element.height - handleSize / 2},
      {name: 'se', x: element.x + element.width - handleSize / 2, y: element.y + element.height - handleSize / 2},
      {name: 'n', x: element.x + element.width / 2 - handleSize / 2, y: element.y - handleSize / 2},
      {name: 's', x: element.x + element.width / 2 - handleSize / 2, y: element.y + element.height - handleSize / 2},
      {name: 'w', x: element.x - handleSize / 2, y: element.y + element.height / 2 - handleSize / 2},
      {name: 'e', x: element.x + element.width - handleSize / 2, y: element.y + element.height / 2 - handleSize / 2}
    ];

    for (const handle of handles) {
      if (mousePos.x >= handle.x && mousePos.x <= handle.x + handleSize &&
        mousePos.y >= handle.y && mousePos.y <= handle.y + handleSize) {
        return handle.name;
      }
    }
    return null;
  }

  getResizeCursor(handle) {
    const cursors = {
      'nw': 'nw-resize',
      'ne': 'ne-resize',
      'sw': 'sw-resize',
      'se': 'se-resize',
      'n': 'n-resize',
      's': 's-resize',
      'w': 'w-resize',
      'e': 'e-resize'
    };
    return cursors[handle] || 'default';
  }

  handleResize(mousePos) {
    const element = this.selectedElement;
    const startX = this.dragOffset.x;
    const startY = this.dragOffset.y;
    const deltaX = mousePos.x - startX;
    const deltaY = mousePos.y - startY;

    const minSize = element.type === 'text' ? (element.minWidth || 50) : 20;
    const minHeight = element.type === 'text' ? (element.minHeight || 20) : 20;

    const originalX = element.x;
    const originalY = element.y;
    const originalWidth = element.width;
    const originalHeight = element.height;

    // Mark text elements as manually resized
    if (element.type === 'text') {
      element.manuallyResized = true;
    }

    // For images, maintain aspect ratio when using corner handles
    const isCornerHandle = ['nw', 'ne', 'sw', 'se'].includes(this.resizeHandle);
    const maintainAspectRatio = element.type === 'image' && isCornerHandle;

    // Store original font size for text elements
    const originalFontSize = element.type === 'text' ? element.fontSize : null;

    switch (this.resizeHandle) {
      case 'se':
        if (maintainAspectRatio) {
          const scale = Math.max(
            (originalWidth + deltaX) / originalWidth,
            (originalHeight + deltaY) / originalHeight
          );
          element.width = Math.max(minSize, originalWidth * scale);
          element.height = Math.max(minHeight, originalHeight * scale);
        } else {
          element.width = Math.max(minSize, originalWidth + deltaX);
          element.height = Math.max(minHeight, originalHeight + deltaY);
        }
        break;
      case 'sw':
        if (maintainAspectRatio) {
          const scale = Math.max(
            (originalWidth - deltaX) / originalWidth,
            (originalHeight + deltaY) / originalHeight
          );
          const newWidth = Math.max(minSize, originalWidth * scale);
          const newHeight = Math.max(minHeight, originalHeight * scale);
          element.x = originalX + originalWidth - newWidth;
          element.width = newWidth;
          element.height = newHeight;
        } else {
          const newWidth = Math.max(minSize, originalWidth - deltaX);
          element.x = originalX + originalWidth - newWidth;
          element.width = newWidth;
          element.height = Math.max(minHeight, originalHeight + deltaY);
        }
        break;
      case 'ne':
        if (maintainAspectRatio) {
          const scale = Math.max(
            (originalWidth + deltaX) / originalWidth,
            (originalHeight - deltaY) / originalHeight
          );
          const newWidth = Math.max(minSize, originalWidth * scale);
          const newHeight = Math.max(minHeight, originalHeight * scale);
          element.y = originalY + originalHeight - newHeight;
          element.width = newWidth;
          element.height = newHeight;
        } else {
          element.width = Math.max(minSize, originalWidth + deltaX);
          const newHeight = Math.max(minHeight, originalHeight - deltaY);
          element.y = originalY + originalHeight - newHeight;
          element.height = newHeight;
        }
        break;
      case 'nw':
        if (maintainAspectRatio) {
          const scale = Math.max(
            (originalWidth - deltaX) / originalWidth,
            (originalHeight - deltaY) / originalHeight
          );
          const newW = Math.max(minSize, originalWidth * scale);
          const newH = Math.max(minHeight, originalHeight * scale);
          element.x = originalX + originalWidth - newW;
          element.y = originalY + originalHeight - newH;
          element.width = newW;
          element.height = newH;
        } else {
          const newW = Math.max(minSize, originalWidth - deltaX);
          const newH = Math.max(minHeight, originalHeight - deltaY);
          element.x = originalX + originalWidth - newW;
          element.y = originalY + originalHeight - newH;
          element.width = newW;
          element.height = newH;
        }
        break;
      case 'n':
        const newHeightN = Math.max(minHeight, originalHeight - deltaY);
        element.y = originalY + originalHeight - newHeightN;
        element.height = newHeightN;
        break;
      case 's':
        element.height = Math.max(minHeight, originalHeight + deltaY);
        break;
      case 'w':
        const newWidthW = Math.max(minSize, originalWidth - deltaX);
        element.x = originalX + originalWidth - newWidthW;
        element.width = newWidthW;
        break;
      case 'e':
        element.width = Math.max(minSize, originalWidth + deltaX);
        break;
    }

    // Keep element within canvas bounds
    if (element.x < 0) {
      element.width += element.x;
      element.x = 0;
    }
    if (element.y < 0) {
      element.height += element.y;
      element.y = 0;
    }
    if (element.x + element.width > this.canvas.width) {
      element.width = this.canvas.width - element.x;
    }
    if (element.y + element.height > this.canvas.height) {
      element.height = this.canvas.height - element.y;
    }

    // For text elements, update font size proportionally when using corner resize
    if (element.type === 'text' && isCornerHandle && originalFontSize) {
      const widthScale = element.width / originalWidth;
      const heightScale = element.height / originalHeight;
      const scale = Math.min(widthScale, heightScale);

      // Calculate new font size
      const newFontSize = Math.max(12, Math.min(200, Math.round(originalFontSize * scale)));
      element.fontSize = newFontSize;

      // Update UI controls
      const fontSizeSlider = document.getElementById('fontSize');
      const fontSizeValue = document.getElementById('fontSizeValue');
      if (fontSizeSlider && fontSizeValue) {
        fontSizeSlider.value = newFontSize;
        fontSizeValue.textContent = newFontSize + 'px';
      }
    }

    // Update drag offset for smooth resizing
    this.dragOffset.x = mousePos.x;
    this.dragOffset.y = mousePos.y;

    this.render();
  }
}

// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
  new BannerDesigner();
});