{% extends "base.html" %}

{% block title %}{{ _('PDF Quote Designer') }}{% endblock %}

{% block extra_css %}
<style>
/* Main Layout */
.designer-layout {
    display: grid;
    grid-template-columns: 250px 1fr 400px;
    gap: 1rem;
    min-height: 700px;
}

@media (max-width: 1536px) {
    .designer-layout {
        grid-template-columns: 200px 1fr 350px;
    }
}

@media (max-width: 1280px) {
    .designer-layout {
        grid-template-columns: 1fr;
    }
    .sidebar, .properties-panel {
        display: none;
    }
}

/* Sidebar - Elements */
.sidebar {
    background: #FFFFFF;
    border: 1px solid #E2E8F0;
    border-radius: 0.75rem;
    padding: 1.5rem;
    color: #1F2937;
    overflow-y: auto;
    max-height: 700px;
    display: flex;
    flex-direction: column;
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.dark .sidebar {
    background: #1F2937;
    border-color: #4A5568;
    color: #E2E8F0;
}

.sidebar h3 {
    font-size: 1rem;
    font-weight: 600;
    margin: 0 0 1rem 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: #1F2937;
}

.dark .sidebar h3 {
    color: #E2E8F0;
}

/* Search Box */
.search-box {
    margin-bottom: 1rem;
}

.search-box input {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    border: 1px solid #E2E8F0;
    background: #FFFFFF;
    color: #1F2937;
    font-size: 0.875rem;
    transition: all 0.2s;
}

.dark .search-box input {
    background: #2D3748;
    border-color: #4A5568;
    color: #E2E8F0;
}

.search-box input::placeholder {
    color: #A0AEC0;
}

.dark .search-box input::placeholder {
    color: #718096;
}

.search-box input:focus {
    outline: none;
    border-color: #3B82F6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.dark .search-box input:focus {
    border-color: #3B82F6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}

/* Tabs */
.sidebar-tabs {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
    border-bottom: 2px solid #E2E8F0;
}

.dark .sidebar-tabs {
    border-bottom-color: #4A5568;
}

.sidebar-tab {
    padding: 0.5rem 1rem;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    font-size: 0.875rem;
    font-weight: 500;
    transition: all 0.2s;
    color: #6B7280;
}

.dark .sidebar-tab {
    color: #9CA3AF;
}

.sidebar-tab:hover {
    color: #1F2937;
}

.dark .sidebar-tab:hover {
    color: #E2E8F0;
}

.sidebar-tab.active {
    color: #3B82F6;
    border-bottom-color: #3B82F6;
}

.dark .sidebar-tab.active {
    color: #60A5FA;
    border-bottom-color: #60A5FA;
}

.sidebar-content {
    flex: 1;
    overflow-y: auto;
}

.tab-pane {
    display: none;
}

.tab-pane.active {
    display: block;
}

.element-group {
    margin-bottom: 1.5rem;
}

.element-group-title {
    font-size: 0.75rem;
    text-transform: uppercase;
    font-weight: 600;
    color: #6B7280;
    margin-bottom: 0.5rem;
    letter-spacing: 0.05em;
}

.dark .element-group-title {
    color: #9CA3AF;
}

.element-item {
    background: #F7F9FB;
    border: 1px solid #E2E8F0;
    border-radius: 0.5rem;
    padding: 0.75rem;
    margin-bottom: 0.5rem;
    cursor: move;
    transition: all 0.2s;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: #1F2937;
}

.dark .element-item {
    background: #2D3748;
    border-color: #4A5568;
    color: #E2E8F0;
}

.element-item:hover {
    background: #EDF2F7;
    border-color: #3B82F6;
    transform: translateX(3px);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.dark .element-item:hover {
    background: #374151;
    border-color: #60A5FA;
}

.element-item i {
    font-size: 1.25rem;
    color: #3B82F6;
}

.dark .element-item i {
    color: #60A5FA;
}

.element-item span {
    font-size: 0.875rem;
    font-weight: 500;
}

/* Variable Items */
.variable-item {
    background: #F7F9FB;
    border: 1px solid #E2E8F0;
    border-radius: 0.375rem;
    padding: 0.5rem;
    margin-bottom: 0.5rem;
    cursor: pointer;
    transition: all 0.2s;
}

.dark .variable-item {
    background: #2D3748;
    border-color: #4A5568;
}

.variable-item:hover {
    background: #EDF2F7;
    border-color: #3B82F6;
}

.dark .variable-item:hover {
    background: #374151;
    border-color: #60A5FA;
}

.variable-name {
    font-family: 'Courier New', monospace;
    font-size: 0.813rem;
    color: #3B82F6;
    margin-bottom: 0.25rem;
    font-weight: 600;
}

.dark .variable-name {
    color: #60A5FA;
}

.variable-desc {
    font-size: 0.75rem;
    color: #6B7280;
}

.dark .variable-desc {
    color: #9CA3AF;
}

/* Canvas Area */
.canvas-area {
    background: white;
    border-radius: 0.75rem;
    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
    padding: 1.5rem;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.canvas-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 1rem;
    padding: 1rem;
    padding-bottom: 1rem;
    border-bottom: 2px solid #e5e7eb;
    background: #f9fafb;
    border-radius: 0.5rem 0.5rem 0 0;
    flex-wrap: wrap;
    gap: 1rem;
}

@media (max-width: 1024px) {
    .canvas-header {
        flex-direction: column;
        align-items: stretch;
    }
    
    .canvas-header h3 {
        margin-bottom: 0.5rem;
    }
}

.dark .canvas-header {
    background: #374151;
    border-bottom-color: #4b5563;
}

.dark .canvas-header h3 {
    color: #f9fafb;
}

.canvas-header h3 {
    font-size: 1rem;
    font-weight: 600;
    color: #1f2937;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
}

.canvas-header-controls {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    flex-wrap: wrap;
}

@media (max-width: 768px) {
    .canvas-header-controls {
        flex-direction: column;
        gap: 0.75rem;
        width: 100%;
    }
}

.page-size-selector-group {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

@media (max-width: 768px) {
    .page-size-selector-group {
        flex-direction: column;
        align-items: flex-start;
        gap: 0.5rem;
        width: 100%;
    }
    
    .page-size-help {
        display: none;
    }
}

.page-size-label {
    font-size: 0.875rem;
    font-weight: 500;
    color: #374151;
    margin: 0;
}

@media (prefers-color-scheme: dark) {
    .page-size-label {
        color: #d1d5db;
    }
}

.page-size-select {
    padding: 0.5rem 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 0.375rem;
    font-size: 0.875rem;
    font-weight: 500;
    background: white;
    color: #1f2937;
    cursor: pointer;
    transition: all 0.2s;
}

.dark .page-size-select {
    background: #374151;
    border-color: #4b5563;
    color: #f9fafb;
}

.dark .page-size-select:hover {
    background: #4b5563;
    border-color: #6b7280;
}

.dark .page-size-select:focus {
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}

.page-size-select:hover {
    border-color: #9ca3af;
    background: #f9fafb;
}

.page-size-select:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

.canvas-toolbar {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    align-items: center;
}

@media (max-width: 768px) {
    .canvas-toolbar {
        width: 100%;
        justify-content: flex-start;
    }
    
    .canvas-toolbar button {
        min-width: 36px;
        padding: 0.5rem;
    }
    
    .canvas-toolbar #zoom-display {
        margin-left: 0.5rem !important;
        font-size: 0.813rem !important;
    }
}

.canvas-toolbar button {
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    border: 1px solid #e5e7eb;
    background: white;
    color: #1f2937;
    cursor: pointer;
    transition: all 0.2s;
    font-size: 0.875rem;
}

.dark .canvas-toolbar button {
    background: #374151;
    border-color: #4b5563;
    color: #f9fafb;
}

.dark .canvas-toolbar button:hover {
    background: #4b5563;
    border-color: #667eea;
}

.canvas-toolbar button:hover {
    background: #f3f4f6;
    border-color: #667eea;
}

#canvas-container {
    flex: 1;
    border: 2px dashed #e5e7eb;
    border-radius: 0.5rem;
    position: relative;
    background: #fafafa;
    overflow: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 600px;
    padding: 20px;
}

#canvas-container > div {
    margin: auto;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    background: white;
    overflow: hidden;
}

/* Preview Modal - Modern Redesign */
.preview-modal {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9999;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0.2s ease;
}

.preview-modal[style*="display: block"] {
    opacity: 1;
    visibility: visible;
}

.preview-modal-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.75);
    backdrop-filter: blur(4px);
    animation: fadeIn 0.2s ease;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.preview-modal-content {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 95%;
    max-width: min(1600px, 98vw);
    max-height: 95vh;
    background: white;
    border-radius: 1rem;
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: slideUp 0.3s ease;
}

@keyframes slideUp {
    from {
        transform: translate(-50%, -45%);
        opacity: 0;
    }
    to {
        transform: translate(-50%, -50%);
        opacity: 1;
    }
}

.dark .preview-modal-content {
    background: #1f2937;
    color: #f9fafb;
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}

/* Enhanced Header */
.preview-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.5rem;
    border-bottom: 1px solid #e5e7eb;
    background: #f9fafb;
    gap: 1rem;
    flex-shrink: 0;
}

.dark .preview-modal-header {
    border-bottom-color: #4b5563;
    background: #111827;
}

.preview-header-left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex: 1;
    min-width: 0;
}

.preview-modal-header h3 {
    font-size: 1.25rem;
    font-weight: 600;
    margin: 0;
    color: #1f2937;
    white-space: nowrap;
}

.dark .preview-modal-header h3 {
    color: #f9fafb;
}

.preview-page-size-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.75rem;
    background: #667eea;
    color: white;
    border-radius: 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.dark .preview-page-size-badge {
    background: #7c3aed;
}

/* Toolbar */
.preview-toolbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-shrink: 0;
}

.preview-toolbar-group {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0 0.75rem;
    border-right: 1px solid #e5e7eb;
}

.dark .preview-toolbar-group {
    border-right-color: #4b5563;
}

.preview-toolbar-group:last-child {
    border-right: none;
    padding-right: 0;
}

.preview-toolbar-select {
    padding: 0.375rem 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 0.5rem;
    background: white;
    color: #1f2937;
    font-size: 0.875rem;
    cursor: pointer;
    transition: all 0.2s;
}

.preview-toolbar-select:hover {
    border-color: #667eea;
}

.preview-toolbar-select:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

.dark .preview-toolbar-select {
    background: #374151;
    border-color: #4b5563;
    color: #f9fafb;
}

.dark .preview-toolbar-select:hover {
    border-color: #7c3aed;
}

.preview-toolbar-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid #d1d5db;
    border-radius: 0.5rem;
    background: white;
    color: #6b7280;
    cursor: pointer;
    transition: all 0.2s;
    font-size: 0.875rem;
}

.preview-toolbar-btn:hover {
    background: #f3f4f6;
    border-color: #9ca3af;
    color: #1f2937;
}

.preview-toolbar-btn:active {
    transform: scale(0.95);
}

.dark .preview-toolbar-btn {
    background: #374151;
    border-color: #4b5563;
    color: #9ca3af;
}

.dark .preview-toolbar-btn:hover {
    background: #4b5563;
    border-color: #6b7280;
    color: #f9fafb;
}

.preview-modal-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    border: none;
    border-radius: 0.5rem;
    background: transparent;
    color: #6b7280;
    cursor: pointer;
    transition: all 0.2s;
    font-size: 1.25rem;
    flex-shrink: 0;
}

.preview-modal-close:hover {
    background: #f3f4f6;
    color: #1f2937;
}

.preview-modal-close:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

.dark .preview-modal-close {
    color: #9ca3af;
}

.dark .preview-modal-close:hover {
    background: #374151;
    color: #f9fafb;
}

/* Enhanced Body */
.preview-modal-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: #e5e7eb;
    min-height: 0;
}

.dark .preview-modal-body {
    background: #1f2937;
}

/* Controls Bar */
.preview-controls-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1rem;
    background: white;
    border-bottom: 1px solid #e5e7eb;
    gap: 1rem;
    flex-wrap: wrap;
    flex-shrink: 0;
}

.dark .preview-controls-bar {
    background: #111827;
    border-bottom-color: #4b5563;
}

.preview-controls-left,
.preview-controls-right {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.preview-control-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 0.5rem;
    background: white;
    color: #6b7280;
    font-size: 0.875rem;
    cursor: pointer;
    transition: all 0.2s;
    white-space: nowrap;
}

.preview-control-btn:hover {
    background: #f3f4f6;
    border-color: #9ca3af;
    color: #1f2937;
}

.preview-control-btn:active {
    transform: scale(0.98);
}

.preview-control-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.preview-control-btn-active {
    background: #667eea !important;
    border-color: #667eea !important;
    color: white !important;
}

.preview-control-btn-active:hover {
    background: #5568d3 !important;
    border-color: #5568d3 !important;
}

.dark .preview-control-btn {
    background: #374151;
    border-color: #4b5563;
    color: #9ca3af;
}

.dark .preview-control-btn:hover:not(:disabled) {
    background: #4b5563;
    border-color: #6b7280;
    color: #f9fafb;
}

.dark .preview-control-btn-active {
    background: #7c3aed !important;
    border-color: #7c3aed !important;
    color: white !important;
}

.dark .preview-control-btn-active:hover {
    background: #6d28d9 !important;
    border-color: #6d28d9 !important;
}

/* Zoom Slider */
.preview-zoom-slider-wrapper {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 200px;
}

.preview-zoom-slider {
    flex: 1;
    height: 0.375rem;
    border-radius: 0.25rem;
    background: #e5e7eb;
    outline: none;
    -webkit-appearance: none;
    appearance: none;
}

.preview-zoom-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    background: #667eea;
    cursor: pointer;
    transition: all 0.2s;
}

.preview-zoom-slider::-webkit-slider-thumb:hover {
    background: #5568d3;
    transform: scale(1.1);
}

.preview-zoom-slider::-moz-range-thumb {
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    background: #667eea;
    cursor: pointer;
    border: none;
    transition: all 0.2s;
}

.preview-zoom-slider::-moz-range-thumb:hover {
    background: #5568d3;
    transform: scale(1.1);
}

.dark .preview-zoom-slider {
    background: #4b5563;
}

.dark .preview-zoom-slider::-webkit-slider-thumb {
    background: #7c3aed;
}

.dark .preview-zoom-slider::-webkit-slider-thumb:hover {
    background: #6d28d9;
}

.dark .preview-zoom-slider::-moz-range-thumb {
    background: #7c3aed;
}

.dark .preview-zoom-slider::-moz-range-thumb:hover {
    background: #6d28d9;
}

.preview-zoom-display {
    min-width: 3.5rem;
    text-align: center;
    font-size: 0.875rem;
    font-weight: 600;
    color: #6b7280;
}

.dark .preview-zoom-display {
    color: #9ca3af;
}

/* Content Wrapper */
.preview-content-wrapper {
    flex: 1;
    position: relative;
    overflow: auto;
    min-height: 0;
    background: #e5e7eb;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 0.5rem;
}

.dark .preview-content-wrapper {
    background: #1f2937;
}

.preview-frame-wrapper {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 1.5rem;
    padding-top: 1rem;
    box-sizing: border-box;
    overflow: auto;
    position: relative;
}

#preview-frame {
    width: 100%;
    height: 100%;
    min-height: 600px;
    border: none;
    border-radius: 0.5rem;
    background: transparent;
    box-shadow: none;
    display: block;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    overflow: visible;
}

.dark #preview-frame {
    background: #374151;
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}

/* Enhanced Loading State */
.preview-loading {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(8px);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 20;
    border-radius: 0.5rem;
    animation: fadeIn 0.2s ease;
}

.dark .preview-loading {
    background: rgba(31, 41, 55, 0.95);
}

.preview-loading.active {
    display: flex;
}

.preview-loading-content {
    text-align: center;
    max-width: 300px;
}

.spinner-large {
    width: 48px;
    height: 48px;
    border: 4px solid #e5e7eb;
    border-top-color: #667eea;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    margin: 0 auto 1rem;
}

.dark .spinner-large {
    border-color: #4b5563;
    border-top-color: #7c3aed;
}

.preview-loading-text {
    font-size: 0.875rem;
    font-weight: 500;
    color: #6b7280;
    margin: 0 0 1rem 0;
}

.dark .preview-loading-text {
    color: #9ca3af;
}

.preview-loading-progress {
    width: 100%;
    height: 4px;
    background: #e5e7eb;
    border-radius: 2px;
    overflow: hidden;
}

.dark .preview-loading-progress {
    background: #4b5563;
}

.preview-loading-progress-bar {
    height: 100%;
    background: linear-gradient(90deg, #667eea, #764ba2);
    border-radius: 2px;
    width: 0%;
    animation: progressPulse 1.5s ease-in-out infinite;
}

@keyframes progressPulse {
    0%, 100% { width: 0%; }
    50% { width: 70%; }
}

.dark .preview-loading-progress-bar {
    background: linear-gradient(90deg, #7c3aed, #9333ea);
}

/* Error State */
.preview-error {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(8px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 20;
    border-radius: 0.5rem;
    animation: fadeIn 0.2s ease;
}

.dark .preview-error {
    background: rgba(31, 41, 55, 0.95);
}

.preview-error-content {
    text-align: center;
    max-width: 400px;
    padding: 2rem;
}

.preview-error-icon {
    font-size: 3rem;
    color: #ef4444;
    margin-bottom: 1rem;
}

.preview-error-title {
    font-size: 1.25rem;
    font-weight: 600;
    color: #1f2937;
    margin: 0 0 0.75rem 0;
}

.dark .preview-error-title {
    color: #f9fafb;
}

.preview-error-message {
    font-size: 0.875rem;
    color: #6b7280;
    margin: 0 0 1.5rem 0;
    line-height: 1.5;
}

.dark .preview-error-message {
    color: #9ca3af;
}

.preview-error-actions {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
}

.preview-error-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.625rem 1.25rem;
    border: none;
    border-radius: 0.5rem;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
}

.preview-error-btn:not(.preview-error-btn-secondary) {
    background: #667eea;
    color: white;
}

.preview-error-btn:not(.preview-error-btn-secondary):hover {
    background: #5568d3;
}

.preview-error-btn-secondary {
    background: #e5e7eb;
    color: #6b7280;
}

.preview-error-btn-secondary:hover {
    background: #d1d5db;
}

.dark .preview-error-btn-secondary {
    background: #374151;
    color: #9ca3af;
}

.dark .preview-error-btn-secondary:hover {
    background: #4b5563;
}

/* Responsive Design */
@media (max-width: 768px) {
    .preview-modal-content {
        width: 100%;
        max-width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
    }
    
    .preview-modal-header {
        padding: 0.75rem 1rem;
        flex-wrap: wrap;
    }
    
    .preview-toolbar {
        order: 3;
        width: 100%;
        margin-top: 0.5rem;
        padding-top: 0.5rem;
        border-top: 1px solid #e5e7eb;
    }
    
    .dark .preview-toolbar {
        border-top-color: #4b5563;
    }
    
    .preview-controls-bar {
        flex-direction: column;
        align-items: stretch;
        gap: 0.75rem;
    }
    
    .preview-controls-left,
    .preview-controls-right {
        flex-wrap: wrap;
        justify-content: center;
    }
    
    .preview-zoom-slider-wrapper {
        width: 100%;
        min-width: auto;
    }
}

/* Screen Reader Only */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

.spinner {
    width: 40px;
    height: 40px;
    border: 3px solid #e5e7eb;
    border-top-color: #667eea;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Properties Panel */
.properties-panel {
    background: white;
    border-radius: 0.75rem;
    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
    padding: 1.5rem;
    max-height: 700px;
    overflow-y: auto;
}

.dark .properties-panel {
    background: #1f2937;
    color: #f9fafb;
}

.dark .properties-panel h3 {
    color: #f9fafb;
    border-bottom-color: #4b5563;
}

.properties-panel h3 {
    font-size: 1rem;
    font-weight: 600;
    margin: 0 0 1rem 0;
    padding-bottom: 1rem;
    border-bottom: 2px solid #e5e7eb;
    color: #1f2937;
}

/* Template Settings Panel */
.template-settings-panel {
    margin-bottom: 1.5rem;
    padding: 1rem;
    background: #f9fafb;
    border-radius: 0.5rem;
    border: 1px solid #e5e7eb;
}

.dark .template-settings-panel {
    background: #2d3748;
    border-color: #4a5568;
}

.template-settings-panel h4 {
    margin: 0 0 0.75rem 0;
    font-size: 0.875rem;
    font-weight: 600;
    color: #374151;
    display: flex;
    align-items: center;
}

.dark .template-settings-panel h4 {
    color: #f9fafb;
}

.template-settings-panel h4 i {
    margin-right: 0.5rem;
}

.template-settings-panel label {
    display: block;
    font-size: 0.813rem;
    font-weight: 500;
    margin-bottom: 0.25rem;
    color: #4b5563;
}

.dark .template-settings-panel label {
    color: #e2e8f0;
}

.template-settings-panel p {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    color: #6b7280;
}

.dark .template-settings-panel p {
    color: #a0aec0;
}

.property-group {
    margin-bottom: 1.5rem;
}

.property-label {
    font-size: 0.813rem;
    font-weight: 500;
    margin-bottom: 0.5rem;
    color: #374151;
}

.dark .property-label {
    color: #d1d5db;
}

.property-input {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    font-size: 0.875rem;
    background: white;
    color: #1f2937;
}

.dark .property-input {
    background: #374151;
    border-color: #4b5563;
    color: #f9fafb;
}

.dark .property-input:disabled {
    background: #1f2937;
    color: #6b7280;
}

.dark .property-input:focus {
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}

.property-input:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

.property-input:disabled {
    background: #f3f4f6;
    color: #6b7280;
    cursor: not-allowed;
}

/* Action Bar */
.action-bar {
    display: flex;
    gap: 0.75rem;
    margin-bottom: 1.5rem;
    flex-wrap: wrap;
    align-items: center;
}

@media (max-width: 768px) {
    .action-bar {
        gap: 0.5rem;
    }
    
    .action-bar .btn {
        font-size: 0.813rem;
        padding: 0.5rem 0.625rem;
    }
    
    .action-bar .btn i {
        margin-right: 0.25rem !important;
    }
    
    .action-bar label.inline-flex {
        font-size: 0.813rem;
        margin-left: 0 !important;
    }
}

/* Button dark mode support */
.dark .btn {
    color: #f9fafb;
}

.dark .btn-secondary {
    background: #4b5563;
    border-color: #6b7280;
    color: #f9fafb;
}

.dark .btn-secondary:hover {
    background: #6b7280;
    border-color: #9ca3af;
}

.dark .btn-primary {
    background: #667eea;
    border-color: #667eea;
}

.dark .btn-primary:hover {
    background: #5568d3;
}

.dark .btn-info {
    background: #0ea5e9;
    border-color: #0ea5e9;
}

.dark .btn-info:hover {
    background: #0284c7;
}

.dark .btn-danger {
    background: #ef4444;
    border-color: #ef4444;
}

.dark .btn-danger:hover {
    background: #dc2626;
}

/* Info Box */
.info-box {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 1rem;
    border-radius: 0.75rem;
    margin-bottom: 1.5rem;
}

.info-box p {
    margin: 0;
    font-size: 0.875rem;
    line-height: 1.6;
}
</style>
{% endblock %}

{% block content %}
<!-- Page Header -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
    <div>
        <h1 class="text-2xl font-bold flex items-center gap-2">
            <i class="fas fa-paint-brush text-purple-600"></i>
            {{ _('Visual Quote Designer') }}
        </h1>
        <p class="text-text-muted-light dark:text-text-muted-dark mt-1">
            {{ _('Drag and drop elements to design your quote layout') }}
        </p>
    </div>
</div>

<!-- Info Box -->
<div class="info-box">
    <p>
        <i class="fas fa-info-circle mr-2"></i>
        <strong>{{ _('How to use:') }}</strong> 
        {{ _('Click elements from the left sidebar to add them to the canvas. Click elements to select and customize them in the properties panel. Use the alignment tools and keyboard shortcuts for faster editing.') }}
    </p>
    <p style="margin-top: 0.5rem; font-size: 0.813rem;">
        <strong>{{ _('Keyboard Shortcuts:') }}</strong>
        <span style="opacity: 0.9;">
            Delete/Backspace = Remove | Ctrl+C = Copy | Ctrl+V = Paste | Ctrl+D = Duplicate | Arrow Keys = Move (+ Shift for 10px steps)
            <span class="block mt-1">{{ _('Ctrl+Z undo; Ctrl+Y or Ctrl+Shift+Z redo (Cmd+Z / Cmd+Shift+Z on Mac). Mouse wheel over the canvas zooms in or out.') }}</span>
        </span>
    </p>
</div>

<!-- Help Section -->
<details class="mb-4 border border-gray-200 dark:border-gray-600 rounded-lg" style="font-size: 0.875rem;">
    <summary class="px-4 py-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-t-lg flex items-center gap-2">
        <i class="fas fa-question-circle text-gray-500"></i>
        <span class="font-medium">{{ _('Help: Quote Items Table & Saving') }}</span>
    </summary>
    <div class="px-4 py-3 pt-0 text-gray-600 dark:text-gray-400 space-y-2">
        <p><strong>{{ _('Quote Items Table:') }}</strong> {{ _('Displays quote line items. Add from Invoice Data in the sidebar.') }}</p>
        <p><strong>{{ _('Saving:') }}</strong> {{ _('Click "Save Design" to persist. If the table disappears after save, try Reset to restore defaults, then re-add the Items Table.') }}</p>
        <p><strong>{{ _('Preview:') }}</strong> {{ _('Use "Generate Preview" to see how the PDF will look with real quote data.') }}</p>
        <p>{{ _('See') }} <code>docs/PDF_LAYOUT_CUSTOMIZATION.md</code> {{ _('for full documentation.') }}</p>
    </div>
</details>

<!-- Action Bar -->
<div class="action-bar">
    <button id="btn-clear" type="button" class="btn btn-secondary">
        <i class="fas fa-eraser mr-2"></i>{{ _('Clear Canvas') }}
    </button>
    <button id="btn-preview" type="button" class="btn btn-info">
        <i class="fas fa-eye mr-2"></i>{{ _('Generate Preview') }}
    </button>
    <button id="btn-save" type="button" class="btn btn-primary">
        <i class="fas fa-save mr-2"></i>{{ _('Save Design') }}
    </button>
    <button id="btn-code" type="button" class="btn btn-secondary">
        <i class="fas fa-code mr-2"></i>{{ _('View Code') }}
    </button>
    <a href="{{ url_for('admin.quote_pdf_layout_export_json', page_size=page_size) }}" class="btn btn-secondary" id="btn-export-json">
        <i class="fas fa-download mr-2"></i>{{ _('Export JSON') }}
    </a>
    <label for="import-json-file" class="btn btn-secondary" style="cursor: pointer; margin: 0;">
        <i class="fas fa-upload mr-2"></i>{{ _('Import JSON') }}
    </label>
    <input type="file" id="import-json-file" accept=".json" style="display: none;">
    <label class="inline-flex items-center ml-4">
        <input type="checkbox" id="snap-to-grid" class="mr-2" checked>
        <span>{{ _('Snap to Grid (10px)') }}</span>
    </label>
    <form id="form-reset" method="POST" action="{{ url_for('admin.quote_pdf_layout_reset') }}" class="inline">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        <button type="button" onclick="confirmResetPdfLayout()" class="btn btn-danger">
            <i class="fas fa-undo mr-2"></i>{{ _('Reset') }}
        </button>
    </form>
</div>

<!-- Main Designer Layout -->
<div class="designer-layout">
    <!-- LEFT: Elements Sidebar -->
    <div class="sidebar">
        <h3>
            <i class="fas fa-cube"></i>
            {{ _('Toolbox') }}
        </h3>
        
        <!-- Search Box -->
        <div class="search-box">
            <input type="text" id="sidebar-search" placeholder="{{ _('Search elements & variables...') }}">
        </div>
        
        <!-- Tabs -->
        <div class="sidebar-tabs">
            <div class="sidebar-tab active" data-tab="elements">
                <i class="fas fa-shapes mr-1"></i>{{ _('Elements') }}
            </div>
            <div class="sidebar-tab" data-tab="variables">
                <i class="fas fa-code mr-1"></i>{{ _('Variables') }}
            </div>
        </div>
        
        <!-- Sidebar Content -->
        <div class="sidebar-content">
            <!-- Elements Tab -->
            <div id="tab-elements" class="tab-pane active">
        <div class="element-group">
                    <div class="element-group-title">{{ _('Basic Elements') }}</div>
                    <div class="element-item" data-type="custom-text">
                        <i class="fas fa-edit"></i>
                        <span>{{ _('Custom Text') }}</span>
                    </div>
            <div class="element-item" data-type="text" data-content="Sample Text">
                <i class="fas fa-font"></i>
                <span>{{ _('Text') }}</span>
            </div>
            <div class="element-item" data-type="heading" data-content="QUOTE">
                <i class="fas fa-heading"></i>
                <span>{{ _('Heading') }}</span>
            </div>
            <div class="element-item" data-type="line">
                <i class="fas fa-minus"></i>
                <span>{{ _('Line') }}</span>
            </div>
                    <div class="element-item" data-type="rectangle">
                        <i class="fas fa-square"></i>
                        <span>{{ _('Rectangle') }}</span>
                    </div>
                    <div class="element-item" data-type="circle">
                        <i class="fas fa-circle"></i>
                        <span>{{ _('Circle') }}</span>
            </div>
        </div>
        
        <div class="element-group">
            <div class="element-group-title">{{ _('Company Info') }}</div>
            <div class="element-item" data-type="logo">
                <i class="fas fa-image"></i>
                <span>{{ _('Company Logo') }}</span>
            </div>
            <div class="element-item" data-type="company-name">
                <i class="fas fa-building"></i>
                <span>{{ _('Company Name') }}</span>
            </div>
            <div class="element-item" data-type="company-info">
                <i class="fas fa-address-card"></i>
                <span>{{ _('Company Details') }}</span>
            </div>
            <div class="element-item" data-type="company-address">
                <i class="fas fa-map-marker-alt"></i>
                <span>{{ _('Address') }}</span>
            </div>
            <div class="element-item" data-type="company-email">
                <i class="fas fa-envelope"></i>
                <span>{{ _('Email') }}</span>
            </div>
            <div class="element-item" data-type="company-phone">
                <i class="fas fa-phone"></i>
                <span>{{ _('Phone') }}</span>
            </div>
            <div class="element-item" data-type="company-website">
                <i class="fas fa-globe"></i>
                <span>{{ _('Website') }}</span>
            </div>
            <div class="element-item" data-type="company-tax-id">
                <i class="fas fa-file-invoice"></i>
                <span>{{ _('Tax ID') }}</span>
            </div>
        </div>
        
        <div class="element-group">
            <div class="element-group-title">{{ _('Quote Data') }}</div>
            <div class="element-item" data-type="invoice-number">
                <i class="fas fa-hashtag"></i>
                <span>{{ _('Quote Number') }}</span>
            </div>
            <div class="element-item" data-type="invoice-date">
                <i class="fas fa-calendar"></i>
                <span>{{ _('Quote Date') }}</span>
            </div>
            <div class="element-item" data-type="due-date">
                <i class="fas fa-calendar-check"></i>
                <span>{{ _('Due Date') }}</span>
            </div>
            <div class="element-item" data-type="invoice-status">
                <i class="fas fa-info-circle"></i>
                <span>{{ _('Status') }}</span>
            </div>
            <div class="element-item" data-type="client-info">
                <i class="fas fa-user"></i>
                <span>{{ _('Client Info') }}</span>
            </div>
            <div class="element-item" data-type="client-name">
                <i class="fas fa-user-circle"></i>
                <span>{{ _('Client Name') }}</span>
            </div>
            <div class="element-item" data-type="client-address">
                <i class="fas fa-map-marker"></i>
                <span>{{ _('Client Address') }}</span>
            </div>
            <div class="element-item" data-type="quote-items-table">
                <i class="fas fa-table"></i>
                <span>{{ _('Quote Items Table') }}</span>
            </div>
            <div class="element-item" data-type="subtotal">
                <i class="fas fa-coins"></i>
                <span>{{ _('Subtotal') }}</span>
            </div>
            <div class="element-item" data-type="tax">
                <i class="fas fa-percent"></i>
                <span>{{ _('Tax') }}</span>
            </div>
            <div class="element-item" data-type="totals">
                <i class="fas fa-calculator"></i>
                <span>{{ _('Total Amount') }}</span>
            </div>
            <div class="element-item" data-type="notes">
                <i class="fas fa-sticky-note"></i>
                <span>{{ _('Notes') }}</span>
            </div>
            <div class="element-item" data-type="terms">
                <i class="fas fa-file-contract"></i>
                <span>{{ _('Terms') }}</span>
            </div>
        </div>
        
                <div class="element-group">
                    <div class="element-group-title">{{ _('Payment & Project') }}</div>
                    <div class="element-item" data-type="payment-date">
                        <i class="fas fa-calendar-check"></i>
                        <span>{{ _('Payment Date') }}</span>
                    </div>
                    <div class="element-item" data-type="payment-method">
                        <i class="fas fa-credit-card"></i>
                        <span>{{ _('Payment Method') }}</span>
                    </div>
                    <div class="element-item" data-type="payment-status">
                        <i class="fas fa-check-circle"></i>
                        <span>{{ _('Payment Status') }}</span>
                    </div>
                    <div class="element-item" data-type="amount-paid">
                        <i class="fas fa-dollar-sign"></i>
                        <span>{{ _('Amount Paid') }}</span>
                    </div>
                    <div class="element-item" data-type="outstanding-amount">
                        <i class="fas fa-exclamation-circle"></i>
                        <span>{{ _('Outstanding') }}</span>
                    </div>
                    <div class="element-item" data-type="project-name">
                        <i class="fas fa-project-diagram"></i>
                        <span>{{ _('Project Name') }}</span>
                    </div>
                    <div class="element-item" data-type="client-email">
                        <i class="fas fa-at"></i>
                        <span>{{ _('Client Email') }}</span>
                    </div>
                    <div class="element-item" data-type="client-phone">
                        <i class="fas fa-mobile-alt"></i>
                        <span>{{ _('Client Phone') }}</span>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Advanced') }}</div>
                    <div class="element-item" data-type="qr-code">
                        <i class="fas fa-qrcode"></i>
                        <span>{{ _('QR Code') }}</span>
                    </div>
                    <div class="element-item" data-type="barcode">
                        <i class="fas fa-barcode"></i>
                        <span>{{ _('Barcode') }}</span>
                    </div>
                    <div class="element-item" data-type="page-number">
                        <i class="fas fa-file-alt"></i>
                        <span>{{ _('Page Number') }}</span>
                    </div>
                    <div class="element-item" data-type="date-now">
                        <i class="fas fa-clock"></i>
                        <span>{{ _('Current Date') }}</span>
                    </div>
                    <div class="element-item" data-type="watermark">
                        <i class="fas fa-stamp"></i>
                        <span>{{ _('Watermark') }}</span>
                    </div>
                    <div class="element-item" data-type="bank-info">
                        <i class="fas fa-university"></i>
                        <span>{{ _('Bank Info') }}</span>
                    </div>
                    <div class="element-item" data-type="currency">
                        <i class="fas fa-money-bill-wave"></i>
                        <span>{{ _('Currency') }}</span>
                    </div>
                    <div class="element-item" data-type="decorative-image">
                        <i class="fas fa-image"></i>
                        <span>{{ _('Decorative Image') }}</span>
                    </div>
                </div>
            </div>
            
            <!-- Variables Tab -->
            <div id="tab-variables" class="tab-pane">
                <div class="element-group">
                    <div class="element-group-title">{{ _('Quote Fields') }}</div>
                    <div class="variable-item" data-variable="invoice.invoice_number">
                        <div class="variable-name">{{ '{{' }} invoice.invoice_number {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote number') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.status">
                        <div class="variable-name">{{ '{{' }} invoice.status {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote status (draft/sent/paid/overdue)') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_date(invoice.issue_date)">
                        <div class="variable-name">{{ '{{' }} format_date(invoice.issue_date) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Issue date') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_date(invoice.due_date)">
                        <div class="variable-name">{{ '{{' }} format_date(invoice.due_date) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Due date') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(invoice.subtotal)">
                        <div class="variable-name">{{ '{{' }} format_money(invoice.subtotal) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Subtotal amount') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.tax_rate">
                        <div class="variable-name">{{ '{{' }} invoice.tax_rate {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Tax rate (%)') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(invoice.tax_amount)">
                        <div class="variable-name">{{ '{{' }} format_money(invoice.tax_amount) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Tax amount') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(invoice.total_amount)">
                        <div class="variable-name">{{ '{{' }} format_money(invoice.total_amount) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Total amount') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.currency_code">
                        <div class="variable-name">{{ '{{' }} invoice.currency_code {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Currency code (EUR, USD, etc)') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.notes">
                        <div class="variable-name">{{ '{{' }} invoice.notes {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote notes') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.terms">
                        <div class="variable-name">{{ '{{' }} invoice.terms {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Terms & conditions') }}</div>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Client Fields') }}</div>
                    <div class="variable-item" data-variable="invoice.client_name">
                        <div class="variable-name">{{ '{{' }} invoice.client_name {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Client company name') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.client_email">
                        <div class="variable-name">{{ '{{' }} invoice.client_email {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Client email address') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.client_address">
                        <div class="variable-name">{{ '{{' }} invoice.client_address {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Client full address') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.client.contact_person">
                        <div class="variable-name">{{ '{{' }} invoice.client.contact_person {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Client contact person') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.client.phone">
                        <div class="variable-name">{{ '{{' }} invoice.client.phone {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Client phone number') }}</div>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Payment Fields') }}</div>
                    <div class="variable-item" data-variable="quote.status">
                        <div class="variable-name">{{ '{{' }} quote.status {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote status') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_date(quote.accepted_at)">
                        <div class="variable-name">{{ '{{' }} format_date(quote.accepted_at) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote accepted date') }}</div>
                    </div>
                    <div class="variable-item" data-variable="quote.visible_to_client">
                        <div class="variable-name">{{ '{{' }} invoice.payment_method {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Payment method') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.payment_reference">
                        <div class="variable-name">{{ '{{' }} invoice.payment_reference {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Payment reference number') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(invoice.amount_paid)">
                        <div class="variable-name">{{ '{{' }} format_money(invoice.amount_paid) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Amount paid') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(invoice.outstanding_amount)">
                        <div class="variable-name">{{ '{{' }} format_money(invoice.outstanding_amount) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Outstanding amount') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.payment_notes">
                        <div class="variable-name">{{ '{{' }} invoice.payment_notes {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Payment notes') }}</div>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Project Fields') }}</div>
                    <div class="variable-item" data-variable="invoice.project.name">
                        <div class="variable-name">{{ '{{' }} invoice.project.name {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Project name') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.project.code">
                        <div class="variable-name">{{ '{{' }} invoice.project.code {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Project code') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.project.description">
                        <div class="variable-name">{{ '{{' }} invoice.project.description {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Project description') }}</div>
                    </div>
                    <div class="variable-item" data-variable="invoice.project.billing_ref">
                        <div class="variable-name">{{ '{{' }} invoice.project.billing_ref {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Project billing reference') }}</div>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Company/Settings Fields') }}</div>
                    <div class="variable-item" data-variable="settings.company_name">
                        <div class="variable-name">{{ '{{' }} settings.company_name {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your company name') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.company_address">
                        <div class="variable-name">{{ '{{' }} settings.company_address {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your company address') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.company_email">
                        <div class="variable-name">{{ '{{' }} settings.company_email {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your company email') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.company_phone">
                        <div class="variable-name">{{ '{{' }} settings.company_phone {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your company phone') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.company_website">
                        <div class="variable-name">{{ '{{' }} settings.company_website {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your company website') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.company_tax_id">
                        <div class="variable-name">{{ '{{' }} settings.company_tax_id {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your tax ID number') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.company_bank_info">
                        <div class="variable-name">{{ '{{' }} settings.company_bank_info {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Your bank account info') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.invoice_terms">
                        <div class="variable-name">{{ '{{' }} settings.invoice_terms {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Default invoice terms') }}</div>
                    </div>
                    <div class="variable-item" data-variable="settings.invoice_notes">
                        <div class="variable-name">{{ '{{' }} settings.invoice_notes {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Default invoice notes') }}</div>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Date/Time Fields') }}</div>
                    <div class="variable-item" data-variable="format_date(now)">
                        <div class="variable-name">{{ '{{' }} format_date(now) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Current date') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_date(invoice.created_at)">
                        <div class="variable-name">{{ '{{' }} format_date(invoice.created_at) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote creation date') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_date(invoice.updated_at)">
                        <div class="variable-name">{{ '{{' }} format_date(invoice.updated_at) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Quote last update date') }}</div>
                    </div>
                </div>
                
                <div class="element-group">
                    <div class="element-group-title">{{ _('Quote Items Loop') }}</div>
                    <div class="variable-item" data-variable="for-loop-start">
                        <div class="variable-name">{{ '{%' }} for item in invoice.items {{ '%}' }}</div>
                        <div class="variable-desc">{{ _('Loop through invoice items') }}</div>
                    </div>
                    <div class="variable-item" data-variable="item.description">
                        <div class="variable-name">{{ '{{' }} item.description {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Item description') }}</div>
                    </div>
                    <div class="variable-item" data-variable="item.quantity">
                        <div class="variable-name">{{ '{{' }} item.quantity {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Item quantity') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(item.unit_price)">
                        <div class="variable-name">{{ '{{' }} format_money(item.unit_price) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Item unit price') }}</div>
                    </div>
                    <div class="variable-item" data-variable="format_money(item.total_amount)">
                        <div class="variable-name">{{ '{{' }} format_money(item.total_amount) {{ '}}' }}</div>
                        <div class="variable-desc">{{ _('Item total amount') }}</div>
                    </div>
                    <div class="variable-item" data-variable="for-loop-end">
                        <div class="variable-name">{{ '{%' }} endfor {{ '%}' }}</div>
                        <div class="variable-desc">{{ _('End loop') }}</div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- CENTER: Canvas Area -->
    <div class="canvas-area">
        <div class="canvas-header">
            <h3>
                <i class="fas fa-palette"></i>
                {{ _('Design Canvas') }}
            </h3>
            <div class="canvas-header-controls">
                <div class="page-size-selector-group">
                    <label class="page-size-label">{{ _('Page Size:') }}</label>
                    <select id="page-size-selector" class="page-size-select">
                        {% if all_templates %}
                            {% for template in all_templates %}
                                <option value="{{ template.page_size }}" {% if template.page_size == page_size %}selected{% endif %}>
                                    {{ template.page_size }}
                                </option>
                            {% endfor %}
                        {% else %}
                            <option value="A4" {% if page_size == 'A4' %}selected{% endif %}>A4</option>
                            <option value="Letter" {% if page_size == 'Letter' %}selected{% endif %}>Letter</option>
                            <option value="Legal" {% if page_size == 'Legal' %}selected{% endif %}>Legal</option>
                            <option value="A3" {% if page_size == 'A3' %}selected{% endif %}>A3</option>
                            <option value="A5" {% if page_size == 'A5' %}selected{% endif %}>A5</option>
                            <option value="Tabloid" {% if page_size == 'Tabloid' %}selected{% endif %}>Tabloid</option>
                        {% endif %}
                    </select>
                    <div class="page-size-help" style="font-size: 0.75rem; color: #6b7280; margin-top: 0.25rem;">
                        The blue border marks the page boundaries. Zoom changes the view, not the page size.
                    </div>
                </div>
                <div class="canvas-toolbar">
                    <button type="button" id="btn-zoom-in" title="{{ _('Zoom In') }}">
                        <i class="fas fa-search-plus"></i>
                    </button>
                    <button type="button" id="btn-zoom-out" title="{{ _('Zoom Out') }}">
                        <i class="fas fa-search-minus"></i>
                    </button>
                    <span id="zoom-display" style="margin-left: 10px; font-size: 0.875rem; color: #6b7280; align-self: center;"></span>
                    <button type="button" id="btn-delete" title="{{ _('Delete Selected') }}">
                        <i class="fas fa-trash"></i>
                    </button>
                </div>
            </div>
        </div>
        <div id="canvas-container"></div>
    </div>

    <!-- RIGHT: Properties Panel -->
    <div class="properties-panel">
        <h3>
            <i class="fas fa-sliders-h"></i>
            {{ _('Properties') }}
        </h3>
        <!-- Template Settings -->
        <div class="template-settings-panel">
            <h4>
                <i class="fas fa-cog"></i>
                {{ _('Template Settings') }}
            </h4>
            <div style="margin-bottom: 0.75rem;">
                <label for="template-date-format">
                    {{ _('Date Format') }}
                </label>
                <input type="text" id="template-date-format" class="form-input" value="{{ date_format if date_format else '%d.%m.%Y' }}" 
                       style="font-size: 0.813rem; font-family: monospace;"
                       placeholder="%d.%m.%Y">
                <p>
                    {{ _('Use strftime format (e.g., %d.%m.%Y for 12.09.2025)') }}
                </p>
            </div>
        </div>
        <div id="properties-content">
            <p class="text-sm text-gray-500 italic">{{ _('Select an element to edit its properties') }}</p>
        </div>
    </div>
</div>

<!-- Preview Modal -->
<div id="preview-modal" class="preview-modal" style="display: none;" role="dialog" aria-labelledby="preview-modal-title" aria-modal="true">
    <div class="preview-modal-overlay" id="preview-modal-overlay"></div>
    <div class="preview-modal-content">
        <!-- Enhanced Header with Toolbar -->
        <div class="preview-modal-header">
            <div class="preview-header-left">
                <h3 id="preview-modal-title">{{ _('PDF Preview') }}</h3>
                <span class="preview-page-size-badge" id="preview-page-size-badge">A4</span>
            </div>
            <div class="preview-toolbar">
                <div class="preview-toolbar-group">
                    <label for="preview-page-size-select" class="sr-only">{{ _('Page Size') }}</label>
                    <select id="preview-page-size-select" class="preview-toolbar-select" aria-label="{{ _('Page Size') }}">
                        <option value="A4">A4</option>
                        <option value="Letter">Letter</option>
                        <option value="Legal">Legal</option>
                        <option value="A3">A3</option>
                        <option value="A5">A5</option>
                        <option value="Tabloid">Tabloid</option>
                    </select>
                </div>
                <div class="preview-toolbar-group">
                    <button type="button" id="preview-btn-refresh" class="preview-toolbar-btn" title="{{ _('Refresh Preview') }}" aria-label="{{ _('Refresh Preview') }}">
                        <i class="fas fa-sync-alt"></i>
                    </button>
                    <button type="button" id="preview-btn-fullscreen" class="preview-toolbar-btn" title="{{ _('Toggle Fullscreen') }}" aria-label="{{ _('Toggle Fullscreen') }}">
                        <i class="fas fa-expand"></i>
                    </button>
                </div>
            </div>
            <button class="preview-modal-close" id="preview-modal-close" type="button" aria-label="{{ _('Close Preview') }}">
                <i class="fas fa-times"></i>
            </button>
        </div>
        
        <!-- Enhanced Body with Controls -->
        <div class="preview-modal-body">
            <!-- Controls Bar -->
            <div class="preview-controls-bar">
                <div class="preview-controls-left">
                    <button type="button" id="preview-btn-zoom-out" class="preview-control-btn" title="{{ _('Zoom Out') }}" aria-label="{{ _('Zoom Out') }}">
                        <i class="fas fa-search-minus"></i>
                    </button>
                    <div class="preview-zoom-slider-wrapper">
                        <input type="range" id="preview-zoom-slider" class="preview-zoom-slider" min="25" max="200" value="100" step="5" aria-label="{{ _('Zoom Level') }}">
                        <span id="preview-zoom-display" class="preview-zoom-display">100%</span>
                    </div>
                    <button type="button" id="preview-btn-zoom-in" class="preview-control-btn" title="{{ _('Zoom In') }}" aria-label="{{ _('Zoom In') }}">
                        <i class="fas fa-search-plus"></i>
                    </button>
                    <button type="button" id="preview-btn-zoom-reset" class="preview-control-btn" title="{{ _('Reset Zoom') }}" aria-label="{{ _('Reset Zoom') }}">
                        <i class="fas fa-undo"></i>
                    </button>
                </div>
                <div class="preview-controls-right">
                    <button type="button" id="preview-btn-fit-width" class="preview-control-btn" title="{{ _('Fit to Width') }}" aria-label="{{ _('Fit to Width') }}">
                        <i class="fas fa-arrows-alt-h"></i> {{ _('Fit Width') }}
                    </button>
                    <button type="button" id="preview-btn-fit-height" class="preview-control-btn" title="{{ _('Fit to Height') }}" aria-label="{{ _('Fit to Height') }}">
                        <i class="fas fa-arrows-alt-v"></i> {{ _('Fit Height') }}
                    </button>
                    <button type="button" id="preview-btn-actual-size" class="preview-control-btn" title="{{ _('Actual Size') }}" aria-label="{{ _('Actual Size') }}">
                        <i class="fas fa-expand-arrows-alt"></i> {{ _('Actual Size') }}
                    </button>
                </div>
            </div>
            
            <!-- Content Wrapper -->
            <div class="preview-content-wrapper" id="preview-content-wrapper">
                <!-- Loading State -->
                <div class="preview-loading" id="preview-loading">
                    <div class="preview-loading-content">
                        <div class="spinner-large"></div>
                        <p class="preview-loading-text">{{ _('Generating preview...') }}</p>
                        <div class="preview-loading-progress">
                            <div class="preview-loading-progress-bar" id="preview-loading-progress-bar"></div>
                        </div>
                    </div>
                </div>
                
                <!-- Error State -->
                <div class="preview-error" id="preview-error" style="display: none;">
                    <div class="preview-error-content">
                        <i class="fas fa-exclamation-triangle preview-error-icon"></i>
                        <h4 class="preview-error-title">{{ _('Preview Error') }}</h4>
                        <p class="preview-error-message" id="preview-error-message"></p>
                        <div class="preview-error-actions">
                            <button type="button" id="preview-btn-retry" class="preview-error-btn">
                                <i class="fas fa-redo"></i> {{ _('Retry') }}
                            </button>
                            <button type="button" id="preview-btn-close-error" class="preview-error-btn preview-error-btn-secondary">
                                {{ _('Close') }}
                            </button>
                        </div>
                    </div>
                </div>
                
                <!-- Preview Frame -->
                <div class="preview-frame-wrapper" id="preview-frame-wrapper">
                    <iframe id="preview-frame" style="width: 100%; height: 100%; border: none; display: block; background: transparent;" title="{{ _('PDF Preview') }}"></iframe>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Hidden Save Form -->
<form id="form-save" method="POST" action="{{ url_for('admin.quote_pdf_layout') }}" class="hidden">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    <input type="hidden" name="page_size" id="save-page-size" value="{{ page_size }}">
    <textarea id="save-html" name="quote_pdf_template_html"></textarea>
    <textarea id="save-css" name="quote_pdf_template_css"></textarea>
    <textarea id="save-design-json" name="design_json"></textarea>
    <textarea id="save-template-json" name="template_json"></textarea>
    <input type="text" id="save-date-format" name="date_format" value="{{ date_format or '%d.%m.%Y' }}">
</form>

<!-- Code Modal (initially hidden) -->
<div id="code-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
    <div class="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[80vh] overflow-auto">
        <div class="flex justify-between items-center mb-4">
            <h3 class="text-lg font-semibold">{{ _('Generated Code') }}</h3>
            <button type="button" id="close-modal" class="text-gray-500 hover:text-gray-700">
                <i class="fas fa-times text-xl"></i>
            </button>
        </div>
        <div class="space-y-4">
            <div>
                <label class="block text-sm font-medium mb-2">HTML:</label>
                <textarea id="code-html" class="w-full h-48 p-3 border rounded font-mono text-sm" readonly></textarea>
            </div>
            <div>
                <label class="block text-sm font-medium mb-2">CSS:</label>
                <textarea id="code-css" class="w-full h-48 p-3 border rounded font-mono text-sm" readonly></textarea>
            </div>
        </div>
    </div>
</div>
{% endblock %}

{% block scripts_extra %}
<!-- Load Konva.js from CDN (jsDelivr is whitelisted in CSP) -->
<script src="https://cdn.jsdelivr.net/npm/konva@9/konva.min.js" crossorigin="anonymous"></script>

<script>
console.log('PDF Editor script starting...');

const CSRF_TOKEN = {{ csrf_token()|tojson }};
const PREVIEW_URL = {{ url_for('admin.quote_pdf_layout_preview')|tojson }};
const LOGO_URL = {{ (settings.get_logo_url() if settings.has_logo() else '')|tojson }};
const SAVED_DESIGN_JSON = {{ (design_json if design_json else '')|tojson }};
const CURRENT_PAGE_SIZE = {{ page_size|tojson }};

// Page size dimensions in pixels at 72 DPI
const PAGE_SIZE_DIMENSIONS = {
    'A4': { width: 595, height: 842 },
    'Letter': { width: 612, height: 792 },
    'Legal': { width: 612, height: 1008 },
    'A3': { width: 842, height: 1191 },
    'A5': { width: 420, height: 595 },
    'Tabloid': { width: 792, height: 1224 }
};

// Issue #504: Infer quote items table from group structure (supports localized headers)
function inferQuoteTableNameFromGroup(child, excludeNode) {
    if (!child || child === excludeNode || (child.className !== 'Group' && (!child.constructor || child.constructor.name !== 'Group'))) return null;
    const children = child.getChildren ? child.getChildren() : (child.children || []);
    if (children.length < 3) return null;
    const textElements = children.filter(function(c) { return c.className === 'Text'; });
    const hasLine = children.some(function(c) { return c.className === 'Line'; });
    if (textElements.length < 2 || !hasLine) return null;
    const headerText = (textElements[0].attrs && textElements[0].attrs.text) ? textElements[0].attrs.text : '';
    const h = headerText.toLowerCase();
    const hasPipeColumns = headerText.indexOf('|') !== -1 && headerText.split('|').length >= 3;
    const itemsKeywords = ['description', 'qty', 'quantity', 'unit price', 'total', 'beschreibung', 'menge', 'preis', 'betrag', 'gesamt', 'quantité', 'prix', 'descrizione', 'quantità', 'prezzo', 'totale', 'omschrijving', 'aantal', 'eenheidsprijs', 'totaal'];
    const hasItemsKw = itemsKeywords.some(function(kw) { return h.indexOf(kw) !== -1; });
    if (hasItemsKw || (hasPipeColumns && (h.indexOf('price') !== -1 || h.indexOf('preis') !== -1 || h.indexOf('prix') !== -1 || h.indexOf('amount') !== -1 || h.indexOf('betrag') !== -1 || h.indexOf('total') !== -1 || h.indexOf('gesamt') !== -1))) {
        return 'quote-items-table';
    }
    if (hasPipeColumns && textElements.length >= 2 && hasLine) return 'quote-items-table';
    return null;
}

function ensureQuoteTableGroupNames(layerRef, bgRef) {
    if (!layerRef || !layerRef.children) return;
    const bg = bgRef || (typeof background !== 'undefined' ? background : null);
    layerRef.children.forEach(function(child) {
        const isGroup = child && (child.className === 'Group' || (child.constructor && child.constructor.name === 'Group'));
        if (!child || child === bg || !isGroup) return;
        const currentName = child.getAttr ? child.getAttr('name') : (child.attrs && child.attrs.name);
        if (currentName === 'quote-items-table') return;
        const inferred = inferQuoteTableNameFromGroup(child, bg);
        if (inferred) {
            if (child.setAttr) child.setAttr('name', inferred);
            if (child.attrs) child.attrs.name = inferred;
            if (child.name) child.name(inferred);
            console.log('[QUOTE_PDF_EDITOR] Ensured quote table group name before save:', inferred);
        }
    });
}

// Overflow and overlap detection functions (same as invoice template)
function getElementBoundingBox(element) {
    if (!element) return null;
    const box = element.getClientRect();
    const x = element.x();
    const y = element.y();
    const width = box.width || element.width() || 0;
    const height = box.height || element.height() || 0;
    if (element.className === 'Group' || element.getType() === 'Group') {
        const groupBox = element.getClientRect();
        return {
            x: groupBox.x,
            y: groupBox.y,
            width: groupBox.width,
            height: groupBox.height,
            right: groupBox.x + groupBox.width,
            bottom: groupBox.y + groupBox.height
        };
    }
    return {
        x: x,
        y: y,
        width: width,
        height: height,
        right: x + width,
        bottom: y + height
    };
}

function checkPageBoundaries(element) {
    const pageSizeSelector = document.getElementById('page-size-selector');
    const currentSize = (pageSizeSelector && pageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
    const dimensions = PAGE_SIZE_DIMENSIONS[currentSize] || PAGE_SIZE_DIMENSIONS['A4'];
    const pageWidth = dimensions.width;
    const pageHeight = dimensions.height;
    const box = getElementBoundingBox(element);
    if (!box) return { isValid: true, overflowLeft: 0, overflowRight: 0, overflowTop: 0, overflowBottom: 0 };
    const overflowLeft = Math.max(0, -box.x);
    const overflowRight = Math.max(0, box.right - pageWidth);
    const overflowTop = Math.max(0, -box.y);
    const overflowBottom = Math.max(0, box.bottom - pageHeight);
    const isValid = overflowLeft === 0 && overflowRight === 0 && overflowTop === 0 && overflowBottom === 0;
    return {
        isValid: isValid,
        overflowLeft: overflowLeft,
        overflowRight: overflowRight,
        overflowTop: overflowTop,
        overflowBottom: overflowBottom,
        box: box
    };
}

function checkOverlaps(element, allElements) {
    if (!element || !allElements) return [];
    const elementBox = getElementBoundingBox(element);
    if (!elementBox) return [];
    const overlaps = [];
    for (let i = 0; i < allElements.length; i++) {
        const otherElement = allElements[i];
        const elementName = otherElement.attrs && otherElement.attrs.name;
        if (otherElement === element || 
            elementName === 'background' || 
            elementName === 'page-border' ||
            otherElement.className === 'Transformer') {
            continue;
        }
        const otherBox = getElementBoundingBox(otherElement);
        if (!otherBox) continue;
        const intersects = !(
            elementBox.right < otherBox.x ||
            elementBox.x > otherBox.right ||
            elementBox.bottom < otherBox.y ||
            elementBox.y > otherBox.bottom
        );
        if (intersects) {
            const overlapX = Math.max(0, Math.min(elementBox.right, otherBox.right) - Math.max(elementBox.x, otherBox.x));
            const overlapY = Math.max(0, Math.min(elementBox.bottom, otherBox.bottom) - Math.max(elementBox.y, otherBox.y));
            const overlapArea = overlapX * overlapY;
            overlaps.push({
                element: otherElement,
                overlapArea: overlapArea,
                overlapX: overlapX,
                overlapY: overlapY
            });
        }
    }
    return overlaps;
}

// Wait for Konva.js to load
let konvaLoadAttempts = 0;
const maxKonvaLoadAttempts = 100; // 5 seconds max wait

function initializePDFEditor() {
    konvaLoadAttempts++;
    
    if (typeof Konva === 'undefined') {
        if (konvaLoadAttempts >= maxKonvaLoadAttempts) {
            console.error('❌ Konva.js failed to load after 5 seconds');
            const container = document.getElementById('canvas-container');
            if (container) {
                container.innerHTML = '<div style="padding: 40px; text-align: center; background: #fee; border: 2px solid red; border-radius: 8px; margin: 20px;">' +
                    '<h3 style="color: #d00; margin-bottom: 10px;">⚠️ Konva.js Library Failed to Load</h3>' +
                    '<p>The canvas library could not be loaded. This might be due to:</p>' +
                    '<ul style="text-align: left; display: inline-block; margin: 10px auto;">' +
                    '<li>Network connectivity issues</li>' +
                    '<li>CDN being blocked</li>' +
                    '<li>Firewall/proxy restrictions</li>' +
                    '</ul>' +
                    '<p><strong>Try:</strong></p>' +
                    '<ul style="text-align: left; display: inline-block; margin: 10px auto;">' +
                    '<li>Refresh the page (Ctrl+F5)</li>' +
                    '<li>Check your internet connection</li>' +
                    '<li>Try a different network</li>' +
                    '<li>Check browser console for errors (F12)</li>' +
                    '</ul>' +
                    '</div>';
            }
            return;
        }
        console.log('Waiting for Konva.js to load... (attempt ' + konvaLoadAttempts + '/' + maxKonvaLoadAttempts + ')');
        setTimeout(initializePDFEditor, 50);
        return;
    }
    
    console.log('✅ Konva.js loaded successfully, initializing editor...');
    
    // Initialize Konva Stage
    const container = document.getElementById('canvas-container');
    // Get page size from selector if available, otherwise use CURRENT_PAGE_SIZE
    const pageSizeSelector = document.getElementById('page-size-selector');
    const currentSize = (pageSizeSelector && pageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
    const dimensions = PAGE_SIZE_DIMENSIONS[currentSize] || PAGE_SIZE_DIMENSIONS['A4'];
    let width = dimensions.width;
    let height = dimensions.height;
    
    console.log('Initializing canvas with page size:', currentSize, 'dimensions:', width, 'x', height);
    
    let stage = new Konva.Stage({
        container: 'canvas-container',
        width: width,
        height: height,
        draggable: false
    });
    
    let layer = new Konva.Layer();
    stage.add(layer);
    
    // Store elements for export
    const elements = [];
    let selectedElement = null;
    let snapToGrid = true;
    const gridSize = 10;
    
    // Base fit scale (for auto-fitting to container)
    let baseFitScale = 1;
    let zoomScale = 1; // Zoom scale applied on top of base fit scale
    
    // Auto-fit scaling function to fit canvas within container
    function fitCanvasToContainer() {
        if (!container || !stage) return;
        
        // Get container dimensions (accounting for padding/borders)
        const containerRect = container.getBoundingClientRect();
        const containerWidth = containerRect.width - 40; // Account for padding
        const containerHeight = containerRect.height - 40;
        
        // Skip if container not ready
        if (containerWidth <= 0 || containerHeight <= 0) return;
        
        // Get actual page dimensions dynamically from current page size selector
        const currentPageSizeSelector = document.getElementById('page-size-selector');
        const currentPageSize = (currentPageSizeSelector && currentPageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
        const currentDimensions = PAGE_SIZE_DIMENSIONS[currentPageSize] || PAGE_SIZE_DIMENSIONS['A4'];
        const pageWidth = currentDimensions.width;
        const pageHeight = currentDimensions.height;
        
        // Calculate scale to fit within container while maintaining aspect ratio
        const scaleX = containerWidth / pageWidth;
        const scaleY = containerHeight / pageHeight;
        baseFitScale = Math.min(scaleX, scaleY, 1); // Don't scale up, only down
        
        // Reset zoom when fitting
        zoomScale = 1;
        
        // Apply base fit scale
        stage.scale({ x: baseFitScale, y: baseFitScale });
        
        // Redraw
        layer.draw();
        
        console.log(`Canvas fitted: scale=${baseFitScale.toFixed(2)}, container=${containerWidth}x${containerHeight}, page=${pageWidth}x${pageHeight}`);
    }
    
    // Fit canvas on initialization (after a short delay to ensure container is rendered)
    setTimeout(() => {
        fitCanvasToContainer();
    }, 100);
    
    // Fit canvas on window resize
    let resizeTimeout;
    window.addEventListener('resize', function() {
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(fitCanvasToContainer, 250);
    });
    
    // Add background with grid
    // Use current page size dimensions to ensure correct size
    const currentPageSizeSelector = document.getElementById('page-size-selector');
    const currentPageSize = (currentPageSizeSelector && currentPageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
    const currentDimensions = PAGE_SIZE_DIMENSIONS[currentPageSize] || PAGE_SIZE_DIMENSIONS['A4'];
    const backgroundWidth = currentDimensions.width;
    const backgroundHeight = currentDimensions.height;
    
    let background = new Konva.Rect({
        x: 0,
        y: 0,
        width: backgroundWidth,
        height: backgroundHeight,
        fill: 'white',
        name: 'background'
    });
    layer.add(background);
    
    // Add page border to clearly mark page boundaries
    let pageBorder = new Konva.Rect({
        x: 0,
        y: 0,
        width: backgroundWidth,
        height: backgroundHeight,
        stroke: '#667eea',  // Blue border matching the UI theme
        strokeWidth: 2,
        fill: 'transparent',
        name: 'page-border',
        listening: false,  // Don't interfere with interactions
        perfectDrawEnabled: false  // Better performance
    });
    layer.add(pageBorder);
    pageBorder.moveToBottom();  // Keep it behind content but visible
    
    // Add grid lines
    function drawGrid() {
        // Get current page size dimensions dynamically (not scaled stage dimensions)
        const currentPageSizeSelector = document.getElementById('page-size-selector');
        const currentPageSize = (currentPageSizeSelector && currentPageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
        const currentDimensions = PAGE_SIZE_DIMENSIONS[currentPageSize] || PAGE_SIZE_DIMENSIONS['A4'];
        const stageWidth = currentDimensions.width;
        const stageHeight = currentDimensions.height;
        
        // Remove old grid if exists
        layer.find('.grid-line').forEach(line => line.destroy());
        
        // Draw vertical lines
        for (let i = 0; i < stageWidth / gridSize; i++) {
            const line = new Konva.Line({
                points: [i * gridSize, 0, i * gridSize, stageHeight],
                stroke: '#e0e0e0',
                strokeWidth: i % 5 === 0 ? 0.5 : 0.25,
                name: 'grid-line',
                listening: false
            });
            layer.add(line);
            line.moveToBottom();
        }
        
        // Draw horizontal lines
        for (let i = 0; i < stageHeight / gridSize; i++) {
            const line = new Konva.Line({
                points: [0, i * gridSize, stageWidth, i * gridSize],
                stroke: '#e0e0e0',
                strokeWidth: i % 5 === 0 ? 0.5 : 0.25,
                name: 'grid-line',
                listening: false
            });
            layer.add(line);
            line.moveToBottom();
        }
        
        if (background) {
            background.moveToBottom();
        }
        // Keep page border behind content but above background
        const pageBorderInGrid = layer.findOne('[name="page-border"]');
        if (pageBorderInGrid) {
            pageBorderInGrid.moveToBottom();
            if (background) {
                background.moveToBottom();
            }
        }
        layer.draw();
    }
    
    drawGrid();
    
    // Element templates
    {% raw %}
    const templates = {
        // Basic elements
        'text': { text: 'Sample Text', fontSize: 14, fontFamily: 'Arial' },
        'heading': { text: 'QUOTE', fontSize: 28, fontFamily: 'Arial', fontStyle: 'bold' },
        
        // Company info
        'company-name': { text: '{{ settings.company_name }}', fontSize: 20, fontFamily: 'Arial', fontStyle: 'bold' },
        'company-info': { text: '{{ settings.company_address }}\\n{{ settings.company_email }}\\n{{ settings.company_phone }}', fontSize: 12 },
        'company-address': { text: '{{ settings.company_address }}', fontSize: 12 },
        'company-email': { text: 'Email: {{ settings.company_email }}', fontSize: 12 },
        'company-phone': { text: 'Phone: {{ settings.company_phone }}', fontSize: 12 },
        'company-website': { text: '{{ settings.company_website }}', fontSize: 12 },
        'company-tax-id': { text: 'Tax ID: {{ settings.company_tax_id }}', fontSize: 12 },
        
        // Quote data
        'quote-number': { text: 'Quote #: {{ quote.quote_number }}', fontSize: 14, fontStyle: 'bold' },
        'quote-date': { text: 'Date: {{ format_date(quote.created_at) }}', fontSize: 12 },
        'valid-until': { text: 'Valid Until: {{ format_date(quote.valid_until) }}', fontSize: 12 },
        'quote-status': { text: 'Status: {{ quote.status|upper }}', fontSize: 12 },
        'client-info': { text: 'Quote For:\\n{{ quote.client.name }}\\n{{ quote.client.address }}', fontSize: 12 },
        'client-name': { text: '{{ quote.client.name }}', fontSize: 14, fontStyle: 'bold' },
        'client-address': { text: '{{ quote.client.address }}', fontSize: 12 },
        'subtotal': { text: 'Subtotal: {{ format_money(quote.subtotal) }}', fontSize: 14 },
        'tax': { text: 'Tax ({{ quote.tax_rate }}%): {{ format_money(quote.tax_amount) }}', fontSize: 14 },
        'totals': { text: 'Total: {{ format_money(quote.total_amount) }}', fontSize: 16, fontStyle: 'bold' },
        'notes': { text: 'Notes: {{ quote.notes }}', fontSize: 11 },
        'terms': { text: 'Terms: {{ quote.terms }}', fontSize: 10 },
        
        // Quote specific
        'quote-title': { text: 'Title: {{ quote.title }}', fontSize: 12 },
        'quote-description': { text: 'Description: {{ quote.description }}', fontSize: 11 },
        'accepted-date': { text: 'Accepted Date: {{ format_date(quote.accepted_at) if quote.accepted_at else "N/A" }}', fontSize: 12 },
        'sent-date': { text: 'Sent Date: {{ format_date(quote.sent_at) if quote.sent_at else "N/A" }}', fontSize: 12 },
        'client-email': { text: 'Email: {{ quote.client.email or "" }}', fontSize: 12 },
        'client-phone': { text: 'Phone: {{ quote.client.phone or "" }}', fontSize: 12 },
        
        // Advanced
        'qr-code': { text: '[QR Code: {{ quote.quote_number }}]', fontSize: 10 },
        'barcode': { text: '{{ quote.quote_number }}', fontSize: 10 },
        'page-number': { text: 'Page 1', fontSize: 10 },
        'date-now': { text: '{{ format_date(now) }}', fontSize: 10 },
        'watermark': { text: 'CONFIDENTIAL', fontSize: 48, fontStyle: 'bold', opacity: 0.1 },
        'bank-info': { text: 'Bank Account:\\n{{ settings.company_bank_info }}', fontSize: 11 },
        'currency': { text: 'Currency: {{ quote.currency_code }}', fontSize: 12 }
    };
    {% endraw %}
    
    // Tab switching
    document.querySelectorAll('.sidebar-tab').forEach(tab => {
        tab.addEventListener('click', function() {
            const targetTab = this.dataset.tab;
            
            // Update tabs
            document.querySelectorAll('.sidebar-tab').forEach(t => t.classList.remove('active'));
            this.classList.add('active');
            
            // Update content
            document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active'));
            document.getElementById('tab-' + targetTab).classList.add('active');
        });
    });
    
    // Search functionality
    const searchBox = document.getElementById('sidebar-search');
    searchBox.addEventListener('input', function() {
        const searchTerm = this.value.toLowerCase().trim();
        
        // Search in elements tab
        const elementItems = document.querySelectorAll('#tab-elements .element-item');
        elementItems.forEach(item => {
            const text = item.textContent.toLowerCase();
            if (text.includes(searchTerm) || searchTerm === '') {
                item.style.display = 'flex';
            } else {
                item.style.display = 'none';
            }
        });
        
        // Search in variables tab
        const variableItems = document.querySelectorAll('#tab-variables .variable-item');
        variableItems.forEach(item => {
            const text = item.textContent.toLowerCase();
            if (text.includes(searchTerm) || searchTerm === '') {
                item.style.display = 'block';
            } else {
                item.style.display = 'none';
            }
        });
        
        // Show/hide group titles
        document.querySelectorAll('.element-group').forEach(group => {
            const visibleItems = Array.from(group.querySelectorAll('.element-item, .variable-item'))
                .filter(item => item.style.display !== 'none');
            group.style.display = visibleItems.length > 0 ? 'block' : 'none';
        });
    });
    
    // Handle variable clicks (add as custom text)
    document.querySelectorAll('.variable-item').forEach(item => {
        item.addEventListener('click', function() {
            const variable = this.dataset.variable;
            let text;
            
            // Handle special loop variables
            if (variable === 'for-loop-start') {
                {% raw %}
                text = '{% for item in quote.items %}';
                {% endraw %}
            } else if (variable === 'for-loop-end') {
                {% raw %}
                text = '{% endfor %}';
                {% endraw %}
            } else {
                {% raw %}
                text = '{{ ' + variable + ' }}';
                {% endraw %}
            }
            
            addElement('custom-text', 100, 100, text);
        });
    });
    
    // Drag from sidebar
    document.querySelectorAll('.element-item').forEach(item => {
        item.addEventListener('click', function() {
            const type = this.dataset.type;
            
            // Handle custom text with prompt
            if (type === 'custom-text') {
                {% raw %}
                const customText = prompt('Enter text (you can use variables like {{ invoice.invoice_number }}):', 'Custom Text');
                {% endraw %}
                if (customText !== null) {
                    addElement('custom-text', 100, 100, customText);
                }
                return;
            }
            
            addElement(type, 100, 100);
        });
    });
    
    function addElement(type, x, y, customText) {
        // Handle custom text
        if (type === 'custom-text' || customText) {
            const text = new Konva.Text({
                x: x,
                y: y,
                text: customText || 'Custom Text',
                fontSize: 14,
                fontFamily: 'Arial',
                fill: 'black',
                draggable: true,
                width: 400,
                name: 'custom-text'
            });
            
            layer.add(text);
            elements.push({ type: 'custom-text', node: text });
            setupSelection(text);
            layer.draw();
            scheduleQuoteEditorHistoryPush();
            return;
        }
        
        const template = templates[type];
        
        // Handle special cases first
            if (type === 'line') {
                const line = new Konva.Line({
                    points: [x, y, x + 200, y],
                    stroke: 'black',
                    strokeWidth: 2,
                draggable: true,
                name: 'line'
                });
                layer.add(line);
                elements.push({ type: 'line', node: line });
                setupSelection(line);
            layer.draw();
                scheduleQuoteEditorHistoryPush();
                return;
            }
        
        if (type === 'rectangle') {
            const rect = new Konva.Rect({
                x: x,
                y: y,
                width: 150,
                height: 100,
                fill: 'transparent',
                stroke: 'black',
                strokeWidth: 2,
                draggable: true,
                name: 'rectangle'
            });
            layer.add(rect);
            elements.push({ type: 'rectangle', node: rect });
            setupSelection(rect);
            layer.draw();
            scheduleQuoteEditorHistoryPush();
            return;
        }
        
        if (type === 'circle') {
            const circle = new Konva.Circle({
                x: x + 50,
                y: y + 50,
                radius: 50,
                fill: 'transparent',
                stroke: 'black',
                strokeWidth: 2,
                draggable: true,
                name: 'circle'
            });
            layer.add(circle);
            elements.push({ type: 'circle', node: circle });
            setupSelection(circle);
            layer.draw();
            scheduleQuoteEditorHistoryPush();
            return;
        }
        
            if (type === 'logo' && LOGO_URL) {
                Konva.Image.fromURL(LOGO_URL, function(image) {
                    image.setAttrs({
                        x: x,
                        y: y,
                        width: 100,
                        height: 50,
                    draggable: true,
                    name: 'logo'
                    });
                    layer.add(image);
                    elements.push({ type: 'logo', node: image });
                    setupSelection(image);
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                });
                return;
            }
        
        if (type === 'decorative-image') {
            // Create a placeholder for decorative images
            const placeholder = new Konva.Group({
                x: x,
                y: y,
                draggable: true,
                name: 'decorative-image',
                imageUrl: '' // Store image URL here
            });
            
            // Ensure name is set via multiple methods for proper serialization
            placeholder.setAttr('name', 'decorative-image');
            placeholder.name('decorative-image');
            
            const rect = new Konva.Rect({
                x: 0,
                y: 0,
                width: 100,
                height: 100,
                fill: '#f0f0f0',
                stroke: '#999',
                strokeWidth: 2,
                dash: [5, 5]
            });
            
            const text = new Konva.Text({
                x: 10,
                y: 40,
                text: 'Decorative\nImage',
                fontSize: 12,
                fontFamily: 'Arial',
                fill: '#666',
                align: 'center',
                width: 80
            });
            
            const icon = new Konva.Text({
                x: 35,
                y: 15,
                text: '🖼️',
                fontSize: 24,
                width: 30,
                align: 'center'
            });
            
            placeholder.add(rect);
            placeholder.add(icon);
            placeholder.add(text);
            
            layer.add(placeholder);
            elements.push({ type: 'decorative-image', node: placeholder });
            setupSelection(placeholder);
            layer.draw();
            scheduleQuoteEditorHistoryPush();
            return;
        }
        
            if (type === 'quote-items-table') {
                addTable(x, y);
            return;
        }
        
            // Removed expenses-table for quotes
            if (false && type === 'expenses-table') {
                addExpensesTable(x, y);
            return;
        }
        
        // Handle text-based elements
        if (!template) return;
        
        const text = new Konva.Text({
            x: x,
            y: y,
            text: template.text,
            fontSize: template.fontSize || 14,
            fontFamily: template.fontFamily || 'Arial',
            fontStyle: template.fontStyle || 'normal',
            fill: 'black',
            draggable: true,
            width: 400,
            opacity: template.opacity !== undefined ? template.opacity : 1,
            name: type
        });
        
        layer.add(text);
        elements.push({ type: type, node: text, template: template });
        setupSelection(text);
        layer.draw();
        scheduleQuoteEditorHistoryPush();
    }
    
    function addTable(x, y) {
        const tableGroup = new Konva.Group({
            x: x,
            y: y,
            draggable: true,
            name: 'quote-items-table'
        });
        tableGroup.setAttr('name', 'quote-items-table');
        
        const header = new Konva.Text({
            text: 'Description | Qty | Price | Total',
            fontSize: 12,
            fontStyle: 'bold',
            fill: 'black',
            width: 500
        });
        
        const line = new Konva.Line({
            points: [0, 20, 500, 20],
            stroke: 'black',
            strokeWidth: 1
        });
        
        {% raw %}
        const items = new Konva.Text({
            y: 25,
            text: '{% for item in quote.items %}\\n{{ item.description }} | {{ item.quantity }} | {{ format_money(item.unit_price) }} | {{ format_money(item.total_amount) }}\\n{% endfor %}',
            fontSize: 11,
            fill: 'black',
            width: 500
        });
        {% endraw %}
        
        tableGroup.add(header, line, items);
        layer.add(tableGroup);
        elements.push({ type: 'quote-items-table', node: tableGroup });
        setupSelection(tableGroup);
        // Also setup selection on children so clicks on them select the parent Group
        setupSelection(header);
        setupSelection(line);
        setupSelection(items);
        layer.draw();
        scheduleQuoteEditorHistoryPush();
    }
    
    function addExpensesTable(x, y) {
        const tableGroup = new Konva.Group({
            x: x,
            y: y,
            draggable: true,
            name: 'quote-items-table'
        });
        
        const header = new Konva.Text({
            text: 'Expense | Date | Category | Amount',
            fontSize: 12,
            fontStyle: 'bold',
            fill: '#856404',
            width: 500
        });
        
        const line = new Konva.Line({
            points: [0, 20, 500, 20],
            stroke: '#856404',
            strokeWidth: 1
        });
        
        {% raw %}
        const items = new Konva.Text({
            y: 25,
            text: '{% for expense in invoice.expenses %}\\n{{ expense.title }} | {{ expense.expense_date }} | {{ expense.category }} | {{ format_money(expense.total_amount) }}\\n{% endfor %}',
            fontSize: 11,
            fill: '#856404',
            width: 500
        });
        {% endraw %}
        
        tableGroup.add(header, line, items);
        layer.add(tableGroup);
        // Removed expenses table for quotes
        setupSelection(tableGroup);
        // Also setup selection on children so clicks on them select the parent Group
        setupSelection(header);
        setupSelection(line);
        setupSelection(items);
        layer.draw();
        scheduleQuoteEditorHistoryPush();
    }
    
    function setupSelection(node) {
        node.on('click', function(e) {
            // If clicking on a child of a table Group, select the Group instead
            // UNLESS Ctrl/Cmd is held, which allows selecting the individual element
            let targetNode = node;
            if (node.parent && node.parent.attrs && (node.parent.attrs.name === 'quote-items-table')) {
                // Allow direct selection if Ctrl/Cmd is held
                if (e.evt.ctrlKey || e.evt.metaKey) {
                    targetNode = node;
                } else {
                    targetNode = node.parent;
                }
            }
            selectElement(targetNode);
            e.cancelBubble = true;
        });
        
        // Add double-click handler for text elements in tables to enable direct editing
        // Check multiple ways to detect Text elements
        const isTextNode = node.className === 'Text' || (node.getType && node.getType() === 'Text') || 
                          (node.constructor && node.constructor.name === 'Text');
        if (isTextNode && node.parent && node.parent.attrs && 
            (node.parent.attrs.name === 'items-table' || node.parent.attrs.name === 'expenses-table')) {
            // Change cursor on hover to indicate editability
            node.on('mouseenter', function() {
                stage.container().style.cursor = 'text';
            });
            node.on('mouseleave', function() {
                stage.container().style.cursor = 'default';
            });
            
            node.on('dblclick', function(e) {
                // Select the individual text element for editing
                selectElement(node);
                e.cancelBubble = true;
                // Focus the text content textarea in properties panel if available
                setTimeout(() => {
                    const propText = document.getElementById('prop-text');
                    if (propText) {
                        propText.disabled = false;
                        propText.readOnly = false;
                        propText.focus();
                        propText.select();
                    }
                }, 150);
            });
        }
        
        // Add snap to grid on drag
        node.on('dragmove', function() {
            if (snapToGrid) {
                node.position({
                    x: Math.round(node.x() / gridSize) * gridSize,
                    y: Math.round(node.y() / gridSize) * gridSize
                });
            }
        });
        
        node.on('dragend', function() {
            // Update properties panel if visible
            const propX = document.getElementById('prop-x');
            const propY = document.getElementById('prop-y');
            if (propX) propX.value = Math.round(node.x());
            if (propY) propY.value = Math.round(node.y());
            scheduleQuoteEditorHistoryPush();
        });
    }
    
    // Snap to grid toggle
    document.getElementById('snap-to-grid')?.addEventListener('change', function(e) {
        snapToGrid = e.target.checked;
    });
    
    function selectElement(node) {
        // Remove previous selection
        layer.find('Transformer').forEach(t => t.destroy());
        
        selectedElement = node;
        
        const transformer = new Konva.Transformer({
            nodes: [node],
            enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
            rotateEnabled: false,
            boundBoxFunc: function(oldBox, newBox) {
                if (newBox.width < 20 || newBox.height < 20) {
                    return oldBox;
                }
                return newBox;
            }
        });
        
        // For Image nodes, convert scale changes to width/height changes
        // This ensures the size is stored correctly when resizing
        if (node.className === 'Image') {
            transformer.on('transformend', function() {
                const image = node;
                const scaleX = image.scaleX();
                const scaleY = image.scaleY();
                
                // Update width and height based on scale
                const newWidth = image.width() * scaleX;
                const newHeight = image.height() * scaleY;
                
                // Reset scale to 1 and apply the new dimensions
                image.width(newWidth);
                image.height(newHeight);
                image.scaleX(1);
                image.scaleY(1);
                
                // Update properties panel if visible
                if (selectedElement === image) {
                    updatePropertiesPanel(image);
                }
                
                layer.draw();
            });
        }
        
        layer.add(transformer);
        transformer.on('transformend', function() {
            scheduleQuoteEditorHistoryPush();
        });
        
        // Explicitly hide rotation handle if it exists (backup for rotateEnabled: false)
        transformer.forceUpdate();
        const rotationHandle = transformer.findOne('.rotater');
        if (rotationHandle) {
            rotationHandle.visible(false);
        }
        
        // Remove any warning indicator dots that might exist
        layer.find('.warning-indicator').forEach(indicator => {
            indicator.destroy();
        });
        
        layer.draw();
        
        // Update properties panel
        updatePropertiesPanel(node);
    }
    
    function updatePropertiesPanel(node) {
        const propsContent = document.getElementById('properties-content');
        if (!propsContent) {
            console.error('Properties content element not found!');
            return;
        }
        
        if (!node) {
            console.error('Node is null or undefined!');
            propsContent.innerHTML = '<p class="text-sm text-red-500">Error: No element selected</p>';
            return;
        }
        
        const attrs = node.attrs || {};
        // Try multiple ways to detect Text elements for better compatibility
        const className = node.className || (node.getType && node.getType()) || 'Unknown';
        const isTextElement = className === 'Text' || (node.getType && node.getType() === 'Text') || 
                             (node.constructor && node.constructor.name === 'Text');
        
        // Debug logging
        console.log('updatePropertiesPanel called:', {
            className: className,
            getType: node.getType ? node.getType() : 'N/A',
            constructorName: node.constructor ? node.constructor.name : 'unknown',
            isTextElement: isTextElement,
            name: attrs.name,
            attrs: attrs,
            node: node
        });
        
        let html = '<div class="space-y-4">';
        
        // Element type
        html += '<div class="property-group">';
        html += '<div class="property-label">Element Type</div>';
        html += `<input type="text" value="${attrs.name || className}" disabled class="property-input bg-gray-100">`;
        html += '</div>';
        
        // Position
        html += '<div class="property-group">';
        html += '<div class="property-label">Position X</div>';
        html += `<input type="number" id="prop-x" value="${Math.round(attrs.x || 0)}" class="property-input">`;
        html += '</div>';
        
        html += '<div class="property-group">';
        html += '<div class="property-label">Position Y</div>';
        html += `<input type="number" id="prop-y" value="${Math.round(attrs.y || 0)}" class="property-input">`;
        html += '</div>';
        
        // Text-specific properties - check if it's a Text element (even if inside a table group)
        if (isTextElement) {
            // Properly escape text for HTML and convert \n to actual newlines
            const textContent = (attrs.text || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/\\n/g, '\n');
            // Use more rows for table items which might be longer
            const isTableText = node.parent && node.parent.attrs && (node.parent.attrs.name === 'items-table' || node.parent.attrs.name === 'expenses-table' || node.parent.attrs.name === 'quote-items-table');
            const textareaRows = isTableText ? 8 : 3;
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Text Content</div>';
            html += `<textarea id="prop-text" class="property-input" rows="${textareaRows}">${textContent}</textarea>`;
            if (isTableText) {
                html += '<p class="text-xs text-gray-500 mt-1">Double-click table text to edit, or edit here in the properties panel.</p>';
            }
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Font Size</div>';
            html += `<input type="number" id="prop-fontSize" value="${attrs.fontSize || 14}" class="property-input">`;
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Font Family</div>';
            html += `<select id="prop-fontFamily" class="property-input">`;
            const fonts = ['Arial', 'Times New Roman', 'Courier New', 'Georgia', 'Verdana', 'Helvetica'];
            fonts.forEach(font => {
                const selected = (attrs.fontFamily === font) ? 'selected' : '';
                html += `<option value="${font}" ${selected}>${font}</option>`;
            });
            html += '</select>';
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Font Style</div>';
            html += `<select id="prop-fontStyle" class="property-input">`;
            html += `<option value="normal" ${attrs.fontStyle === 'normal' ? 'selected' : ''}>Normal</option>`;
            html += `<option value="bold" ${attrs.fontStyle === 'bold' ? 'selected' : ''}>Bold</option>`;
            html += `<option value="italic" ${attrs.fontStyle === 'italic' ? 'selected' : ''}>Italic</option>`;
            html += '</select>';
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Text Color</div>';
            html += `<input type="color" id="prop-fill" value="${rgbToHex(attrs.fill || '#000000')}" class="property-input">`;
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Width</div>';
            html += `<input type="number" id="prop-width" value="${attrs.width || 400}" class="property-input">`;
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Opacity</div>';
            html += `<input type="range" id="prop-opacity" min="0" max="1" step="0.1" value="${attrs.opacity !== undefined ? attrs.opacity : 1}" class="property-input">`;
            html += `<span id="opacity-value">${(attrs.opacity !== undefined ? attrs.opacity : 1) * 100}%</span>`;
            html += '</div>';
        }
        
        // Shape-specific properties
        if (className === 'Rect' || className === 'Circle') {
            html += '<div class="property-group">';
            html += '<div class="property-label">Fill Color</div>';
            html += `<input type="color" id="prop-fill" value="${rgbToHex(attrs.fill || '#ffffff')}" class="property-input">`;
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Stroke Color</div>';
            html += `<input type="color" id="prop-stroke" value="${rgbToHex(attrs.stroke || '#000000')}" class="property-input">`;
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Stroke Width</div>';
            html += `<input type="number" id="prop-strokeWidth" value="${attrs.strokeWidth || 1}" class="property-input">`;
            html += '</div>';
            
            if (className === 'Rect') {
                html += '<div class="property-group">';
                html += '<div class="property-label">Width</div>';
                html += `<input type="number" id="prop-width" value="${attrs.width || 100}" class="property-input">`;
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Height</div>';
                html += `<input type="number" id="prop-height" value="${attrs.height || 100}" class="property-input">`;
                html += '</div>';
            }
            
            if (className === 'Circle') {
                html += '<div class="property-group">';
                html += '<div class="property-label">Radius</div>';
                html += `<input type="number" id="prop-radius" value="${attrs.radius || 50}" class="property-input">`;
                html += '</div>';
            }
        }
        
        // Line-specific properties
        if (className === 'Line') {
            html += '<div class="property-group">';
            html += '<div class="property-label">Stroke Color</div>';
            html += `<input type="color" id="prop-stroke" value="${rgbToHex(attrs.stroke || '#000000')}" class="property-input">`;
            html += '</div>';
            
            html += '<div class="property-group">';
            html += '<div class="property-label">Stroke Width</div>';
            html += `<input type="number" id="prop-strokeWidth" value="${attrs.strokeWidth || 1}" class="property-input">`;
            html += '</div>';
        }
        
        // Decorative image properties
        // Use includes() to handle cases where name might be modified (e.g., 'decorative-image element-overlap')
        if (attrs.name && attrs.name.includes('decorative-image')) {
            // Use getAttr to ensure we get the imageUrl
            const imageUrl = node.getAttr('imageUrl') || attrs.imageUrl || '';
            console.log('Properties panel - decorative image URL:', imageUrl);
            html += '<div class="property-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e5e7eb;">';
            html += '<div class="property-label" style="font-weight: bold; color: #667eea;">Decorative Image</div>';
            
            if (imageUrl && imageUrl.trim() !== '') {
                html += '<div style="margin-bottom: 1rem;">';
                html += `<img src="${imageUrl}" alt="Decorative image" style="max-width: 100%; max-height: 150px; border: 1px solid #ddd; border-radius: 4px;">`;
                html += '</div>';
            }
            
            html += '<div class="property-group">';
            html += '<label for="decorative-image-upload" class="property-label">Upload Image</label>';
            html += '<input type="file" id="decorative-image-upload" accept="image/png,image/jpeg,image/jpg,image/gif,image/webp" style="display: none;">';
            html += `<button type="button" id="btn-upload-decorative-image" class="property-input" style="background: #667eea; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; width: 100%;">`;
            html += '<i class="fas fa-upload mr-2"></i>';
            html += imageUrl ? '{{ _("Change Image") }}' : '{{ _("Upload Image") }}';
            html += '</button>';
            html += '</div>';
            
            if (imageUrl) {
                html += '<div class="property-group">';
                html += '<button type="button" id="btn-remove-decorative-image" class="property-input" style="background: #ef4444; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; width: 100%;">';
                html += '<i class="fas fa-trash mr-2"></i>{{ _("Remove Image") }}';
                html += '</button>';
                html += '</div>';
            }
            
            html += '<input type="hidden" id="prop-image-url" value="' + (imageUrl || '') + '">';
            html += '</div>';
        }
        
        // Group-specific properties (for items-table and expenses-table)
        // Check both className and constructor name for Groups
        const isGroup = className === 'Group' || (node.constructor && node.constructor.name === 'Group');
        const isTableGroup = isGroup && (attrs.name === 'items-table' || attrs.name === 'expenses-table' || attrs.name === 'quote-items-table');
        console.log('Group check:', {
            className: className,
            constructorName: node.constructor ? node.constructor.name : 'unknown',
            isGroup: isGroup,
            name: attrs.name,
            isTableGroup: isTableGroup
        });
        
        if (isTableGroup) {
            try {
                // Find child elements - use getChildren() to get direct children
                const children = node.getChildren();
                const textElements = children.filter(child => child.className === 'Text');
                const lineElements = children.filter(child => child.className === 'Line');
                
                const headerText = textElements[0]; // First text is header
                const line = lineElements[0];
                const itemsText = textElements[1]; // Second text is items
                
                const headerAttrs = headerText ? headerText.attrs : {};
                const lineAttrs = line ? line.attrs : {};
                const itemsAttrs = itemsText ? itemsText.attrs : {};
                
                // Debug logging
                console.log('Table Group detected:', attrs.name);
                console.log('Children count:', children.length);
                console.log('Text elements:', textElements.length);
                console.log('Line elements:', lineElements.length);
                
                html += '<div class="property-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e5e7eb;">';
                html += '<div class="property-label" style="font-weight: bold; color: #667eea;">Table Header</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Header Text</div>';
                html += `<textarea id="prop-table-header-text" class="property-input" rows="2">${(headerAttrs.text || '').replace(/\\n/g, '\n')}</textarea>`;
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Header Font Size</div>';
                html += `<input type="number" id="prop-table-header-fontSize" value="${headerAttrs.fontSize || 12}" class="property-input">`;
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Header Font Style</div>';
                html += `<select id="prop-table-header-fontStyle" class="property-input">`;
                html += `<option value="normal" ${headerAttrs.fontStyle === 'normal' ? 'selected' : ''}>Normal</option>`;
                html += `<option value="bold" ${headerAttrs.fontStyle === 'bold' ? 'selected' : ''}>Bold</option>`;
                html += `<option value="italic" ${headerAttrs.fontStyle === 'italic' ? 'selected' : ''}>Italic</option>`;
                html += '</select>';
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Header Color</div>';
                html += `<input type="color" id="prop-table-header-fill" value="${rgbToHex(headerAttrs.fill || '#000000')}" class="property-input">`;
                html += '</div>';
                
                html += '</div>';
                
                html += '<div class="property-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e5e7eb;">';
                html += '<div class="property-label" style="font-weight: bold; color: #667eea;">Table Items</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Items Template</div>';
                html += `<textarea id="prop-table-items-text" class="property-input" rows="4">${(itemsAttrs.text || '').replace(/\\n/g, '\n')}</textarea>`;
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Items Font Size</div>';
                html += `<input type="number" id="prop-table-items-fontSize" value="${itemsAttrs.fontSize || 11}" class="property-input">`;
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Items Color</div>';
                html += `<input type="color" id="prop-table-items-fill" value="${rgbToHex(itemsAttrs.fill || '#000000')}" class="property-input">`;
                html += '</div>';
                
                html += '</div>';
                
                html += '<div class="property-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e5e7eb;">';
                html += '<div class="property-label" style="font-weight: bold; color: #667eea;">Table Separator Line</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Line Color</div>';
                html += `<input type="color" id="prop-table-line-stroke" value="${rgbToHex(lineAttrs.stroke || '#000000')}" class="property-input">`;
                html += '</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Line Width</div>';
                html += `<input type="number" id="prop-table-line-strokeWidth" value="${lineAttrs.strokeWidth || 1}" class="property-input">`;
                html += '</div>';
                
                html += '</div>';
                
                html += '<div class="property-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e5e7eb;">';
                html += '<div class="property-label" style="font-weight: bold; color: #667eea;">Table Dimensions</div>';
                
                html += '<div class="property-group">';
                html += '<div class="property-label">Table Width</div>';
                const tableWidth = headerAttrs.width || itemsAttrs.width || 500;
                html += `<input type="number" id="prop-table-width" value="${tableWidth}" class="property-input">`;
                html += '</div>';
                
                html += '</div>';
            } catch (error) {
                console.error('Error processing table group:', error);
                html += '<div class="property-group"><p class="text-sm text-red-500">Error loading table properties: ' + error.message + '</p></div>';
            }
        } else if (isGroup) {
            // Fallback for Groups that aren't recognized as tables
            // Show at least basic info
            html += '<div class="property-group" style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e5e7eb;">';
            html += '<div class="property-label" style="font-weight: bold; color: #667eea;">Group Information</div>';
            html += '<div class="property-group">';
            html += '<div class="property-label">Group Name</div>';
            html += `<input type="text" value="${attrs.name || 'Unnamed Group'}" disabled class="property-input bg-gray-100">`;
            html += '</div>';
            html += '<div class="property-group">';
            html += '<div class="property-label">Children Count</div>';
            try {
                const childrenCount = node.getChildren ? node.getChildren().length : (node.children ? node.children.length : 0);
                html += `<input type="text" value="${childrenCount} children" disabled class="property-input bg-gray-100">`;
            } catch (e) {
                html += `<input type="text" value="Unknown" disabled class="property-input bg-gray-100">`;
            }
            html += '</div>';
            html += '<p class="text-sm text-gray-500 italic mt-2">This is a Group element. If this is a table, make sure it has the name "quote-items-table".</p>';
            html += '</div>';
        }
        
        // Layer controls
        html += '<div class="property-group" style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 2px solid #e5e7eb;">';
        html += '<div class="property-label">Layer Order</div>';
        html += '<div style="display: flex; gap: 0.5rem;">';
        html += '<button onclick="moveLayer(\'up\')" class="btn btn-sm btn-secondary flex-1"><i class="fas fa-arrow-up"></i></button>';
        html += '<button onclick="moveLayer(\'down\')" class="btn btn-sm btn-secondary flex-1"><i class="fas fa-arrow-down"></i></button>';
        html += '<button onclick="moveLayer(\'top\')" class="btn btn-sm btn-secondary flex-1"><i class="fas fa-angle-double-up"></i></button>';
        html += '<button onclick="moveLayer(\'bottom\')" class="btn btn-sm btn-secondary flex-1"><i class="fas fa-angle-double-down"></i></button>';
        html += '</div>';
        html += '</div>';
        
        html += '</div>';
        
        console.log('Setting properties HTML, length:', html.length);
        console.log('HTML preview (first 500 chars):', html.substring(0, 500));
        propsContent.innerHTML = html;
        console.log('Properties HTML set successfully');
        console.log('Properties content element exists:', !!propsContent);
        console.log('Properties content innerHTML length after setting:', propsContent.innerHTML.length);
        
        // Attach event listeners
        try {
            attachPropertyListeners();
            console.log('Property listeners attached successfully');
        } catch (error) {
            console.error('Error attaching property listeners:', error);
        }
    }
    
    function rgbToHex(color) {
        if (color.startsWith('#')) return color;
        if (color === 'transparent') return '#ffffff';
        if (color === 'black') return '#000000';
        if (color === 'white') return '#ffffff';
        return color;
    }
    
    function attachPropertyListeners() {
        if (!selectedElement) return;
        
        const propX = document.getElementById('prop-x');
        const propY = document.getElementById('prop-y');
        const propText = document.getElementById('prop-text');
        const propFontSize = document.getElementById('prop-fontSize');
        const propFontFamily = document.getElementById('prop-fontFamily');
        const propFontStyle = document.getElementById('prop-fontStyle');
        const propFill = document.getElementById('prop-fill');
        const propStroke = document.getElementById('prop-stroke');
        const propStrokeWidth = document.getElementById('prop-strokeWidth');
        const propWidth = document.getElementById('prop-width');
        const propHeight = document.getElementById('prop-height');
        const propRadius = document.getElementById('prop-radius');
        const propOpacity = document.getElementById('prop-opacity');
        
        if (propX) propX.addEventListener('input', () => { selectedElement.x(parseFloat(propX.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propY) propY.addEventListener('input', () => { selectedElement.y(parseFloat(propY.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propText) {
            // Add both input and change listeners to ensure changes are captured
            propText.addEventListener('input', function() { 
                if (selectedElement && selectedElement.text) {
                    selectedElement.text(this.value); 
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
            propText.addEventListener('change', function() { 
                if (selectedElement && selectedElement.text) {
                    selectedElement.text(this.value); 
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
            // Also handle paste events
            propText.addEventListener('paste', function() {
                setTimeout(() => {
                    if (selectedElement && selectedElement.text) {
                        selectedElement.text(this.value);
                        layer.draw();
                        scheduleQuoteEditorHistoryPush();
                    }
                }, 10);
            });
        }
        if (propFontSize) propFontSize.addEventListener('input', () => { selectedElement.fontSize(parseFloat(propFontSize.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propFontFamily) propFontFamily.addEventListener('change', () => { selectedElement.fontFamily(propFontFamily.value); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propFontStyle) propFontStyle.addEventListener('change', () => { selectedElement.fontStyle(propFontStyle.value); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propFill) propFill.addEventListener('input', () => { selectedElement.fill(propFill.value); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propStroke) propStroke.addEventListener('input', () => { selectedElement.stroke(propStroke.value); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propStrokeWidth) propStrokeWidth.addEventListener('input', () => { selectedElement.strokeWidth(parseFloat(propStrokeWidth.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propWidth) propWidth.addEventListener('input', () => { selectedElement.width(parseFloat(propWidth.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propHeight) propHeight.addEventListener('input', () => { selectedElement.height(parseFloat(propHeight.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propRadius) propRadius.addEventListener('input', () => { selectedElement.radius(parseFloat(propRadius.value)); layer.draw(); scheduleQuoteEditorHistoryPush(); });
        if (propOpacity) {
            propOpacity.addEventListener('input', () => {
                selectedElement.opacity(parseFloat(propOpacity.value));
                document.getElementById('opacity-value').textContent = (propOpacity.value * 100) + '%';
                layer.draw();
                scheduleQuoteEditorHistoryPush();
            });
        }
        
        // Function to update decorative image element with uploaded image (moved outside to be accessible)
        window.updateDecorativeImageElement = function(element, imageUrl) {
            const elementName = element && element.attrs ? element.attrs.name : '';
            if (!element || !elementName || !elementName.includes('decorative-image')) {
                // Silently return if element is not a decorative image (this can happen when buttons exist but wrong element is selected)
                return;
            }
            
            console.log('updateDecorativeImageElement called with URL:', imageUrl);
            
            // Store image URL in element attributes - use setAttr to ensure it's saved
            element.setAttr('imageUrl', imageUrl || '');
            // Also ensure it's in attrs for proper serialization
            element.attrs.imageUrl = imageUrl || '';
            
            // Remove existing image if any
            const existingImage = element.findOne('Image');
            if (existingImage) {
                existingImage.destroy();
            }
            
            // Remove all placeholder elements
            const rect = element.findOne('Rect');
            const allTexts = element.find('Text');
            allTexts.forEach(textNode => {
                const text = textNode.text();
                if (text.includes('Decorative') || text.includes('🖼️')) {
                    textNode.destroy();
                }
            });
            
            if (rect) {
                rect.destroy();
            }
            
            if (imageUrl && imageUrl.trim() !== '') {
                console.log('Loading image from URL:', imageUrl);
                
                // Create a temporary image to check if it loads
                const tempImg = new Image();
                tempImg.crossOrigin = 'anonymous';
                
                tempImg.onload = function() {
                    console.log('Image verified, loading into Konva. Dimensions:', tempImg.width, 'x', tempImg.height);
                    
                    // Load and display the actual image
                    Konva.Image.fromURL(imageUrl, function(konvaImage) {
                        console.log('Konva image loaded successfully, dimensions:', konvaImage.width(), 'x', konvaImage.height());
                        
                        // Use actual image dimensions or default to 100x100
                        let width = konvaImage.width() || tempImg.width || 100;
                        let height = konvaImage.height() || tempImg.height || 100;
                        const aspectRatio = width / height;
                        
                        // Scale to fit within 200x200 but maintain aspect ratio
                        const maxSize = 200;
                        if (width > maxSize || height > maxSize) {
                            if (width > height) {
                                width = maxSize;
                                height = maxSize / aspectRatio;
                            } else {
                                height = maxSize;
                                width = maxSize * aspectRatio;
                            }
                        }
                        
                        // Preserve transparency - don't set fill or opacity that would override image alpha
                        konvaImage.setAttrs({
                            x: 0,
                            y: 0,
                            width: width,
                            height: height,
                            // Ensure transparency is preserved
                            opacity: 1.0,  // Don't override image's built-in alpha channel
                            listening: true
                        });
                        
                        // Update group size
                        element.width(width);
                        element.height(height);
                        
                        // Add image
                        element.add(konvaImage);
                        layer.draw();
                        scheduleQuoteEditorHistoryPush();
                        console.log('Image added to element, new dimensions:', width, 'x', height);
                        
                        // Force a redraw of the properties panel to show the image
                        if (selectedElement === element) {
                            setTimeout(() => {
                                updatePropertiesPanel(element);
                                attachPropertyListeners();
                            }, 100);
                        }
                    }, function(error) {
                        console.error('Konva failed to load image:', error);
                        alert('Failed to load image into canvas. The image URL is saved but may not display correctly.');
                    });
                };
                
                tempImg.onerror = function() {
                    console.error('Failed to load image (onerror):', imageUrl);
                    alert('Failed to load image. Please check the image URL: ' + imageUrl);
                };
                
                tempImg.src = imageUrl;
            } else {
                // Restore placeholder if image removed
                const newRect = new Konva.Rect({
                    x: 0,
                    y: 0,
                    width: 100,
                    height: 100,
                    fill: '#f0f0f0',
                    stroke: '#999',
                    strokeWidth: 2,
                    dash: [5, 5]
                });
                element.add(newRect);
                
                const newText = new Konva.Text({
                    x: 10,
                    y: 40,
                    text: 'Decorative\nImage',
                    fontSize: 12,
                    fontFamily: 'Arial',
                    fill: '#666',
                    align: 'center',
                    width: 80
                });
                element.add(newText);
                
                const newIcon = new Konva.Text({
                    x: 35,
                    y: 15,
                    text: '🖼️',
                    fontSize: 24,
                    width: 30,
                    align: 'center'
                });
                element.add(newIcon);
                
                element.width(100);
                element.height(100);
                layer.draw();
                scheduleQuoteEditorHistoryPush();
            }
        };
        
        // Decorative image upload handler
        const decorativeImageUpload = document.getElementById('decorative-image-upload');
        const btnUploadDecorativeImage = document.getElementById('btn-upload-decorative-image');
        const btnRemoveDecorativeImage = document.getElementById('btn-remove-decorative-image');
        
        if (btnUploadDecorativeImage) {
            btnUploadDecorativeImage.addEventListener('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const elementName = selectedElement && selectedElement.attrs ? selectedElement.attrs.name : '';
                // Only proceed if selected element is a decorative image
                // Check if name includes 'decorative-image' (it might be 'decorative-image element-overlap' or similar)
                if (!selectedElement || !elementName || !elementName.includes('decorative-image')) {
                    // Silently return - button might exist in DOM from previous selection
                    return;
                }
                // Get fresh reference to file input in case DOM was recreated
                const currentInput = document.getElementById('decorative-image-upload');
                if (currentInput) {
                    currentInput.click();
                } else {
                    console.error('File input element not found');
                }
            });
        }

        if (decorativeImageUpload) {
            decorativeImageUpload.addEventListener('change', function(e) {
                const file = e.target.files[0];
                if (!file) return;
                
                // Only proceed if selected element is a decorative image
                const elementName = selectedElement && selectedElement.attrs ? selectedElement.attrs.name : '';
                // Check if name includes 'decorative-image' (it might be 'decorative-image element-overlap' or similar)
                if (!selectedElement || !elementName || !elementName.includes('decorative-image')) {
                    // Silently return - file input might be triggered when wrong element is selected
                    return;
                }
                
                // Validate file type
                const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'];
                if (!allowedTypes.includes(file.type)) {
                    alert('{{ _("Only image files (PNG, JPG, JPEG, GIF, WEBP) are allowed") }}');
                    return;
                }
                
                // Validate file size (5MB)
                if (file.size > 5 * 1024 * 1024) {
                    alert('{{ _("File size must be less than 5MB") }}');
                    return;
                }
                
                // Upload image
                const formData = new FormData();
                formData.append('file', file);
                formData.append('csrf_token', '{{ csrf_token() }}');
                
                btnUploadDecorativeImage.disabled = true;
                btnUploadDecorativeImage.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>{{ _("Uploading...") }}';
                
                fetch('{{ url_for("admin.upload_template_image") }}', {
                    method: 'POST',
                    body: formData
                })
                .then(response => response.json())
                .then(data => {
                    console.log('Upload response:', data);
                    if (data.success && data.image_url) {
                        // Update the decorative image element with the uploaded image
                        const elementName = selectedElement && selectedElement.attrs ? selectedElement.attrs.name : '';
                        if (window.updateDecorativeImageElement && selectedElement && elementName && elementName.includes('decorative-image')) {
                            window.updateDecorativeImageElement(selectedElement, data.image_url);
                            // Refresh properties panel after image loads (wait longer for image to actually load)
                            setTimeout(() => {
                                updatePropertiesPanel(selectedElement);
                                // Re-attach event listeners after properties panel refresh
                                attachPropertyListeners();
                            }, 1000);
                        } else {
                            console.error('updateDecorativeImageElement function not found or invalid element');
                            alert('{{ _("Error: Image update function not found") }}');
                        }
                    } else {
                        alert(data.error || '{{ _("Failed to upload image") }}');
                    }
                })
                .catch(error => {
                    console.error('Upload error:', error);
                    alert('{{ _("Failed to upload image") }}');
                })
                .finally(() => {
                    btnUploadDecorativeImage.disabled = false;
                    btnUploadDecorativeImage.innerHTML = '<i class="fas fa-upload mr-2"></i>{{ _("Change Image") }}';
                    decorativeImageUpload.value = '';
                });
            });
        }

        if (btnRemoveDecorativeImage) {
            btnRemoveDecorativeImage.addEventListener('click', () => {
                // Only proceed if selected element is a decorative image
                const elementName = selectedElement && selectedElement.attrs ? selectedElement.attrs.name : '';
                if (!selectedElement || !elementName || !elementName.includes('decorative-image')) {
                    // Silently return - button might exist in DOM from previous selection
                    return;
                }
                if (confirm('{{ _("Are you sure you want to remove this image?") }}')) {
                    if (window.updateDecorativeImageElement) {
                        window.updateDecorativeImageElement(selectedElement, null);
                    }
                    updatePropertiesPanel(selectedElement);
                }
            });
        }
        
        // Table-specific property listeners
        const propTableHeaderText = document.getElementById('prop-table-header-text');
        const propTableHeaderFontSize = document.getElementById('prop-table-header-fontSize');
        const propTableHeaderFontStyle = document.getElementById('prop-table-header-fontStyle');
        const propTableHeaderFill = document.getElementById('prop-table-header-fill');
        const propTableItemsText = document.getElementById('prop-table-items-text');
        const propTableItemsFontSize = document.getElementById('prop-table-items-fontSize');
        const propTableItemsFill = document.getElementById('prop-table-items-fill');
        const propTableLineStroke = document.getElementById('prop-table-line-stroke');
        const propTableLineStrokeWidth = document.getElementById('prop-table-line-strokeWidth');
        const propTableWidth = document.getElementById('prop-table-width');
        
        if (propTableHeaderText) {
            propTableHeaderText.addEventListener('input', () => {
                const headerText = selectedElement.find('Text')[0];
                if (headerText) {
                    headerText.text(propTableHeaderText.value.replace(/\n/g, '\\n'));
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableHeaderFontSize) {
            propTableHeaderFontSize.addEventListener('input', () => {
                const headerText = selectedElement.find('Text')[0];
                if (headerText) {
                    headerText.fontSize(parseFloat(propTableHeaderFontSize.value));
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableHeaderFontStyle) {
            propTableHeaderFontStyle.addEventListener('change', () => {
                const headerText = selectedElement.find('Text')[0];
                if (headerText) {
                    headerText.fontStyle(propTableHeaderFontStyle.value);
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableHeaderFill) {
            propTableHeaderFill.addEventListener('input', () => {
                const headerText = selectedElement.find('Text')[0];
                if (headerText) {
                    headerText.fill(propTableHeaderFill.value);
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableItemsText) {
            propTableItemsText.addEventListener('input', () => {
                const itemsText = selectedElement.find('Text')[1];
                if (itemsText) {
                    itemsText.text(propTableItemsText.value.replace(/\n/g, '\\n'));
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableItemsFontSize) {
            propTableItemsFontSize.addEventListener('input', () => {
                const itemsText = selectedElement.find('Text')[1];
                if (itemsText) {
                    itemsText.fontSize(parseFloat(propTableItemsFontSize.value));
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableItemsFill) {
            propTableItemsFill.addEventListener('input', () => {
                const itemsText = selectedElement.find('Text')[1];
                if (itemsText) {
                    itemsText.fill(propTableItemsFill.value);
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableLineStroke) {
            propTableLineStroke.addEventListener('input', () => {
                const line = selectedElement.find('Line')[0];
                if (line) {
                    line.stroke(propTableLineStroke.value);
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableLineStrokeWidth) {
            propTableLineStrokeWidth.addEventListener('input', () => {
                const line = selectedElement.find('Line')[0];
                if (line) {
                    line.strokeWidth(parseFloat(propTableLineStrokeWidth.value));
                    layer.draw();
                    scheduleQuoteEditorHistoryPush();
                }
            });
        }
        
        if (propTableWidth) {
            propTableWidth.addEventListener('input', () => {
                const width = parseFloat(propTableWidth.value);
                const headerText = selectedElement.find('Text')[0];
                const itemsText = selectedElement.find('Text')[1];
                const line = selectedElement.find('Line')[0];
                
                if (headerText) headerText.width(width);
                if (itemsText) itemsText.width(width);
                if (line) {
                    const currentY = line.points()[1];
                    line.points([0, currentY, width, currentY]);
                }
                layer.draw();
                scheduleQuoteEditorHistoryPush();
            });
        }
    }
    
    // Layer management functions
    window.moveLayer = function(direction) {
        if (!selectedElement) return;
        
        if (direction === 'up') {
            selectedElement.moveUp();
        } else if (direction === 'down') {
            selectedElement.moveDown();
        } else if (direction === 'top') {
            selectedElement.moveToTop();
        } else if (direction === 'bottom') {
            selectedElement.moveToBottom();
        }
        
        layer.draw();
        scheduleQuoteEditorHistoryPush();
    };
    
    // Toolbar actions
    document.getElementById('btn-delete').addEventListener('click', function() {
        if (selectedElement) {
            selectedElement.destroy();
            layer.find('Transformer').forEach(t => t.destroy());
            selectedElement = null;
            layer.draw();
            document.getElementById('properties-content').innerHTML = '<p class="text-sm text-gray-500 italic">Select an element to edit its properties</p>';
            pushQuoteEditorHistory();
        }
    });
    
    document.getElementById('btn-clear').addEventListener('click', async function() {
        const confirmed = await showConfirm(
            '{{ _("Clear all elements?") }}',
            {
                title: '{{ _("Clear Canvas") }}',
                confirmText: '{{ _("Clear") }}',
                cancelText: '{{ _("Cancel") }}',
                variant: 'warning'
            }
        );
        if (confirmed) {
            layer.destroyChildren();
            layer.add(background);
            // Re-add page border after clearing
            const pageBorderAfterClear = layer.findOne('[name="page-border"]');
            if (!pageBorderAfterClear) {
                const currentPageSizeSelector = document.getElementById('page-size-selector');
                const currentPageSize = (currentPageSizeSelector && currentPageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
                const currentDimensions = PAGE_SIZE_DIMENSIONS[currentPageSize] || PAGE_SIZE_DIMENSIONS['A4'];
                const newPageBorder = new Konva.Rect({
                    x: 0,
                    y: 0,
                    width: currentDimensions.width,
                    height: currentDimensions.height,
                    stroke: '#667eea',
                    strokeWidth: 2,
                    fill: 'transparent',
                    name: 'page-border',
                    listening: false,
                    perfectDrawEnabled: false
                });
                layer.add(newPageBorder);
                newPageBorder.moveToBottom();
            }
            elements.length = 0;
            selectedElement = null;
            layer.draw();
        }
    });
    
    // Zoom controls (zoom scale is declared above with baseFitScale)
    const zoomDisplay = document.getElementById('zoom-display');
    
    function updateZoomDisplay() {
        if (zoomDisplay) {
            const percentage = Math.round((baseFitScale * zoomScale) * 100);
            zoomDisplay.textContent = `${percentage}%`;
        }
    }
    
    // Initialize zoom display after initial fit
    setTimeout(() => {
        updateZoomDisplay();
    }, 150);
    
    document.getElementById('btn-zoom-in').addEventListener('click', function() {
        zoomScale = Math.min(zoomScale + 0.1, 2);
        stage.scale({ x: baseFitScale * zoomScale, y: baseFitScale * zoomScale });
        layer.draw();
        updateZoomDisplay();
    });
    
    document.getElementById('btn-zoom-out').addEventListener('click', function() {
        zoomScale = Math.max(zoomScale - 0.1, 0.5);
        stage.scale({ x: baseFitScale * zoomScale, y: baseFitScale * zoomScale });
        layer.draw();
        updateZoomDisplay();
    });

    const quoteWheelZoomContainer = document.getElementById('canvas-container');
    if (quoteWheelZoomContainer) {
        quoteWheelZoomContainer.addEventListener('wheel', function(ev) {
            if (!stage) return;
            ev.preventDefault();
            const step = 0.06;
            if (ev.deltaY < 0) {
                zoomScale = Math.min(zoomScale + step, 2);
            } else {
                zoomScale = Math.max(zoomScale - step, 0.5);
            }
            stage.scale({ x: baseFitScale * zoomScale, y: baseFitScale * zoomScale });
            layer.draw();
            updateZoomDisplay();
        }, { passive: false });
    }
    
    // Generate preview
    // ============================================
    // Enhanced PDF Preview Modal - Complete Rewrite
    // ============================================
    
    try {
    // State Management
    const previewState = {
        isOpen: false,
        zoomLevel: 100,
        pageSize: CURRENT_PAGE_SIZE || 'A4',
        isLoading: false,
        hasError: false,
        errorMessage: '',
        currentRequest: null,
        isFullscreen: false,
        fitMode: 'auto' // 'auto', 'width', 'height', 'actual'
    };
    
    // DOM Elements - with safety checks
    const previewModal = document.getElementById('preview-modal');
    const previewModalOverlay = document.getElementById('preview-modal-overlay');
    const previewModalClose = document.getElementById('preview-modal-close');
    const previewFrame = document.getElementById('preview-frame');
    const previewFrameWrapper = document.getElementById('preview-frame-wrapper');
    const previewLoading = document.getElementById('preview-loading');
    const previewError = document.getElementById('preview-error');
    const previewErrorMessage = document.getElementById('preview-error-message');
    const previewPageSizeBadge = document.getElementById('preview-page-size-badge');
    const previewPageSizeSelect = document.getElementById('preview-page-size-select');
    const previewZoomSlider = document.getElementById('preview-zoom-slider');
    const previewZoomDisplay = document.getElementById('preview-zoom-display');
    const previewBtnZoomIn = document.getElementById('preview-btn-zoom-in');
    const previewBtnZoomOut = document.getElementById('preview-btn-zoom-out');
    const previewBtnZoomReset = document.getElementById('preview-btn-zoom-reset');
    const previewBtnFitWidth = document.getElementById('preview-btn-fit-width');
    const previewBtnFitHeight = document.getElementById('preview-btn-fit-height');
    const previewBtnActualSize = document.getElementById('preview-btn-actual-size');
    const previewBtnRefresh = document.getElementById('preview-btn-refresh');
    const previewBtnFullscreen = document.getElementById('preview-btn-fullscreen');
    const previewBtnRetry = document.getElementById('preview-btn-retry');
    const previewBtnCloseError = document.getElementById('preview-btn-close-error');
    
    // Early return if essential elements are missing
    if (!previewModal || !previewFrame) {
        console.error('Preview modal essential elements not found. Modal may not work correctly.');
    }
    
    // Utility Functions
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }
    
    function updateZoomDisplay() {
        if (previewZoomDisplay) {
            previewZoomDisplay.textContent = previewState.zoomLevel + '%';
        }
        if (previewZoomSlider) {
            previewZoomSlider.value = previewState.zoomLevel;
        }
    }
    
    function applyZoom() {
        if (!previewFrame || !previewFrame.contentDocument) return;
        const zoom = previewState.zoomLevel / 100;
        
        // Apply zoom to the preview-container inside the iframe, not the wrapper
        const container = previewFrame.contentDocument.querySelector('.preview-container');
        if (container) {
            container.style.transform = `scale(${zoom})`;
            // Use top center origin so content expands from top, allowing scrolling to top
            container.style.transformOrigin = 'top center';
        }
        
        // Ensure iframe body and html allow scrolling and can expand
        const iframeDoc = previewFrame.contentDocument;
        const iframeBody = iframeDoc.body;
        const iframeHtml = iframeDoc.documentElement;
        if (iframeBody) {
            iframeBody.style.overflow = 'auto';
            iframeBody.style.overflowX = 'auto';
            iframeBody.style.overflowY = 'auto';
            // Remove height constraints to allow expansion
            iframeBody.style.height = 'auto';
            iframeBody.style.minHeight = '100vh';
        }
        if (iframeHtml) {
            iframeHtml.style.overflow = 'auto';
            iframeHtml.style.overflowX = 'auto';
            iframeHtml.style.overflowY = 'auto';
            // Remove height constraint to allow expansion
            iframeHtml.style.height = 'auto';
        }
        
        // Reset wrapper transform - we don't want to scale the iframe itself
        if (previewFrameWrapper) {
            previewFrameWrapper.style.transform = 'none';
            previewFrameWrapper.style.transformOrigin = 'initial';
        }
    }
    
    function setZoom(level) {
        previewState.zoomLevel = Math.max(25, Math.min(200, level));
        updateZoomDisplay();
        applyZoom();
    }
    
    function zoomIn() {
        const steps = [25, 50, 75, 100, 125, 150, 175, 200];
        const current = previewState.zoomLevel;
        const next = steps.find(s => s > current) || 200;
        setZoom(next);
    }
    
    function zoomOut() {
        const steps = [25, 50, 75, 100, 125, 150, 175, 200];
        const current = previewState.zoomLevel;
        const next = [...steps].reverse().find(s => s < current) || 25;
        setZoom(next);
    }
    
    function resetZoom() {
        setZoom(100);
        previewState.fitMode = 'auto';
        updateFitButtons();
    }
    
    function fitToPage() {
        // Fit the entire page (both width and height) to fit in viewport
        try {
            if (!previewFrame || !previewFrameWrapper) {
                previewState.fitMode = 'auto';
                updateFitButtons();
                return;
            }
            if (!previewFrame.contentDocument) {
                previewState.fitMode = 'auto';
                updateFitButtons();
                return;
            }
            const wrapper = previewFrameWrapper;
            if (!wrapper.parentElement) return;
            
            // Get available dimensions accounting for padding
            const parentPadding = 48; // 1.5rem * 2 = 48px
            const availableWidth = wrapper.parentElement.clientWidth - parentPadding;
            const availableHeight = wrapper.parentElement.clientHeight - parentPadding;
            
            const frameDoc = previewFrame.contentDocument;
            const container = frameDoc.querySelector('.preview-container');
            if (container) {
                // Get base dimensions (before any zoom)
                const containerWidth = container.offsetWidth || container.scrollWidth || container.clientWidth;
                const containerHeight = container.offsetHeight || container.scrollHeight || container.clientHeight;
                const currentZoom = previewState.zoomLevel / 100;
                const baseWidth = containerWidth / (currentZoom || 1);
                const baseHeight = containerHeight / (currentZoom || 1);
                
                if (baseWidth > 0 && baseHeight > 0 && availableWidth > 0 && availableHeight > 0) {
                    // Calculate zoom to fit both dimensions with margin
                    const margin = 40; // Margin on all sides
                    const zoomX = ((availableWidth - margin) / baseWidth) * 100;
                    const zoomY = ((availableHeight - margin) / baseHeight) * 100;
                    // Use the smaller zoom to ensure everything fits
                    const zoom = Math.min(zoomX, zoomY, 200); // Cap at 200%
                    setZoom(Math.max(25, Math.round(zoom))); // Min 25%
                    previewState.fitMode = 'auto';
                    updateFitButtons();
                }
            }
        } catch (error) {
            console.warn('Error in fitToPage:', error);
            // Fallback
            setZoom(100);
            previewState.fitMode = 'auto';
            updateFitButtons();
        }
    }
    
    function fitToWidth() {
        try {
            if (!previewFrame || !previewFrameWrapper) {
                previewState.fitMode = 'width';
                updateFitButtons();
                return;
            }
            if (!previewFrame.contentDocument) {
                // If iframe not loaded yet, set mode and wait
                previewState.fitMode = 'width';
                updateFitButtons();
                return;
            }
            const wrapper = previewFrameWrapper;
            if (!wrapper.parentElement) return;
            
            // Get available width accounting for padding (1.5rem = 24px on each side = 48px total)
            const parentPadding = 48; // 1.5rem * 2 = 48px
            const availableWidth = wrapper.parentElement.clientWidth - parentPadding;
            
            const frameDoc = previewFrame.contentDocument;
            const container = frameDoc.querySelector('.preview-container');
            if (container) {
                // Get the actual content width from the container (before any zoom)
                // We need to get the base width, not the scaled width
                const containerWidth = container.offsetWidth || container.scrollWidth || container.clientWidth;
                // If container has a transform scale, we need to account for it
                const currentTransform = container.style.transform;
                const currentZoom = previewState.zoomLevel / 100;
                const baseWidth = containerWidth / (currentZoom || 1);
                
                if (baseWidth > 0 && availableWidth > 0) {
                    // Calculate zoom to fit width with a small margin for better appearance
                    const margin = 20; // Small margin
                    const zoom = ((availableWidth - margin) / baseWidth) * 100;
                    setZoom(Math.min(200, Math.max(25, Math.round(zoom))));
                    previewState.fitMode = 'width';
                    updateFitButtons();
                }
            }
        } catch (error) {
            console.warn('Error in fitToWidth:', error);
            // Fallback
            setZoom(100);
            previewState.fitMode = 'width';
            updateFitButtons();
        }
    }
    
    function fitToHeight() {
        try {
            if (!previewFrame || !previewFrameWrapper) {
                previewState.fitMode = 'height';
                updateFitButtons();
                return;
            }
            if (!previewFrame.contentDocument) {
                // If iframe not loaded yet, set mode and wait
                previewState.fitMode = 'height';
                updateFitButtons();
                return;
            }
            const wrapper = previewFrameWrapper;
            if (!wrapper.parentElement) return;
            
            // Get available height accounting for padding
            const parentPadding = 48; // 1.5rem * 2 = 48px
            const availableHeight = wrapper.parentElement.clientHeight - parentPadding;
            
            const frameDoc = previewFrame.contentDocument;
            const container = frameDoc.querySelector('.preview-container');
            if (container) {
                // Get base height before any zoom
                const containerHeight = container.offsetHeight || container.scrollHeight || container.clientHeight;
                const currentZoom = previewState.zoomLevel / 100;
                const baseHeight = containerHeight / (currentZoom || 1);
                
                if (baseHeight > 0 && availableHeight > 0) {
                    // Calculate zoom to fit height with a small margin
                    const margin = 20; // Small margin
                    const zoom = ((availableHeight - margin) / baseHeight) * 100;
                    setZoom(Math.min(200, Math.max(25, Math.round(zoom))));
                    previewState.fitMode = 'height';
                    updateFitButtons();
                }
            }
        } catch (error) {
            console.warn('Error in fitToHeight:', error);
            // Fallback
            setZoom(100);
            previewState.fitMode = 'height';
            updateFitButtons();
        }
    }
    
    function actualSize() {
        setZoom(100);
        previewState.fitMode = 'actual';
        updateFitButtons();
    }
    
    function updateFitButtons() {
        const activeClass = 'preview-control-btn-active';
        [previewBtnFitWidth, previewBtnFitHeight, previewBtnActualSize].forEach(btn => {
            btn.classList.remove(activeClass);
        });
        
        if (previewState.fitMode === 'width') {
            previewBtnFitWidth.classList.add(activeClass);
        } else if (previewState.fitMode === 'height') {
            previewBtnFitHeight.classList.add(activeClass);
        } else if (previewState.fitMode === 'actual') {
            previewBtnActualSize.classList.add(activeClass);
        }
    }
    
    function showLoading() {
        previewState.isLoading = true;
        previewState.hasError = false;
        if (previewLoading) previewLoading.classList.add('active');
        if (previewError) previewError.style.display = 'none';
        if (previewFrame) previewFrame.style.display = 'none';
        
        // Simulate progress
        const progressBar = document.getElementById('preview-loading-progress-bar');
        if (progressBar) {
            progressBar.style.width = '0%';
            let progress = 0;
            const interval = setInterval(() => {
                if (!previewState.isLoading) {
                    clearInterval(interval);
                    return;
                }
                progress = Math.min(90, progress + Math.random() * 10);
                progressBar.style.width = progress + '%';
            }, 200);
        }
    }
    
    function hideLoading() {
        previewState.isLoading = false;
        if (previewLoading) previewLoading.classList.remove('active');
        if (previewFrame) previewFrame.style.display = 'block';
    }
    
    function showError(message) {
        previewState.hasError = true;
        previewState.errorMessage = message;
        if (previewErrorMessage) previewErrorMessage.textContent = message;
        if (previewError) previewError.style.display = 'flex';
        if (previewLoading) previewLoading.classList.remove('active');
        if (previewFrame) previewFrame.style.display = 'none';
    }
    
    function hideError() {
        previewState.hasError = false;
        if (previewError) previewError.style.display = 'none';
    }
    
    function updatePageSizeBadge(size) {
        if (previewPageSizeBadge) previewPageSizeBadge.textContent = size;
        if (previewPageSizeSelect) {
            previewPageSizeSelect.value = size;
        }
    }
    
    function openPreviewModal() {
        if (!previewModal) {
            console.error('Preview modal element not found');
            return;
        }
        previewState.isOpen = true;
        previewModal.style.display = 'block';
        document.body.style.overflow = 'hidden';
        
        // Sync preview page size with main page size selector
        const mainPageSizeSelector = document.getElementById('page-size-selector');
        if (mainPageSizeSelector) {
            previewState.pageSize = mainPageSizeSelector.value || CURRENT_PAGE_SIZE || 'A4';
        } else {
            previewState.pageSize = CURRENT_PAGE_SIZE || 'A4';
        }
        
        // Reset state
        setZoom(100);
        updatePageSizeBadge(previewState.pageSize);
        
        // Update preview page size selector to match
        if (previewPageSizeSelect) {
            previewPageSizeSelect.value = previewState.pageSize;
        }
        
        // Focus management for accessibility
        previewModal.setAttribute('aria-hidden', 'false');
        setTimeout(() => {
            if (previewBtnRefresh) {
                previewBtnRefresh.focus();
            }
        }, 100);
    }
    
    function closePreviewModal() {
        if (!previewModal) return;
        previewState.isOpen = false;
        previewModal.style.display = 'none';
        document.body.style.overflow = '';
        previewModal.setAttribute('aria-hidden', 'true');
        
        // Cancel any ongoing requests
        if (previewState.currentRequest && typeof previewState.currentRequest.abort === 'function') {
            try {
                previewState.currentRequest.abort();
            } catch (e) {
                console.warn('Error aborting request:', e);
            }
            previewState.currentRequest = null;
        }
        
        // Reset state
        hideError();
        hideLoading();
        previewState.isFullscreen = false;
        updateFullscreenButton();
    }
    
    function toggleFullscreen() {
        if (!document.fullscreenElement) {
            const modalContent = document.querySelector('.preview-modal-content');
            if (modalContent && modalContent.requestFullscreen) {
                modalContent.requestFullscreen().then(() => {
                    previewState.isFullscreen = true;
                    updateFullscreenButton();
                }).catch(err => {
                    console.error('Error entering fullscreen:', err);
                });
            } else {
                // Fallback: try on modal itself
                previewModal.requestFullscreen().then(() => {
                    previewState.isFullscreen = true;
                    updateFullscreenButton();
                }).catch(err => {
                    console.error('Error entering fullscreen:', err);
                });
            }
        } else {
            document.exitFullscreen().then(() => {
                previewState.isFullscreen = false;
                updateFullscreenButton();
            });
        }
    }
    
    function updateFullscreenButton() {
        if (!previewBtnFullscreen) return;
        const icon = previewBtnFullscreen.querySelector('i');
        if (icon) {
            if (previewState.isFullscreen) {
                icon.className = 'fas fa-compress';
                previewBtnFullscreen.setAttribute('title', '{{ _("Exit Fullscreen") }}');
            } else {
                icon.className = 'fas fa-expand';
                previewBtnFullscreen.setAttribute('title', '{{ _("Toggle Fullscreen") }}');
            }
        }
    }
    
    async function loadPreview(pageSize = null) {
        if (pageSize) {
            previewState.pageSize = pageSize;
            updatePageSizeBadge(pageSize);
            // Update preview selector to match
            if (previewPageSizeSelect) {
                previewPageSizeSelect.value = pageSize;
            }
        }
        
        showLoading();
        hideError();
        
        // For preview, we want to load the saved template for the selected page size
        // So we send the page_size and let the backend load the correct template
        // We still send the current canvas JSON as a fallback, but backend will use saved template if available
        const { html, css, json } = generateCode();
        
        const fd = new FormData();
        fd.append('html', html);
        fd.append('css', css);
        // Send template_json only if we're previewing the current page size
        // Otherwise, let backend load the template for the selected page size
        const mainPageSizeSelector = document.getElementById('page-size-selector');
        const currentMainPageSize = (mainPageSizeSelector && mainPageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
        const pageSizeForPreview = previewState.pageSize;
        
        // Only send template_json if previewing the same size as the current editor
        // Otherwise, backend will load the saved template for the selected page size
        if (pageSizeForPreview === currentMainPageSize && json && json.trim()) {
            fd.append('template_json', json);
        }
        
        fd.append('page_size', pageSizeForPreview);
        fd.append('csrf_token', CSRF_TOKEN);
        
        const previewUrl = PREVIEW_URL + '?t=' + Date.now();
        
        // Create abort controller for request cancellation (if supported)
        let controller = null;
        let signal = null;
        if (typeof AbortController !== 'undefined' && typeof AbortSignal !== 'undefined') {
            try {
                controller = new AbortController();
                if (controller && controller.signal) {
                    // Verify signal is a proper AbortSignal instance
                    const testSignal = controller.signal;
                    if (testSignal instanceof AbortSignal) {
                        signal = testSignal;
                        previewState.currentRequest = controller;
                    }
                }
            } catch (e) {
                console.warn('AbortController not available:', e);
            }
        }
        
        // Build fetch options
        const fetchOptions = {
            method: 'POST',
            body: fd,
            cache: 'no-cache',
            headers: {
                'Cache-Control': 'no-cache'
            }
        };
        
        // Only add signal if it's a valid AbortSignal instance and not aborted
        if (signal instanceof AbortSignal && !signal.aborted) {
            fetchOptions.signal = signal;
        }
        
        try {
            const response = await fetch(previewUrl, fetchOptions);
            
            if (!response || !response.ok) {
                throw new Error(`HTTP error! status: ${response ? response.status : 'unknown'}`);
            }
            
            const previewHtml = await response.text();
            
            // Check if request was cancelled
            if (controller && controller.signal && controller.signal.aborted) {
                return;
            }
            
            const doc = previewFrame.contentDocument || previewFrame.contentWindow.document;
            doc.open();
            doc.write(previewHtml);
            doc.close();
            
            // Wait for iframe to load
            previewFrame.onload = () => {
                hideLoading();
                setTimeout(() => {
                    try {
                        // Always fit to page on initial load to show complete quote
                        setTimeout(() => {
                            try {
                                fitToPage();
                            } catch (fitError) {
                                console.warn('Error fitting to page:', fitError);
                                // Fallback to 100% zoom
                                setZoom(100);
                            }
                        }, 300);
                    } catch (zoomError) {
                        console.warn('Error applying zoom:', zoomError);
                        hideLoading();
                    }
                }, 100);
            };
            
            // Also handle load event if already loaded
            if (previewFrame.contentDocument && previewFrame.contentDocument.readyState === 'complete') {
                try {
                    previewFrame.onload();
                } catch (e) {
                    console.warn('Error calling onload handler:', e);
                }
            }
            
        } catch (err) {
            if (err.name === 'AbortError') {
                console.log('Preview request cancelled');
                return;
            }
            console.error('Preview error:', err);
            showError(err.message || '{{ _("Failed to load preview. Please try again.") }}');
        } finally {
            previewState.currentRequest = null;
        }
    }
    
    
    // Event Listeners - with safety checks
    if (previewModalOverlay) {
        previewModalOverlay.addEventListener('click', closePreviewModal);
    }
    if (previewModalClose) {
        previewModalClose.addEventListener('click', closePreviewModal);
    }
    
    // Keyboard shortcuts
    document.addEventListener('keydown', function(e) {
        if (!previewState.isOpen) return;
        
        // Close on Escape
        if (e.key === 'Escape') {
            if (previewState.isFullscreen) {
                toggleFullscreen();
            } else {
                closePreviewModal();
            }
            return;
        }
        
        // Zoom shortcuts (Ctrl/Cmd + Plus/Minus/0)
        if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
            if (e.key === '+' || e.key === '=') {
                e.preventDefault();
                zoomIn();
            } else if (e.key === '-' || e.key === '_') {
                e.preventDefault();
                zoomOut();
            } else if (e.key === '0') {
                e.preventDefault();
                resetZoom();
            }
        }
        
        // Fullscreen (F11)
        if (e.key === 'F11') {
            e.preventDefault();
            toggleFullscreen();
        }
    });
    
    // Zoom controls (with safety checks)
    if (previewBtnZoomIn) previewBtnZoomIn.addEventListener('click', zoomIn);
    if (previewBtnZoomOut) previewBtnZoomOut.addEventListener('click', zoomOut);
    if (previewBtnZoomReset) previewBtnZoomReset.addEventListener('click', resetZoom);
    if (previewBtnFitWidth) previewBtnFitWidth.addEventListener('click', fitToWidth);
    if (previewBtnFitHeight) previewBtnFitHeight.addEventListener('click', fitToHeight);
    if (previewBtnActualSize) previewBtnActualSize.addEventListener('click', actualSize);
    
    // Zoom slider with debounce
    if (previewZoomSlider) {
        const debouncedZoom = debounce((value) => {
            setZoom(parseInt(value));
        }, 50);
        
        previewZoomSlider.addEventListener('input', (e) => {
            if (previewZoomDisplay) {
                previewZoomDisplay.textContent = e.target.value + '%';
            }
            debouncedZoom(e.target.value);
        });
    }
    
    // Page size selector - syncs with main selector and loads correct template
    if (previewPageSizeSelect) {
        previewPageSizeSelect.addEventListener('change', (e) => {
            const newSize = e.target.value;
            if (confirm('{{ _("Changing page size will reload the preview with the template for that size. Continue?") }}')) {
                // Update main page size selector to match (for consistency)
                const mainPageSizeSelector = document.getElementById('page-size-selector');
                if (mainPageSizeSelector) {
                    mainPageSizeSelector.value = newSize;
                }
                // Load preview with new size (this will load the template for that size)
                loadPreview(newSize);
            } else {
                e.target.value = previewState.pageSize;
            }
        });
    }
    
    // Toolbar buttons
    if (previewBtnRefresh) previewBtnRefresh.addEventListener('click', () => loadPreview());
    if (previewBtnFullscreen) previewBtnFullscreen.addEventListener('click', toggleFullscreen);
    
    // Error handling
    if (previewBtnRetry) {
        previewBtnRetry.addEventListener('click', () => {
            hideError();
            loadPreview();
        });
    }
    
    if (previewBtnCloseError) {
        previewBtnCloseError.addEventListener('click', () => {
            hideError();
        });
    }
    
    // Fullscreen change detection
    document.addEventListener('fullscreenchange', () => {
        previewState.isFullscreen = !!document.fullscreenElement;
        updateFullscreenButton();
    });
    
    // Main preview button
    const btnPreview = document.getElementById('btn-preview');
    if (btnPreview) {
        btnPreview.addEventListener('click', function() {
            openPreviewModal();
            loadPreview();
        });
    } else {
        console.warn('Preview button not found');
    }
    
    } catch (error) {
        console.error('Error initializing preview modal:', error);
        // Fallback: simple preview functionality
        const btnPreview = document.getElementById('btn-preview');
        if (btnPreview) {
            btnPreview.addEventListener('click', function() {
                alert('{{ _("Preview functionality is currently unavailable. Please check the browser console for errors.") }}');
            });
        }
    }
    
    // Generate code from canvas
    function generateCode() {
        try {
            // Get current page size and dimensions
            const currentSize = (pageSizeSelector && pageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
            const dimensions = PAGE_SIZE_DIMENSIONS[currentSize] || PAGE_SIZE_DIMENSIONS['A4'];
            
            // Convert pixels to points (1 point = 1/72 inch, at 72 DPI: 1px = 1pt)
            function pxToPt(px) {
                return Math.round(px || 0);
            }
            
            // Build ReportLab template JSON structure
            const templateJson = {
                page: {
                    size: currentSize,
                    margin: {
                        top: 20,
                        right: 20,
                        bottom: 20,
                        left: 20
                    }
                },
                elements: [],
                styles: {
                    default: {
                        font: "Helvetica",
                        size: 10,
                        color: "#000000"
                    }
                }
            };
            
            // Legacy HTML/CSS generation for preview (backward compatibility)
            let bodyContent = '';
            
            if (!layer || !layer.children) {
                console.error('Layer or children not found');
                return { html: '<div class="quote-wrapper"></div>', css: '', json: JSON.stringify(templateJson, null, 2) };
            }
            
            layer.children.forEach((child, index) => {
                if (!child || child === background || child.className === 'Transformer') return;
                
                // Filter out invisible elements
                if (child.attrs && (child.attrs.opacity === 0 || child.attrs.visible === false)) {
                    console.log('Skipping invisible element in code generation:', child.className, child.attrs.name || 'unnamed');
                    return;
                }
                
                // Filter out grid lines and page border - they should not be included in the exported template
                if (child.className === 'Line' && child.attrs.name === 'grid-line') return;
                if (child.className === 'Rect' && child.attrs.name === 'page-border') return;
                // Filter out warning indicator dots - they should not be included in the exported template
                if (child.className === 'Circle' && child.attrs.name === 'warning-indicator') return;
                
                // Filter out background rectangles - any rectangle with name="background" or matching full page dimensions
                if (child.className === 'Rect') {
                    const rectName = child.attrs.name || '';
                    const rectX = child.attrs.x || 0;
                    const rectY = child.attrs.y || 0;
                    const rectWidth = child.attrs.width || 0;
                    const rectHeight = child.attrs.height || 0;
                    const rectFill = child.attrs.fill || '';
                    const rectStroke = child.attrs.stroke || '';
                    
                    // Filter out if name is "background" or "page-border"
                    if (rectName === 'background' || rectName === 'page-border') {
                        console.log('Skipping rectangle with name:', rectName);
                        return;
                    }
                    
                    // CRITICAL FIX: Filter out rectangles that are children of decorative-image groups
                    // These are placeholder rectangles that should not appear in the preview
                    const parent = child.getParent && child.getParent();
                    if (parent) {
                        const parentName = parent.attrs ? (parent.attrs.name || '') : '';
                        if (parentName && parentName.includes('decorative-image')) {
                            console.log('Skipping rectangle inside decorative-image group:', {rectName, parentName});
                            return;
                        }
                    }
                    
                    // Filter out zero-sized rectangles
                    if (rectWidth === 0 && rectHeight === 0) {
                        console.log('Skipping zero-sized rectangle');
                        return;
                    }
                    
                    // Filter out full-page rectangles at 0,0 with white/transparent fill and black stroke (unwanted borders)
                    // Check if it matches page dimensions (within 5px tolerance)
                    const pageWidth = dimensions.width;
                    const pageHeight = dimensions.height;
                    const isFullPage = Math.abs(rectX) < 5 && Math.abs(rectY) < 5 && 
                                      Math.abs(rectWidth - pageWidth) < 5 && 
                                      Math.abs(rectHeight - pageHeight) < 5;
                    
                    if (isFullPage && (rectFill === 'white' || rectFill === '#ffffff' || rectFill === 'transparent' || rectFill === '') && 
                        (rectStroke === 'black' || rectStroke === '#000000')) {
                        console.log('Filtering out unwanted full-page rectangle border:', {x: rectX, y: rectY, width: rectWidth, height: rectHeight, fill: rectFill, stroke: rectStroke, name: rectName});
                        return;
                    }
                    
                    // Filter out rectangles that are completely outside page bounds (likely artifacts)
                    if (rectX + rectWidth < -10 || rectY + rectHeight < -10 || 
                        rectX > pageWidth + 10 || rectY > pageHeight + 10) {
                        console.log('Skipping rectangle outside page bounds:', {x: rectX, y: rectY, width: rectWidth, height: rectHeight});
                        return;
                    }
                }
                
                const attrs = child.attrs;
                const x = Math.round(attrs.x || 0);
                const y = Math.round(attrs.y || 0);
                const opacity = attrs.opacity !== undefined ? attrs.opacity : 1;
                
                // Debug logging for elements being included
                console.log(`Including element in template JSON: type=${child.className}, name=${attrs.name || 'unnamed'}, x=${x}, y=${y}, width=${attrs.width || 'N/A'}, height=${attrs.height || 'N/A'}`);
                
                if (child.className === 'Text') {
                const fontSize = attrs.fontSize || 14;
                const fontFamily = attrs.fontFamily || 'Arial';
                const fontStyle = attrs.fontStyle || 'normal';
                const fontWeight = fontStyle === 'bold' ? 'bold' : 'normal';
                const fontStyleCss = fontStyle === 'italic' ? 'italic' : 'normal';
                const color = attrs.fill || 'black';
                const text = attrs.text || '';
                const textAlign = attrs.align || 'left';
                
                // Map font family to ReportLab font names
                let reportLabFont = 'Helvetica';
                if (fontFamily.toLowerCase().includes('arial')) {
                    reportLabFont = 'Helvetica';
                } else if (fontFamily.toLowerCase().includes('times')) {
                    reportLabFont = 'Times-Roman';
                } else if (fontFamily.toLowerCase().includes('courier')) {
                    reportLabFont = 'Courier';
                }
                if (fontWeight === 'bold') {
                    reportLabFont += '-Bold';
                } else if (fontStyleCss === 'italic') {
                    reportLabFont += '-Oblique';
                }
                
                // Add to ReportLab template JSON
                templateJson.elements.push({
                    type: 'text',
                    x: pxToPt(x),
                    y: pxToPt(y),
                    text: text,
                    width: pxToPt(attrs.width || 400),
                    style: {
                        font: reportLabFont,
                        size: fontSize,
                        color: color,
                        align: textAlign,
                        opacity: opacity
                    }
                });
                
                // Legacy HTML for preview
                const escapedText = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
                bodyContent += `  <div class="element text-element" style="position:absolute;left:${x}px;top:${y}px;font-size:${fontSize}px;font-family:'${fontFamily}';font-weight:${fontWeight};font-style:${fontStyleCss};color:${color};opacity:${opacity};width:${attrs.width || 400}px;text-align:${textAlign}">${escapedText}</div>\n`;
            } else if (child.className === 'Image') {
                const w = Math.round(attrs.width || 100);
                const h = Math.round(attrs.height || 50);
                
                // Add to ReportLab template JSON - image source needs Jinja2 template syntax
                {% raw %}
                const imageSource = '{{ get_logo_base64(settings.get_logo_path()) if settings.has_logo() and settings.get_logo_path() else "" }}';
                {% endraw %}
                templateJson.elements.push({
                    type: 'image',
                    x: pxToPt(x),
                    y: pxToPt(y),
                    width: pxToPt(w),
                    height: pxToPt(h),
                    source: imageSource,
                    opacity: opacity
                });
                
                // Legacy HTML for preview
                {% raw %}
                bodyContent += `  <img src="{{ get_logo_base64(settings.get_logo_path()) if settings.has_logo() and settings.get_logo_path() else '' }}" style="position:absolute;left:${x}px;top:${y}px;width:${w}px;height:${h}px;opacity:${opacity}" alt="Logo">\n`;
                {% endraw %}
            } else if (child.className === 'Rect') {
                const w = Math.round(attrs.width || 100);
                const h = Math.round(attrs.height || 100);
                const fill = attrs.fill || 'transparent';
                const stroke = attrs.stroke || 'black';
                const strokeWidth = attrs.strokeWidth || 1;
                
                // Add to ReportLab template JSON
                templateJson.elements.push({
                    type: 'rectangle',
                    x: pxToPt(x),
                    y: pxToPt(y),
                    width: pxToPt(w),
                    height: pxToPt(h),
                    style: {
                        fill: fill !== 'transparent' ? fill : null,
                        stroke: stroke,
                        strokeWidth: strokeWidth,
                        opacity: opacity
                    }
                });
                
                // Legacy HTML for preview
                bodyContent += `  <div class="rectangle-element" style="position:absolute;left:${x}px;top:${y}px;width:${w}px;height:${h}px;background:${fill};border:${strokeWidth}px solid ${stroke};opacity:${opacity}"></div>\n`;
            } else if (child.className === 'Circle') {
                const radius = Math.round(attrs.radius || 50);
                const fill = attrs.fill || 'transparent';
                const stroke = attrs.stroke || 'black';
                const strokeWidth = attrs.strokeWidth || 1;
                const adjustedX = x - radius;
                const adjustedY = y - radius;
                
                // Add to ReportLab template JSON
                templateJson.elements.push({
                    type: 'circle',
                    x: pxToPt(x),
                    y: pxToPt(y),
                    width: pxToPt(radius * 2),
                    height: pxToPt(radius * 2),
                    style: {
                        fill: fill !== 'transparent' ? fill : null,
                        stroke: stroke,
                        strokeWidth: strokeWidth,
                        opacity: opacity
                    }
                });
                
                // Legacy HTML for preview
                bodyContent += `  <div class="circle-element" style="position:absolute;left:${adjustedX}px;top:${adjustedY}px;width:${radius*2}px;height:${radius*2}px;background:${fill};border:${strokeWidth}px solid ${stroke};border-radius:50%;opacity:${opacity}"></div>\n`;
            } else if (child.className === 'Line') {
                const points = attrs.points || [];
                const stroke = attrs.stroke || 'black';
                const strokeWidth = attrs.strokeWidth || 1;
                const lineName = attrs.name || '';
                const lineX = attrs.x || 0;
                const lineY = attrs.y || 0;
                const lineWidth = attrs.width || 0;
                
                // Filter out unwanted border lines at position (0,0) or very close with full page width
                const pageWidth = dimensions.width;
                const isAtOrigin = Math.abs(lineX) < 5 && Math.abs(lineY) < 5;
                const isFullWidth = lineWidth > pageWidth * 0.9 || (points.length >= 4 && Math.abs(points[2] - points[0]) > pageWidth * 0.9);
                
                // Filter out lines at origin with full width (border lines)
                if (isAtOrigin && isFullWidth) {
                    console.log('Filtering out unwanted border line at origin:', {x: lineX, y: lineY, width: lineWidth, stroke: stroke, name: lineName});
                    return;
                }
                
                // Filter out unwanted separator lines (gray lines at top) that match full page width
                if (points.length >= 4) {
                    const x1 = Math.round(points[0]);
                    const y1 = Math.round(points[1]);
                    const x2 = Math.round(points[2]);
                    const y2 = Math.round(points[3]);
                    const calculatedWidth = Math.abs(x2 - x1);
                    
                    // Filter out full-width gray lines near the top (likely unwanted separators)
                    const isFullWidthLine = calculatedWidth > pageWidth * 0.9;
                    const isNearTop = Math.min(y1, y2) < 50; // Within 50px of top
                    const isGray = stroke === '#e0e0e0' || stroke === '#dee2e6' || stroke === 'gray' || stroke === 'grey' || 
                                  stroke === '#cccccc' || stroke === '#999999' || stroke === '#667eea';
                    
                    if (isFullWidthLine && isNearTop && isGray && lineName !== 'items-table-separator' && lineName !== 'expenses-table-separator') {
                        console.log('Filtering out unwanted gray separator line:', {width: calculatedWidth, y: Math.min(y1, y2), stroke: stroke, name: lineName});
                        return;
                    }
                }
                
                if (points.length >= 4) {
                    const x1 = Math.round(points[0]);
                    const y1 = Math.round(points[1]);
                    const x2 = Math.round(points[2]);
                    const y2 = Math.round(points[3]);
                    const width = Math.abs(x2 - x1);
                    const adjustedX = x + Math.min(x1, x2);
                    const adjustedY = y + Math.min(y1, y2);
                    
                    // Final check: filter out lines at origin (0,0) with full width - these are border lines
                    const pageWidth = dimensions.width;
                    const isAtOrigin = Math.abs(adjustedX) < 5 && Math.abs(adjustedY) < 5;
                    const isFullWidth = width > pageWidth * 0.9;
                    const isBorderLine = isAtOrigin && isFullWidth;
                    
                    if (isBorderLine) {
                        console.log('Final filter: Removing border line at origin in code generation:', {adjustedX, adjustedY, width, stroke});
                        return;
                    }
                    
                    // Add to ReportLab template JSON
                    templateJson.elements.push({
                        type: 'line',
                        x: pxToPt(adjustedX),
                        y: pxToPt(adjustedY),
                        width: pxToPt(width),
                        style: {
                            stroke: stroke,
                            strokeWidth: strokeWidth,
                            opacity: opacity
                        }
                    });
                    
                    // Legacy HTML for preview
                    bodyContent += `  <hr class="line-element" style="position:absolute;left:${adjustedX}px;top:${adjustedY}px;width:${width}px;border:none;border-top:${strokeWidth}px solid ${stroke};margin:0;opacity:${opacity}">\n`;
                }
            } else if (child.className === 'Group' || child.constructor.name === 'Group' || child.children) {
                // It's a Group element (check multiple ways since className might be undefined after JSON restore)
                // Check if this is a table by looking at the group's name (use getAttr too - Konva may store name there after load)
                const nameAttr = (child.getAttr && child.getAttr('name')) || (child.attrs && child.attrs.name) || '';
                let isItemsTable = nameAttr === 'items-table' || nameAttr === 'quote-items-table';
                let isExpensesTable = nameAttr === 'expenses-table';
                
                // CRITICAL FIX: Check if this is a decorative-image group (handle name variations)
                const groupName = nameAttr;
                const isDecorativeImage = groupName && groupName.includes('decorative-image');
                
                // Fallback: Check if group has table-like structure but missing name (Issue #504 - i18n-aware)
                if (!isItemsTable && !isExpensesTable && !isDecorativeImage && child.children && child.children.length >= 3) {
                    const inferred = inferQuoteTableNameFromGroup(child, null);
                    if (inferred === 'quote-items-table') {
                        isItemsTable = true;
                        console.log('⚠ Quote items table detected by structure (missing name) - using inference');
                    }
                }
                
                // Process decorative-image groups first (before tables)
                if (isDecorativeImage) {
                    // Decorative image element
                    // Get imageUrl from attrs - try multiple methods to ensure we get it
                    let imageUrl = '';
                    try {
                        imageUrl = child.getAttr('imageUrl') || '';
                    } catch(e) {
                        console.warn('Could not get imageUrl via getAttr:', e);
                    }
                    if (!imageUrl) {
                        imageUrl = attrs.imageUrl || '';
                    }
                    // Also check if stored in the node's attrs directly
                    if (!imageUrl && child.attrs) {
                        imageUrl = child.attrs.imageUrl || '';
                    }
                    
                    console.log('Generating code for decorative image, URL:', imageUrl, 'attrs:', attrs);
                    
                    // Find the actual image in the group if it exists
                    const children = child.getChildren ? child.getChildren() : (child.children || []);
                    const imageElement = children.find(c => c.className === 'Image');
                    
                    // Issue #537: Use group's visual bounding box so resized (scaled) dimensions persist
                    let actualX = Math.round(attrs.x || 0);
                    let actualY = Math.round(attrs.y || 0);
                    let actualWidth = 100;
                    let actualHeight = 100;
                    const groupBox = child.getClientRect ? child.getClientRect() : null;
                    if (groupBox && groupBox.width > 0 && groupBox.height > 0) {
                        actualX = Math.round(groupBox.x);
                        actualY = Math.round(groupBox.y);
                        actualWidth = Math.round(groupBox.width);
                        actualHeight = Math.round(groupBox.height);
                    } else if (imageElement) {
                        const sx = (imageElement.scaleX && imageElement.scaleX()) || 1;
                        const sy = (imageElement.scaleY && imageElement.scaleY()) || 1;
                        actualWidth = Math.round((imageElement.width() || imageElement.attrs.width || 100) * sx);
                        actualHeight = Math.round((imageElement.height() || imageElement.attrs.height || 100) * sy);
                    }
                    
                    console.log('Decorative image dimensions:', actualWidth, 'x', actualHeight, 'URL:', imageUrl);
                    
                    // Issue #432: Only add to template JSON when source is non-empty
                    if (imageUrl && imageUrl.trim() !== '') {
                        templateJson.elements.push({
                            type: 'image',
                            x: pxToPt(actualX),
                            y: pxToPt(actualY),
                            width: pxToPt(actualWidth),
                            height: pxToPt(actualHeight),
                            source: imageUrl,
                            opacity: opacity,
                            decorative: true
                        });
                    }
                    
                    // Legacy HTML for preview
                    if (imageUrl && imageUrl.trim() !== '') {
                        bodyContent += `  <img src="${imageUrl}" style="position:absolute;left:${actualX}px;top:${actualY}px;width:${actualWidth}px;height:${actualHeight}px;opacity:${opacity}" alt="Decorative image" class="image-element">\n`;
                    } else {
                        bodyContent += `  <div style="position:absolute;left:${actualX}px;top:${actualY}px;width:${actualWidth}px;height:${actualHeight}px;background:#f0f0f0;border:2px dashed #999;opacity:${opacity};display:flex;align-items:center;justify-content:center;color:#666;font-size:12px;">Decorative Image</div>\n`;
                    }
                    return;
                }
                
                if (isItemsTable) {
                    // Extract actual header text from the table group's first Text child
                    const children = child.getChildren ? child.getChildren() : (child.children || []);
                    const textElements = children.filter(c => c.className === 'Text');
                    const headerText = textElements[0] ? (textElements[0].attrs.text || '') : '';
                    
                    // Parse header text
                    let headerParts = ['Description', 'Qty', 'Unit Price', 'Total'];
                    if (headerText && headerText.includes('|')) {
                        headerParts = headerText.split('|').map(part => part.trim()).filter(part => part.length > 0);
                        while (headerParts.length < 4) {
                            headerParts.push(['Description', 'Qty', 'Unit Price', 'Total'][headerParts.length]);
                        }
                    }
                    
                    // Add to ReportLab template JSON - quote items table
                    {% raw %}
                    const itemsData = '{{ quote.items }}';
                    const itemsRowTemplate = {
                        description: '{{ item.description }}',
                        quantity: '{{ item.quantity }}',
                        unit_price: '{{ format_money(item.unit_price) }}',
                        total_amount: '{{ format_money(item.total_amount) }}'
                    };
                    {% endraw %}
                    templateJson.elements.push({
                        type: 'table',
                        x: pxToPt(x),
                        y: pxToPt(y),
                        width: pxToPt(515),
                        opacity: opacity,
                        columns: [
                            {width: 250, header: headerParts[0] || 'Description', field: 'description', align: 'left'},
                            {width: 70, header: headerParts[1] || 'Qty', field: 'quantity', align: 'center'},
                            {width: 110, header: headerParts[2] || 'Unit Price', field: 'unit_price', align: 'right'},
                            {width: 110, header: headerParts[3] || 'Total', field: 'total_amount', align: 'right'}
                        ],
                        data: itemsData,
                        row_template: itemsRowTemplate
                    });
                    
                    // Legacy HTML for preview
                    bodyContent += `  <!-- Items Table Start -->\n`;
                    bodyContent += `  <div style="position:absolute;left:${x}px;top:${y}px;opacity:${opacity};width:515px;">\n`;
                    bodyContent += `    <table style="width:100%;border-collapse:collapse;font-size:11px;background:white;">\n`;
                    bodyContent += `      <thead>\n`;
                    bodyContent += `        <tr style="background-color:#f8f9fa;border-bottom:2px solid #333;">\n`;
                    bodyContent += `          <th style="text-align:left;padding:10px;font-weight:bold;font-size:12px;">${(headerParts[0] || 'Description').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `          <th style="text-align:center;padding:10px;font-weight:bold;font-size:12px;width:70px;">${(headerParts[1] || 'Qty').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `          <th style="text-align:right;padding:10px;font-weight:bold;font-size:12px;width:110px;">${(headerParts[2] || 'Unit Price').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `          <th style="text-align:right;padding:10px;font-weight:bold;font-size:12px;width:110px;">${(headerParts[3] || 'Total').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `        </tr>\n`;
                    bodyContent += `      </thead>\n`;
                    bodyContent += `      <tbody>\n`;
                    {% raw %}
                    bodyContent += `        {% if quote.items %}\n`;
                    bodyContent += `        {% for item in quote.items %}\n`;
                    bodyContent += `        <tr style="border-bottom:1px solid #ddd;">\n`;
                    bodyContent += `          <td style="padding:10px;vertical-align:top;">{{ item.description }}</td>\n`;
                    bodyContent += `          <td style="padding:10px;text-align:center;vertical-align:top;">{{ item.quantity }}</td>\n`;
                    bodyContent += `          <td style="padding:10px;text-align:right;vertical-align:top;">{{ format_money(item.unit_price) }}</td>\n`;
                    bodyContent += `          <td style="padding:10px;text-align:right;vertical-align:top;font-weight:bold;">{{ format_money(item.total_amount) }}</td>\n`;
                    bodyContent += `        </tr>\n`;
                    bodyContent += `        {% endfor %}\n`;
                    bodyContent += `        {% else %}\n`;
                    bodyContent += `        <tr>\n`;
                    bodyContent += `          <td colspan="4" style="padding:10px;text-align:center;color:#999;">No items</td>\n`;
                    bodyContent += `        </tr>\n`;
                    bodyContent += `        {% endif %}\n`;
                    {% endraw %}
                    bodyContent += `      </tbody>\n`;
                    bodyContent += `    </table>\n`;
                    bodyContent += `  </div>\n`;
                    bodyContent += `  <!-- Items Table End -->\n`;
                } else if (isExpensesTable) {
                    // Extract actual header text from the table group's first Text child
                    const children = child.getChildren ? child.getChildren() : (child.children || []);
                    const textElements = children.filter(c => c.className === 'Text');
                    const headerText = textElements[0] ? (textElements[0].attrs.text || '') : '';
                    
                    // Parse header text (format: "Expense | Date | Category | Amount" or localized)
                    // Default to English if header text is empty or doesn't contain |
                    let headerParts = ['Expense', 'Date', 'Category', 'Amount'];
                    if (headerText && headerText.includes('|')) {
                        headerParts = headerText.split('|').map(part => part.trim()).filter(part => part.length > 0);
                        // Ensure we have at least 4 parts, pad with defaults if needed
                        while (headerParts.length < 4) {
                            headerParts.push(['Expense', 'Date', 'Category', 'Amount'][headerParts.length]);
                        }
                    }
                    
                    // Add to ReportLab template JSON - expenses table (for quotes, this might not be used)
                    {% raw %}
                    const expensesDataQuote = '{{ quote.expenses }}';
                    const expensesRowTemplateQuote = {
                        title: '{{ expense.title }}',
                        expense_date: '{{ expense.expense_date }}',
                        category: '{{ expense.category }}',
                        total_amount: '{{ format_money(expense.total_amount) }}'
                    };
                    {% endraw %}
                    templateJson.elements.push({
                        type: 'table',
                        x: pxToPt(x),
                        y: pxToPt(y),
                        width: pxToPt(515),
                        opacity: opacity,
                        columns: [
                            {width: 200, header: headerParts[0] || 'Expense', field: 'title', align: 'left'},
                            {width: 100, header: headerParts[1] || 'Date', field: 'expense_date', align: 'center'},
                            {width: 105, header: headerParts[2] || 'Category', field: 'category', align: 'left'},
                            {width: 110, header: headerParts[3] || 'Amount', field: 'total_amount', align: 'right'}
                        ],
                        data: expensesDataQuote,
                        row_template: expensesRowTemplateQuote,
                        style: {
                            headerBackground: '#fff3cd',
                            headerTextColor: '#856404',
                            rowBackground: '#fffbf0',
                            rowTextColor: '#856404'
                        }
                    });
                    
                    // Legacy HTML for preview
                    bodyContent += `  <!-- Expenses Table Start -->\n`;
                    bodyContent += `  <div style="position:absolute;left:${x}px;top:${y}px;opacity:${opacity};width:515px;">\n`;
                    bodyContent += `    <table style="width:100%;border-collapse:collapse;font-size:11px;background:#fffbf0;">\n`;
                    bodyContent += `      <thead>\n`;
                    bodyContent += `        <tr style="background-color:#fff3cd;border-bottom:2px solid #856404;">\n`;
                    bodyContent += `          <th style="text-align:left;padding:10px;font-weight:bold;font-size:12px;color:#856404;">${(headerParts[0] || 'Expense').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `          <th style="text-align:center;padding:10px;font-weight:bold;font-size:12px;width:100px;color:#856404;">${(headerParts[1] || 'Date').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `          <th style="text-align:left;padding:10px;font-weight:bold;font-size:12px;width:110px;color:#856404;">${(headerParts[2] || 'Category').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `          <th style="text-align:right;padding:10px;font-weight:bold;font-size:12px;width:110px;color:#856404;">${(headerParts[3] || 'Amount').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</th>\n`;
                    bodyContent += `        </tr>\n`;
                    bodyContent += `      </thead>\n`;
                    bodyContent += `      <tbody>\n`;
                    {% raw %}
                    bodyContent += `        {% if quote.expenses %}\n`;
                    bodyContent += `        {% for expense in quote.expenses %}\n`;
                    bodyContent += `        <tr style="border-bottom:1px solid #f0e5c1;">\n`;
                    bodyContent += `          <td style="padding:10px;vertical-align:top;color:#856404;">{{ expense.title }}</td>\n`;
                    bodyContent += `          <td style="padding:10px;text-align:center;vertical-align:top;color:#856404;">{{ expense.expense_date }}</td>\n`;
                    bodyContent += `          <td style="padding:10px;vertical-align:top;color:#856404;">{{ expense.category }}</td>\n`;
                    bodyContent += `          <td style="padding:10px;text-align:right;vertical-align:top;font-weight:bold;color:#856404;">{{ format_money(expense.total_amount) }}</td>\n`;
                    bodyContent += `        </tr>\n`;
                    bodyContent += `        {% endfor %}\n`;
                    bodyContent += `        {% else %}\n`;
                    bodyContent += `        <tr>\n`;
                    bodyContent += `          <td colspan="4" style="padding:10px;text-align:center;color:#999;">No expenses</td>\n`;
                    bodyContent += `        </tr>\n`;
                    bodyContent += `        {% endif %}\n`;
                    {% endraw %}
                    bodyContent += `      </tbody>\n`;
                    bodyContent += `    </table>\n`;
                    bodyContent += `  </div>\n`;
                    bodyContent += `  <!-- Expenses Table End -->\n`;
                } else if (attrs.name && attrs.name.includes('decorative-image')) {
                    // Decorative image element (handles name variations like "decorative-image element-overlap")
                    // This should not be reached if the earlier check worked, but keeping as fallback
                    // Get imageUrl from attrs - try multiple methods to ensure we get it
                    let imageUrl = '';
                    try {
                        imageUrl = child.getAttr('imageUrl') || '';
                    } catch(e) {
                        console.warn('Could not get imageUrl via getAttr:', e);
                    }
                    if (!imageUrl) {
                        imageUrl = attrs.imageUrl || '';
                    }
                    // Also check if stored in the node's attrs directly
                    if (!imageUrl && child.attrs) {
                        imageUrl = child.attrs.imageUrl || '';
                    }
                    
                    console.log('Generating code for decorative image (fallback), URL:', imageUrl, 'attrs:', attrs);
                    
                    // Find the actual image in the group if it exists
                    const children = child.getChildren ? child.getChildren() : (child.children || []);
                    const imageElement = children.find(c => c.className === 'Image');
                    
                    // Issue #537: Use group's visual bounding box so resized (scaled) dimensions persist
                    let actualX = Math.round(attrs.x || 0);
                    let actualY = Math.round(attrs.y || 0);
                    let actualWidth = 100;
                    let actualHeight = 100;
                    const groupBox = child.getClientRect ? child.getClientRect() : null;
                    if (groupBox && groupBox.width > 0 && groupBox.height > 0) {
                        actualX = Math.round(groupBox.x);
                        actualY = Math.round(groupBox.y);
                        actualWidth = Math.round(groupBox.width);
                        actualHeight = Math.round(groupBox.height);
                    } else if (imageElement) {
                        const sx = (imageElement.scaleX && imageElement.scaleX()) || 1;
                        const sy = (imageElement.scaleY && imageElement.scaleY()) || 1;
                        actualWidth = Math.round((imageElement.width() || imageElement.attrs.width || 100) * sx);
                        actualHeight = Math.round((imageElement.height() || imageElement.attrs.height || 100) * sy);
                    }
                    
                    console.log('Decorative image dimensions (fallback):', actualWidth, 'x', actualHeight, 'URL:', imageUrl);
                    
                    // Issue #432: Only add to template JSON when source is non-empty
                    if (imageUrl && imageUrl.trim() !== '') {
                        templateJson.elements.push({
                            type: 'image',
                            x: pxToPt(actualX),
                            y: pxToPt(actualY),
                            width: pxToPt(actualWidth),
                            height: pxToPt(actualHeight),
                            source: imageUrl,
                            opacity: opacity,
                            decorative: true
                        });
                    }
                    
                    // Legacy HTML for preview
                    if (imageUrl && imageUrl.trim() !== '') {
                        bodyContent += `  <img src="${imageUrl}" style="position:absolute;left:${actualX}px;top:${actualY}px;width:${actualWidth}px;height:${actualHeight}px;opacity:${opacity}" alt="Decorative image" class="image-element">\n`;
                    } else {
                        bodyContent += `  <div style="position:absolute;left:${actualX}px;top:${actualY}px;width:${actualWidth}px;height:${actualHeight}px;background:#f0f0f0;border:2px dashed #999;opacity:${opacity};display:flex;align-items:center;justify-content:center;color:#666;font-size:12px;">Decorative Image</div>\n`;
                    }
                } else {
                        // Regular group (not a table)
                        bodyContent += `  <div style="position:absolute;left:${x}px;top:${y}px;opacity:${opacity}">\n`;
                        child.children.forEach(c => {
                            if (c.className === 'Text') {
                                const text = (c.attrs.text || '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
                                // Preserve text alignment for text in groups
                                const textAlign = c.attrs.align || 'left';
                                bodyContent += `    <div style="font-size:${c.attrs.fontSize || 12}px;font-weight:${c.attrs.fontStyle === 'bold' ? 'bold' : 'normal'};text-align:${textAlign}">${text}</div>\n`;
                            } else if (c.className === 'Line') {
                                bodyContent += `    <hr style="border-top:${c.attrs.strokeWidth || 1}px solid ${c.attrs.stroke || 'black'};margin:${c.attrs.y || 0}px 0">\n`;
                            }
                        });
                        bodyContent += `  </div>\n`;
                    }
                }
            });
        
            // Legacy HTML/CSS generation for preview (backward compatibility)
            const widthPx = dimensions.width;
            const heightPx = dimensions.height;
            
            const generatedHtml = `<div class="quote-wrapper">
${bodyContent}</div>`;
            
            const generatedCss = `@page {
    size: ${currentSize};
    margin: 0;
}
html, body {
    margin: 0;
    padding: 0;
    width: ${widthPx}px;
    height: ${heightPx}px;
    font-family: Arial, sans-serif;
    overflow: hidden;
}
.invoice-wrapper, .quote-wrapper {
    position: relative;
    width: ${widthPx}px !important;
    height: ${heightPx}px !important;
    max-width: ${widthPx}px !important;
    max-height: ${heightPx}px !important;
    background: white;
    padding: 0 !important;
    box-sizing: border-box !important;
    margin: 0 !important;
    overflow: hidden !important;
    clip-path: inset(0) !important;
    contain: layout style paint;
    isolation: isolate;
}
.element, .text-element {
    white-space: pre-wrap;
    box-sizing: border-box;
    max-width: 100%;
}
.rectangle-element, .circle-element {
    box-sizing: border-box;
    max-width: 100%;
    max-height: 100%;
}
.line-element {
    padding: 0;
    box-sizing: border-box;
    max-width: 100%;
}
table {
    width: 100%;
    border-collapse: collapse;
    margin: 10px 0;
}
table th {
    background-color: #f8f9fa;
    font-weight: bold;
    text-align: left;
    padding: 10px;
    border-bottom: 2px solid #333;
}
table td {
    padding: 10px;
    border-bottom: 1px solid #ddd;
}
table tr:last-child td {
    border-bottom: 2px solid #333;
}`;
            
            // Return both JSON (new format) and HTML/CSS (legacy for preview)
            return { 
                html: generatedHtml,  // Legacy HTML for preview
                css: generatedCss,   // Legacy CSS for preview
                json: JSON.stringify(templateJson, null, 2)  // ReportLab template JSON
            };
        } catch (error) {
            console.error('Error in generateCode():', error);
            console.error('Stack trace:', error.stack);
            // Return minimal valid structure on error
            const currentSize = (pageSizeSelector && pageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
            const dimensions = PAGE_SIZE_DIMENSIONS[currentSize] || PAGE_SIZE_DIMENSIONS['A4'];
            const widthPx = dimensions.width;
            const heightPx = dimensions.height;
            return {
                html: `<div class="quote-wrapper"><p style="color: red; padding: 20px;">Error generating template: ${error.message}</p></div>`,
                css: `@page { size: ${currentSize}; margin: 0; } html, body { margin: 0; padding: 0; width: ${widthPx}px; height: ${heightPx}px; } .quote-wrapper { width: ${widthPx}px; height: ${heightPx}px; background: white; padding: 0; }`,
                json: JSON.stringify({ page: { size: currentSize, margin: { top: 20, right: 20, bottom: 20, left: 20 } }, elements: [], styles: {} }, null, 2)
            };
        }
    }
    
    // View code
    document.getElementById('btn-code').addEventListener('click', function() {
        const { html, css } = generateCode();
        
        // Log for debugging
        console.log('=== GENERATED HTML ===');
        console.log(html);
        console.log('=== END HTML ===');
        
        // Check if items table is present
        const hasItemsTable = html.includes('<!-- Items Table Start -->');
        const hasForLoop = html.includes('{' + '% for item in quote.items');
        console.log('Has Items Table marker:', hasItemsTable);
        console.log('Has Jinja2 loop:', hasForLoop);
        
        document.getElementById('code-html').value = html;
        document.getElementById('code-css').value = css;
        document.getElementById('code-modal').classList.remove('hidden');
    });
    
    document.getElementById('close-modal').addEventListener('click', function() {
        document.getElementById('code-modal').classList.add('hidden');
    });
    
    // Page size selector handler
    // Reuse the pageSizeSelector declared at the top level
    if (pageSizeSelector) {
        // Set the current page size in the dropdown
        if (CURRENT_PAGE_SIZE) {
            pageSizeSelector.value = CURRENT_PAGE_SIZE;
        }
        
        pageSizeSelector.addEventListener('change', async function() {
            const newSize = this.value;
            const confirmed = await showConfirm(
                'Switching page size will reload the template. Any unsaved changes will be lost. Continue?',
                {
                    title: 'Switch Page Size',
                    confirmText: 'Continue',
                    cancelText: 'Cancel',
                    variant: 'warning'
                }
            );
            if (confirmed) {
                window.location.href = '{{ url_for("admin.quote_pdf_layout") }}?size=' + encodeURIComponent(newSize);
            } else {
                // Reset to current size
                this.value = CURRENT_PAGE_SIZE || 'A4';
            }
        });
    }
    
    // Cleanup function to remove unwanted elements before saving
    function cleanupUnwantedElements() {
        if (!layer) return;
        
        const pageWidth = dimensions.width;
        const pageHeight = dimensions.height;
        let removedCount = 0;
        
        // Get all children (create a copy since we'll be modifying the array)
        const children = layer.children.slice();
        
        children.forEach((child) => {
            if (!child || child === background || child.className === 'Transformer') return;
            // Never remove Groups or named table/decorative nodes (cleanup only touches Rects and Lines)
            const isGroup = child.className === 'Group' || (child.constructor && child.constructor.name === 'Group');
            const name = (child.getAttr && child.getAttr('name')) || (child.attrs && child.attrs.name) || '';
            const isTableOrDecorative = name === 'quote-items-table' || (typeof name === 'string' && name.includes('decorative-image'));
            if (isGroup || isTableOrDecorative) return;
            
            // Remove invisible elements (opacity 0 or not visible)
            if (child.attrs && (child.attrs.opacity === 0 || child.attrs.visible === false)) {
                console.log('Removing invisible element:', child.className, child.attrs.name || 'unnamed');
                child.destroy();
                removedCount++;
                return;
            }
            
            // Remove zero-sized elements (never remove Groups - they have no width/height in attrs; would delete quote-items-table)
            if (child.attrs) {
                const isGroup = child.className === 'Group' || (child.constructor && child.constructor.name === 'Group');
                if (!isGroup) {
                    const width = child.attrs.width || 0;
                    const height = child.attrs.height || 0;
                    if (width === 0 && height === 0 && child.className !== 'Line' && child.className !== 'Circle') {
                        console.log('Removing zero-sized element:', child.className, child.attrs.name || 'unnamed');
                        child.destroy();
                        removedCount++;
                        return;
                    }
                }
            }
            
            // Remove unwanted rectangles
            if (child.className === 'Rect') {
                const rectName = child.attrs.name || '';
                const rectX = child.attrs.x || 0;
                const rectY = child.attrs.y || 0;
                const rectWidth = child.attrs.width || 0;
                const rectHeight = child.attrs.height || 0;
                const rectFill = child.attrs.fill || '';
                const rectStroke = child.attrs.stroke || '';
                
                // Remove if name is "background" or "page-border" (duplicates)
                if (rectName === 'background' || rectName === 'page-border') {
                    console.log('Removing duplicate rectangle:', rectName);
                    child.destroy();
                    removedCount++;
                    return;
                }
                
                // Remove full-page rectangles at 0,0 with white/transparent fill and black stroke
                const isFullPage = Math.abs(rectX) < 5 && Math.abs(rectY) < 5 && 
                                  Math.abs(rectWidth - pageWidth) < 5 && 
                                  Math.abs(rectHeight - pageHeight) < 5;
                
                if (isFullPage && (rectFill === 'white' || rectFill === '#ffffff' || rectFill === 'transparent' || rectFill === '') && 
                    (rectStroke === 'black' || rectStroke === '#000000')) {
                    console.log('Removing unwanted full-page rectangle border before save');
                    child.destroy();
                    removedCount++;
                    return;
                }
            }
            
            // Remove unwanted lines (border lines and separator lines)
            if (child.className === 'Line') {
                const lineName = child.attrs.name || '';
                const lineX = child.attrs.x || 0;
                const lineY = child.attrs.y || 0;
                const lineWidth = child.attrs.width || 0;
                const points = child.attrs.points || [];
                
                // Skip grid lines
                if (lineName === 'grid-line') return;
                
                // Remove border lines at origin (0,0) with full page width
                const isAtOrigin = Math.abs(lineX) < 5 && Math.abs(lineY) < 5;
                const isFullWidthBorder = lineWidth > pageWidth * 0.9 || 
                                        (points.length >= 4 && Math.abs(points[2] - points[0]) > pageWidth * 0.9);
                
                if (isAtOrigin && isFullWidthBorder) {
                    console.log('Removing unwanted border line at origin before save');
                    child.destroy();
                    removedCount++;
                    return;
                }
                
                // Remove unwanted gray separator lines
                if (points.length >= 4) {
                    const x1 = points[0];
                    const y1 = points[1];
                    const x2 = points[2];
                    const y2 = points[3];
                    const calculatedWidth = Math.abs(x2 - x1);
                    const stroke = child.attrs.stroke || '';
                    
                    // Remove full-width gray/blue lines near top or anywhere
                    const isFullWidth = calculatedWidth > pageWidth * 0.9;
                    const isNearTop = Math.min(y1, y2) < 50;
                    const isGrayOrBlue = stroke === '#e0e0e0' || stroke === '#dee2e6' || stroke === 'gray' || stroke === 'grey' || 
                                        stroke === '#cccccc' || stroke === '#999999' || stroke === '#667eea';
                    
                    if (isFullWidth && (isNearTop || isAtOrigin) && isGrayOrBlue && lineName !== 'items-table-separator' && lineName !== 'expenses-table-separator') {
                        console.log('Removing unwanted separator line before save');
                        child.destroy();
                        removedCount++;
                        return;
                    }
                }
            }
        });
        
        if (removedCount > 0) {
            console.log(`Cleaned up ${removedCount} unwanted elements before saving`);
            layer.draw();
        }
    }
    
    // Save
    document.getElementById('btn-save').addEventListener('click', function() {
        // Issue #504: Ensure quote table groups have correct name BEFORE cleanup (so names are set while layer is intact)
        if (typeof ensureQuoteTableGroupNames === 'function' && layer) {
            ensureQuoteTableGroupNames(layer, background);
            layer.draw();
        }
        // Clean up unwanted elements before generating code
        cleanupUnwantedElements();
        // Issue #432: Sync imageUrl onto decorative-image groups BEFORE generateCode() so template_json has correct source
        layer.find('[name="decorative-image"]').forEach((decorativeImageGroup, idx) => {
            try {
                let imageUrl = decorativeImageGroup.getAttr('imageUrl');
                if (!imageUrl) imageUrl = decorativeImageGroup.attrs.imageUrl;
                if (!imageUrl) {
                    const imageNode = decorativeImageGroup.findOne('Image');
                    if (imageNode) {
                        const imgAttrs = imageNode.attrs || {};
                        imageUrl = imgAttrs.src || imgAttrs.url || imgAttrs.imageUrl || '';
                    }
                }
                if (imageUrl) {
                    decorativeImageGroup.setAttr('imageUrl', imageUrl);
                    decorativeImageGroup.attrs.imageUrl = imageUrl;
                }
            } catch(e) {
                console.warn('Could not get/set imageUrl for decorative image:', e);
            }
        });
        let allDecorativeGroups = layer.find('[name="decorative-image"]');
        layer.find('Group').forEach(group => {
            const name = group.name() || group.getAttr('name') || '';
            if (name.includes('decorative-image') && !allDecorativeGroups.includes(group)) {
                allDecorativeGroups = allDecorativeGroups.concat([group]);
            }
        });
        allDecorativeGroups.forEach((decorativeImageGroup, idx) => {
            try {
                const currentName = decorativeImageGroup.name() || decorativeImageGroup.getAttr('name') || '';
                if (!currentName.includes('decorative-image')) {
                    decorativeImageGroup.setAttr('name', 'decorative-image');
                    decorativeImageGroup.name('decorative-image');
                    if (decorativeImageGroup.attrs) decorativeImageGroup.attrs.name = 'decorative-image';
                }
                const imageUrl = decorativeImageGroup.getAttr('imageUrl') || decorativeImageGroup.attrs.imageUrl;
                if (imageUrl) {
                    decorativeImageGroup.setAttr('imageUrl', imageUrl);
                    decorativeImageGroup.attrs.imageUrl = imageUrl;
                }
            } catch(e) {}
        });
        const decorativeImageUrlMap = new Map();
        let decorativeImageGroups = layer.find('[name="decorative-image"]');
        layer.find('Group').forEach(group => {
            const name = group.name() || group.getAttr('name') || '';
            if (name.includes('decorative-image') && !decorativeImageGroups.includes(group)) {
                decorativeImageGroups = decorativeImageGroups.concat([group]);
            }
        });
        decorativeImageGroups.forEach((decorativeImageGroup, index) => {
            const currentPrimaryName = decorativeImageGroup.name();
            if (currentPrimaryName !== 'decorative-image') {
                decorativeImageGroup.name('decorative-image');
                decorativeImageGroup.setAttr('name', 'decorative-image');
                if (decorativeImageGroup.attrs) decorativeImageGroup.attrs.name = 'decorative-image';
            }
            let imageUrl = decorativeImageGroup.getAttr('imageUrl') || decorativeImageGroup.attrs.imageUrl || '';
            if (!imageUrl) {
                const imageNode = decorativeImageGroup.findOne('Image');
                if (imageNode) {
                    const imgAttrs = imageNode.attrs || {};
                    imageUrl = imgAttrs.src || imgAttrs.url || imgAttrs.imageUrl || '';
                }
            }
            const x = decorativeImageGroup.x() || 0;
            const y = decorativeImageGroup.y() || 0;
            decorativeImageUrlMap.set(`${x}_${y}`, imageUrl || '');
            decorativeImageUrlMap.set(`${x}_${y}_${index}`, imageUrl || '');
            decorativeImageUrlMap.set(`index_${index}`, imageUrl || '');
        });

        const { html, css, json } = generateCode();

        // Log what we're saving for debugging
        console.log('=== SAVING TO DATABASE ===');
        console.log('HTML length:', html.length);
        console.log('Has Items Table:', html.includes('<!-- Items Table Start -->'));
        console.log('Has Jinja2 loop:', html.includes('{' + '% for item in quote.items'));
        console.log('Number of elements:', layer.children.length);
        console.log('Page size:', CURRENT_PAGE_SIZE);

        // Serialize the stage
        const stageJson = stage.toJSON();
        
        // Explicitly inject name and imageUrl into serialized JSON (Issue #432). Use index for decorative-image;
        // use position-based matching for quote-items-table so names are never applied to the wrong node (Issue #504).
        const layerJson = stageJson.children && stageJson.children[0];
        if (layer && layerJson && layerJson.children && layer.children) {
            for (let i = 0; i < layer.children.length; i++) {
                const layerChild = layer.children[i];
                const jsonChild = layerJson.children[i];
                if (!jsonChild) continue;
                const name = layerChild.getAttr ? layerChild.getAttr('name') : (layerChild.attrs && layerChild.attrs.name);
                if (name && (name === 'decorative-image' || (typeof name === 'string' && name.includes('decorative-image')))) {
                    if (!jsonChild.attrs) jsonChild.attrs = {};
                    jsonChild.attrs.name = 'decorative-image';
                    jsonChild.attrs.imageUrl = (layerChild.getAttr ? layerChild.getAttr('imageUrl') : (layerChild.attrs && layerChild.attrs.imageUrl)) || '';
                }
            }
            const tableGroupsFromLayer = [];
            layer.children.forEach(function(child) {
                const isGroup = child && (child.className === 'Group' || (child.constructor && child.constructor.name === 'Group'));
                if (!child || !isGroup) return;
                const n = child.getAttr ? child.getAttr('name') : (child.attrs && child.attrs.name);
                if (n === 'quote-items-table') {
                    const x = Math.round(child.x() || child.attrs.x || 0);
                    const y = Math.round(child.y() || child.attrs.y || 0);
                    tableGroupsFromLayer.push({ name: n, x: x, y: y });
                }
            });
            const tolerance = 5;
            layerJson.children.forEach(function(jsonChild) {
                if (!jsonChild || jsonChild.className !== 'Group') return;
                const jx = Math.round((jsonChild.attrs && jsonChild.attrs.x) || 0);
                const jy = Math.round((jsonChild.attrs && jsonChild.attrs.y) || 0);
                for (let t = 0; t < tableGroupsFromLayer.length; t++) {
                    const tg = tableGroupsFromLayer[t];
                    if (tg && Math.abs(tg.x - jx) <= tolerance && Math.abs(tg.y - jy) <= tolerance) {
                        if (!jsonChild.attrs) jsonChild.attrs = {};
                        jsonChild.attrs.name = tg.name;
                        tableGroupsFromLayer[t] = null;
                        break;
                    }
                }
            });
        }
        
        let decorativeImageIndex = 0;
        function ensureImageUrlInJson(node, parentKey = '') {
            const nodeName = node.attrs && node.attrs.name ? node.attrs.name : '';
            if (nodeName && nodeName.includes('decorative-image')) {
                const x = node.attrs.x || 0;
                const y = node.attrs.y || 0;
                let imageUrl = decorativeImageUrlMap.get(`${x}_${y}`) ||
                              decorativeImageUrlMap.get(`${x}_${y}_${decorativeImageIndex}`) ||
                              decorativeImageUrlMap.get(`index_${decorativeImageIndex}`) || '';
                if (!node.attrs) node.attrs = {};
                node.attrs.name = 'decorative-image';
                node.attrs.imageUrl = (typeof imageUrl === 'string' ? imageUrl : '');
                decorativeImageIndex++;
            }
            if (node.children) {
                node.children.forEach((child, idx) => ensureImageUrlInJson(child, `${parentKey}.children[${idx}]`));
            }
        }
        
        if (stageJson.children) {
            stageJson.children.forEach((child, idx) => ensureImageUrlInJson(child, `children[${idx}]`));
        }
        
        document.getElementById('save-html').value = html;
        document.getElementById('save-css').value = css;
        document.getElementById('save-design-json').value = JSON.stringify(stageJson);
        if (!json || !json.trim()) {
            console.error('No JSON generated from template!');
            alert('Error: Could not generate template JSON. Please try again.');
            return;
        }
        try {
            JSON.parse(json);
        } catch (e) {
            console.error('Invalid JSON generated:', e);
            alert('Error: Generated template JSON is invalid. Please try again.');
            return;
        }
        document.getElementById('save-template-json').value = json;
        // Use page size from selector if available, otherwise use CURRENT_PAGE_SIZE
        const pageSizeForSave = (pageSizeSelector && pageSizeSelector.value) || CURRENT_PAGE_SIZE || 'A4';
        document.getElementById('save-page-size').value = pageSizeForSave;
        // Save date format
        const dateFormatInput = document.getElementById('template-date-format');
        if (dateFormatInput) {
            document.getElementById('save-date-format').value = dateFormatInput.value;
        }
        document.getElementById('form-save').submit();
    });
    
    // Keyboard shortcuts
    document.addEventListener('keydown', function(e) {
        // Check if user is editing text in an input field or textarea
        const activeElement = document.activeElement;
        const isEditingText = activeElement && (
            activeElement.tagName === 'INPUT' ||
            activeElement.tagName === 'TEXTAREA' ||
            activeElement.isContentEditable
        );

        const modQ = e.ctrlKey || e.metaKey;
        if (modQ && !isEditingText) {
            const kq = (e.key || '').toLowerCase();
            if (kq === 'z' && !e.shiftKey) {
                e.preventDefault();
                undoQuoteEditor();
                return;
            }
            if (kq === 'y' || (kq === 'z' && e.shiftKey)) {
                e.preventDefault();
                redoQuoteEditor();
                return;
            }
        }
        
        // Delete selected element with Delete or Backspace (only if not editing text)
        if ((e.key === 'Delete' || e.key === 'Backspace') && selectedElement && !isEditingText) {
            e.preventDefault();
            selectedElement.destroy();
            layer.find('Transformer').forEach(t => t.destroy());
            selectedElement = null;
            layer.draw();
            document.getElementById('properties-content').innerHTML = '<p class="text-sm text-gray-500 italic">Select an element to edit its properties</p>';
            pushQuoteEditorHistory();
        }
        
        // Copy with Ctrl+C
        if (e.ctrlKey && e.key === 'c' && selectedElement) {
            e.preventDefault();
            window.copiedElement = selectedElement.toJSON();
        }
        
        // Paste with Ctrl+V
        if (e.ctrlKey && e.key === 'v' && window.copiedElement) {
            e.preventDefault();
            const json = window.copiedElement;
            const node = Konva.Node.create(json);
            node.x(node.x() + 20);
            node.y(node.y() + 20);
            layer.add(node);
            setupSelection(node);
            layer.draw();
            pushQuoteEditorHistory();
        }
        
        // Duplicate with Ctrl+D
        if (e.ctrlKey && e.key === 'd' && selectedElement) {
            e.preventDefault();
            const json = selectedElement.toJSON();
            const node = Konva.Node.create(json);
            node.x(node.x() + 20);
            node.y(node.y() + 20);
            layer.add(node);
            setupSelection(node);
            layer.draw();
            pushQuoteEditorHistory();
        }
        
        // Arrow keys to move element (only if not editing text)
        if (selectedElement && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key) && !isEditingText) {
            e.preventDefault();
            const step = e.shiftKey ? 10 : 1;
            
            switch(e.key) {
                case 'ArrowUp':
                    selectedElement.y(selectedElement.y() - step);
                    break;
                case 'ArrowDown':
                    selectedElement.y(selectedElement.y() + step);
                    break;
                case 'ArrowLeft':
                    selectedElement.x(selectedElement.x() - step);
                    break;
                case 'ArrowRight':
                    selectedElement.x(selectedElement.x() + step);
                    break;
            }
            
            layer.draw();
            
            // Update properties panel if visible
            const propX = document.getElementById('prop-x');
            const propY = document.getElementById('prop-y');
            if (propX) propX.value = Math.round(selectedElement.x());
            if (propY) propY.value = Math.round(selectedElement.y());
            scheduleQuoteEditorHistoryPush();
        }
    });
    
    // Click on background to deselect
    background.on('click', function() {
        layer.find('Transformer').forEach(t => t.destroy());
        selectedElement = null;
        layer.draw();
        document.getElementById('properties-content').innerHTML = '<p class="text-sm text-gray-500 italic">Select an element to edit its properties</p>';
    });
    
    // Alignment tools
    window.alignElements = function(direction) {
        if (!selectedElement) return;
        
        const stageWidth = stage.width();
        const stageHeight = stage.height();
        const box = selectedElement.getClientRect();
        
        switch(direction) {
            case 'left':
                selectedElement.x(0);
                break;
            case 'center-h':
                selectedElement.x((stageWidth - box.width) / 2);
                break;
            case 'right':
                selectedElement.x(stageWidth - box.width);
                break;
            case 'top':
                selectedElement.y(0);
                break;
            case 'center-v':
                selectedElement.y((stageHeight - box.height) / 2);
                break;
            case 'bottom':
                selectedElement.y(stageHeight - box.height);
                break;
        }
        
        layer.draw();
        
        // Update properties panel
        const propX = document.getElementById('prop-x');
        const propY = document.getElementById('prop-y');
        if (propX) propX.value = Math.round(selectedElement.x());
        if (propY) propY.value = Math.round(selectedElement.y());
        scheduleQuoteEditorHistoryPush();
    };
    
    // Save design state
    window.saveDesignState = function() {
        return {
            json: stage.toJSON(),
            html: generateCode().html,
            css: generateCode().css
        };
    };
    
    // Load design state
    window.loadDesignState = function(designJson) {
        try {
            const json = JSON.parse(designJson);
            stage = Konva.Node.create(json, 'canvas-container');
            layer = stage.children[0];
            
                // Remove any warning indicator dots that might have been loaded
                layer.find('.warning-indicator').forEach(indicator => {
                    indicator.destroy();
                });
                
                // Re-setup selections for all elements
                layer.children.forEach(child => {
                    if (child !== background && child.className !== 'Transformer') {
                        setupSelection(child);
                        // If it's a table Group, also setup selection on children
                        if (child.className === 'Group' && (child.attrs.name === 'items-table' || child.attrs.name === 'expenses-table' || child.attrs.name === 'quote-items-table')) {
                            child.children.forEach(grandChild => {
                                setupSelection(grandChild);
                            });
                        }
                    }
                });
            
            layer.draw();
        } catch(e) {
            console.error('Failed to load design state:', e);
        }
    };
    
    // Add alignment buttons to canvas toolbar
    const canvasToolbar = document.querySelector('.canvas-toolbar');
    if (canvasToolbar) {
        const alignmentButtons = `
            <button type="button" onclick="alignElements('left')" title="{{ _('Align Left') }}">
                <i class="fas fa-align-left"></i>
            </button>
            <button type="button" onclick="alignElements('center-h')" title="{{ _('Center Horizontally') }}">
                <i class="fas fa-align-center"></i>
            </button>
            <button type="button" onclick="alignElements('right')" title="{{ _('Align Right') }}">
                <i class="fas fa-align-right"></i>
            </button>
            <button type="button" onclick="alignElements('top')" title="{{ _('Align Top') }}">
                <i class="fas fa-arrow-up"></i>
            </button>
            <button type="button" onclick="alignElements('center-v')" title="{{ _('Center Vertically') }}">
                <i class="fas fa-arrows-alt-v"></i>
            </button>
            <button type="button" onclick="alignElements('bottom')" title="{{ _('Align Bottom') }}">
                <i class="fas fa-arrow-down"></i>
            </button>
        `;
        canvasToolbar.insertAdjacentHTML('beforeend', alignmentButtons);
    }
    
    window.__quoteReloadCanvasFromSnapshot = function(jsonString) {
                const savedJson = JSON.parse(jsonString);
                
                // Get current page size dimensions
                const currentSize = CURRENT_PAGE_SIZE || 'A4';
                const dimensions = PAGE_SIZE_DIMENSIONS[currentSize] || PAGE_SIZE_DIMENSIONS['A4'];
                
                // Update saved JSON dimensions to match current page size
                if (savedJson.attrs) {
                    savedJson.attrs.width = dimensions.width;
                    savedJson.attrs.height = dimensions.height;
                }
                
                // Clear current layer
                layer.destroyChildren();
                
                // Recreate stage from saved JSON
                const restoredStage = Konva.Node.create(savedJson, 'canvas-container');
                
                // Ensure stage has correct dimensions
                restoredStage.width(dimensions.width);
                restoredStage.height(dimensions.height);
                
                // Replace current stage
                stage.destroy();
                stage = restoredStage;
                layer = stage.children[0];
                
                // Issue #432: Synchronously restore name and imageUrl from saved JSON onto live nodes
                const layerJson = savedJson.children && savedJson.children[0];
                if (layerJson && layerJson.children && layer.children) {
                    for (let i = 0; i < layer.children.length; i++) {
                        const liveChild = layer.children[i];
                        const jsonChild = layerJson.children[i];
                        const isGroup = liveChild && (liveChild.className === 'Group' || (liveChild.constructor && liveChild.constructor.name === 'Group'));
                        if (!liveChild || !jsonChild || !isGroup) continue;
                        const savedName = (jsonChild.attrs && jsonChild.attrs.name) ? jsonChild.attrs.name : '';
                        if (savedName && savedName.includes('decorative-image')) {
                            const savedImageUrl = (jsonChild.attrs && jsonChild.attrs.imageUrl) ? jsonChild.attrs.imageUrl : '';
                            if (liveChild.setAttr) {
                                liveChild.setAttr('name', 'decorative-image');
                                liveChild.setAttr('imageUrl', savedImageUrl || '');
                            }
                            if (liveChild.attrs) {
                                liveChild.attrs.name = 'decorative-image';
                                liveChild.attrs.imageUrl = savedImageUrl || '';
                            }
                            if (liveChild.name) liveChild.name('decorative-image');
                        } else if (savedName === 'quote-items-table') {
                            if (liveChild.setAttr) liveChild.setAttr('name', 'quote-items-table');
                            if (liveChild.attrs) liveChild.attrs.name = 'quote-items-table';
                            if (liveChild.name) liveChild.name('quote-items-table');
                        }
                    }
                }
                
                // Find or create background by name and resize it
                background = layer.findOne('[name="background"]');
                if (background) {
                    if (background.className === 'Rect') {
                        background.width(dimensions.width);
                        background.height(dimensions.height);
                    }
                } else {
                    // Create background if it doesn't exist
                    background = new Konva.Rect({
                        x: 0,
                        y: 0,
                        width: dimensions.width,
                        height: dimensions.height,
                        fill: 'white',
                        name: 'background'
                    });
                    layer.add(background);
                    background.moveToBottom();
                }
                
                // Update page border to match new dimensions
                const pageBorder = layer.findOne('[name="page-border"]');
                if (pageBorder) {
                    pageBorder.width(dimensions.width);
                    pageBorder.height(dimensions.height);
                } else {
                    // Create page border if it doesn't exist
                    const newPageBorder = new Konva.Rect({
                        x: 0,
                        y: 0,
                        width: dimensions.width,
                        height: dimensions.height,
                        stroke: '#667eea',
                        strokeWidth: 2,
                        fill: 'transparent',
                        name: 'page-border',
                        listening: false,
                        perfectDrawEnabled: false
                    });
                    layer.add(newPageBorder);
                    newPageBorder.moveToBottom();
                }
                
                // Defensive fix (Issue #504): Restore quote-items-table group name from structure when missing (i18n-aware)
                layer.children.forEach((child) => {
                    if (!child || child === background || child.className !== 'Group') return;
                    const currentName = child.getAttr ? child.getAttr('name') : (child.attrs && child.attrs.name);
                    if (currentName === 'quote-items-table') return;
                    const inferredName = inferQuoteTableNameFromGroup(child, background);
                    if (inferredName) {
                        if (child.setAttr) child.setAttr('name', inferredName);
                        if (child.attrs) child.attrs.name = inferredName;
                        if (child.name) child.name(inferredName);
                        console.log('[QUOTE] [LOAD] Restored quote table group name from structure:', inferredName);
                    }
                });
                
                // Clean up unwanted elements that might have been saved
                // Remove unwanted full-page rectangle borders
                layer.children.forEach((child) => {
                    if (child.className === 'Rect' && child !== background && child !== pageBorder) {
                        const rectName = child.attrs.name || '';
                        const rectX = child.attrs.x || 0;
                        const rectY = child.attrs.y || 0;
                        const rectWidth = child.attrs.width || 0;
                        const rectHeight = child.attrs.height || 0;
                        const rectFill = child.attrs.fill || '';
                        const rectStroke = child.attrs.stroke || '';
                        
                        // Remove if name is "background" (duplicate)
                        if (rectName === 'background') {
                            console.log('Removing duplicate background rectangle');
                            child.destroy();
                            return;
                        }
                        
                        // Remove full-page rectangles at 0,0 with white fill and black stroke (unwanted borders)
                        const isFullPage = Math.abs(rectX) < 5 && Math.abs(rectY) < 5 && 
                                          Math.abs(rectWidth - dimensions.width) < 5 && 
                                          Math.abs(rectHeight - dimensions.height) < 5;
                        
                        if (isFullPage && (rectFill === 'white' || rectFill === '#ffffff' || rectFill === 'transparent') && 
                            (rectStroke === 'black' || rectStroke === '#000000')) {
                            console.log('Removing unwanted full-page rectangle border on load');
                            child.destroy();
                            return;
                        }
                    }
                    
                    // Remove unwanted lines (border lines and separator lines)
                    if (child.className === 'Line') {
                        const lineX = child.attrs.x || 0;
                        const lineY = child.attrs.y || 0;
                        const points = child.attrs.points || [];
                        const stroke = child.attrs.stroke || '';
                        const lineName = child.attrs.name || '';
                        
                        // Skip grid lines
                        if (lineName === 'grid-line') {
                            return;
                        }
                        
                        // Calculate actual line position and width from points
                        let actualX = lineX;
                        let actualY = lineY;
                        let lineWidth = 0;
                        
                        if (points.length >= 4) {
                            const x1 = points[0];
                            const y1 = points[1];
                            const x2 = points[2];
                            const y2 = points[3];
                            lineWidth = Math.abs(x2 - x1);
                            actualX = lineX + Math.min(x1, x2);
                            actualY = lineY + Math.min(y1, y2);
                        } else {
                            // If no points, check width attribute
                            lineWidth = child.attrs.width || 0;
                            actualX = lineX;
                            actualY = lineY;
                        }
                        
                        // Remove border lines at origin (0,0) with full page width
                        const isAtOrigin = Math.abs(actualX) < 5 && Math.abs(actualY) < 5;
                        const isFullWidth = lineWidth > dimensions.width * 0.9;
                        const isGrayOrBlue = stroke === '#e0e0e0' || stroke === '#dee2e6' || stroke === 'gray' || stroke === 'grey' || 
                                            stroke === '#cccccc' || stroke === '#999999' || stroke === '#667eea';
                        
                        // Remove any line at origin with full width (border lines)
                        if (isAtOrigin && isFullWidth) {
                            console.log('Removing unwanted border line at origin on load:', {actualX, actualY, lineWidth, stroke, name: lineName});
                            child.destroy();
                            return;
                        }
                        
                        // Remove unwanted gray/blue separator lines near top
                        if (points.length >= 4) {
                            const y1 = points[1];
                            const y2 = points[3];
                            const isNearTop = Math.min(y1, y2) < 50 || actualY < 50;
                            
                            if (isFullWidth && isNearTop && isGrayOrBlue && lineName !== 'items-table-separator' && lineName !== 'expenses-table-separator') {
                                console.log('Removing unwanted separator line on load:', {actualX, actualY, lineWidth, stroke, name: lineName});
                                child.destroy();
                                return;
                            }
                        }
                    }
                });
                
                layer.draw();
                
                // Update width and height variables for fit function
                width = dimensions.width;
                height = dimensions.height;
                
                // Redraw grid for new size
                drawGrid();
                
                // Remove any warning indicator dots that might have been loaded
                layer.find('.warning-indicator').forEach(indicator => {
                    indicator.destroy();
                });
                
                // Fix logo images: Konva Image nodes need to reload the image from URL
                // When Konva Image nodes are serialized/deserialized, the image data is lost
                // We need to reload the image from the URL and restore all attributes
                if (LOGO_URL) {
                    // Find logo images by name attribute first
                    let logoImages = layer.find('[name="logo"]');
                    
                    // Also check all Image nodes in case name attribute is lost during deserialization
                    // An Image node with name="logo" or any Image node that should be a logo
                    const allImages = layer.find('Image');
                    allImages.forEach(function(imgNode) {
                        // If it's already in logoImages, skip
                        if (logoImages.indexOf(imgNode) === -1) {
                            // Check if it has name="logo" or if it's likely a logo (has width/height but no image data)
                            const nodeName = imgNode.name ? imgNode.name() : (imgNode.attrs ? imgNode.attrs.name : null);
                            if (nodeName === 'logo' || (!imgNode.image() && imgNode.width() && imgNode.height())) {
                                logoImages = logoImages.concat([imgNode]);
                            }
                        }
                    });
                    
                    console.log('Found', logoImages.length, 'logo image(s) to restore');
                    
                    logoImages.forEach(function(logoNode) {
                        // Check if it's an Image node - use multiple methods for robustness
                        const isImage = logoNode.className === 'Image' || 
                                      (logoNode.constructor && logoNode.constructor.name === 'Image') ||
                                      (logoNode instanceof Konva.Image) ||
                                      (logoNode.getClassName && logoNode.getClassName() === 'Image');
                        
                        if (isImage) {
                            // Save all attributes before reloading
                            const savedAttrs = {
                                x: logoNode.x(),
                                y: logoNode.y(),
                                width: logoNode.width(),
                                height: logoNode.height(),
                                scaleX: logoNode.scaleX(),
                                scaleY: logoNode.scaleY(),
                                rotation: logoNode.rotation(),
                                opacity: logoNode.opacity(),
                                draggable: logoNode.draggable(),
                                visible: logoNode.visible()
                            };
                            
                            // Store reference to update elements array
                            const oldNode = logoNode;
                            const parent = logoNode.getParent();
                            const index = logoNode.getZIndex();
                            
                            console.log('Restoring logo image at', savedAttrs.x, savedAttrs.y);
                            
                            // Reload the image from URL
                            Konva.Image.fromURL(LOGO_URL, function(newImage) {
                                // Calculate actual size considering scale
                                // If scaleX/scaleY are not 1, the actual size is width*scaleX and height*scaleY
                                let finalWidth = savedAttrs.width;
                                let finalHeight = savedAttrs.height;
                                
                                // If scale was applied, we need to account for it
                                // Reset scale to 1 and apply the scaled dimensions as the new width/height
                                if (savedAttrs.scaleX !== 1 || savedAttrs.scaleY !== 1) {
                                    finalWidth = savedAttrs.width * savedAttrs.scaleX;
                                    finalHeight = savedAttrs.height * savedAttrs.scaleY;
                                }
                                
                                // Restore all attributes, but reset scale to 1 and use calculated dimensions
                                newImage.setAttrs({
                                    x: savedAttrs.x,
                                    y: savedAttrs.y,
                                    width: finalWidth,
                                    height: finalHeight,
                                    scaleX: 1,
                                    scaleY: 1,
                                    rotation: savedAttrs.rotation,
                                    opacity: savedAttrs.opacity,
                                    draggable: savedAttrs.draggable,
                                    visible: savedAttrs.visible
                                });
                                newImage.name('logo');
                                
                                // Replace the old node with the new one
                                oldNode.destroy();
                                parent.add(newImage);
                                newImage.zIndex(index);
                                
                                // Update elements array
                                const elementIndex = elements.findIndex(e => e.node === oldNode);
                                if (elementIndex !== -1) {
                                    elements[elementIndex].node = newImage;
                                } else {
                                    // Add to elements array if not found
                                    elements.push({ type: 'logo', node: newImage });
                                }
                                
                                // Setup selection for the new image
                                setupSelection(newImage);
                                
                                layer.draw();
                                console.log('Logo image restored successfully');
                            }, function(error) {
                                console.error('Failed to load logo image:', error);
                                // Keep the old node if image fails to load, but try to make it visible
                                // by setting a placeholder
                                if (oldNode && oldNode.parent) {
                                    oldNode.visible(true);
                                    layer.draw();
                                }
                            });
                        } else {
                            console.warn('Found node with name="logo" but it is not an Image:', logoNode.className, logoNode.constructor ? logoNode.constructor.name : 'unknown');
                        }
                    });
                }
                
                // Restore decorative images - must happen AFTER stage is created and fully deserialized
                // Find decorative image groups and restore their images
                console.log('🔍 Starting decorative image restoration...');
                console.log('Layer children count:', layer.children.length);
                console.log('Searching for decorative-image groups...');
                console.log('Saved JSON available:', !!savedJson);
                if (savedJson && savedJson.children) {
                    console.log('Saved JSON has', savedJson.children.length, 'top-level children');
                }
                
                setTimeout(() => {
                    let decorativeImageGroups = layer.find('[name="decorative-image"]');
                    console.log('Found', decorativeImageGroups.length, 'decorative image group(s) via layer.find');
                    
                    // Also try finding by className and checking name - check ALL children, not just Groups
                    console.log('Checking all', layer.children.length, 'layer children...');
                    const potentialGroups = [];
                    layer.children.forEach((child, idx) => {
                        // Check if it's a Group by className or getType
                        const isGroup = child.className === 'Group' || 
                                       (child.getType && child.getType() === 'Group') ||
                                       (child.constructor && child.constructor.name === 'Group');
                        
                        if (isGroup) {
                            const nameViaGetAttr = child.getAttr ? child.getAttr('name') : null;
                            const nameViaName = child.name ? child.name() : null;
                            const nameViaAttrs = child.attrs ? child.attrs.name : null;
                            
                            if (nameViaGetAttr === 'decorative-image' || 
                                nameViaName === 'decorative-image' || 
                                nameViaAttrs === 'decorative-image') {
                                console.log(`  ✅ Found decorative-image group at index ${idx}`);
                                potentialGroups.push(child);
                            } else {
                                // Check if it has Image or Rect children (might be decorative image without name)
                                const hasImage = child.findOne ? child.findOne('Image') : null;
                                const hasRect = child.findOne ? child.findOne('Rect') : null;
                                if (hasImage || hasRect) {
                                    console.log(`  ⚠️ Group ${idx} has Image/Rect but name="${nameViaGetAttr || nameViaName || nameViaAttrs || 'unnamed'}"`);
                                }
                            }
                        } else {
                            // Check if it's an Image node that might be part of a decorative image
                            if (child.className === 'Image' || (child.getType && child.getType() === 'Image')) {
                                const parent = child.getParent ? child.getParent() : null;
                                if (parent && (parent.className === 'Group' || (parent.getType && parent.getType() === 'Group'))) {
                                    const parentName = parent.getAttr ? parent.getAttr('name') : (parent.attrs ? parent.attrs.name : null);
                                    if (parentName === 'decorative-image') {
                                        console.log(`  ✅ Found decorative-image group via Image node's parent at index ${idx}`);
                                        if (!potentialGroups.includes(parent)) {
                                            potentialGroups.push(parent);
                                        }
                                    }
                                }
                            }
                        }
                    });
                    
                    console.log('Total potential decorative-image groups found:', potentialGroups.length);
                    
                    // Also search the saved JSON directly to find decorative-image groups
                    const jsonGroups = [];
                    if (savedJson && savedJson.children) {
                        function findDecorativeImageGroupsInJson(node, path = '') {
                            if (node.attrs && node.attrs.name === 'decorative-image') {
                                console.log(`  ✅ Found decorative-image in JSON at path: ${path}, imageUrl:`, node.attrs.imageUrl || 'NOT FOUND');
                                jsonGroups.push({ node: node, path: path, imageUrl: node.attrs.imageUrl || '' });
                            }
                            if (node.children) {
                                node.children.forEach((child, idx) => {
                                    findDecorativeImageGroupsInJson(child, path ? `${path}.children[${idx}]` : `children[${idx}]`);
                                });
                            }
                        }
                        console.log('Searching saved JSON for decorative-image groups...');
                        savedJson.children.forEach((child, idx) => {
                            findDecorativeImageGroupsInJson(child, `children[${idx}]`);
                        });
                        console.log('Found', jsonGroups.length, 'decorative-image group(s) in saved JSON');
                    }
                    
                    // Combine found groups with potential groups
                    let groupsToProcess = decorativeImageGroups;
                    
                    // Add any potential groups that weren't found by layer.find
                    potentialGroups.forEach(group => {
                        if (!groupsToProcess.includes(group)) {
                            console.log('Adding potential group to process list');
                            groupsToProcess = groupsToProcess.concat([group]);
                        }
                    });
                    
                    // If we found groups in JSON but not in the layer, try to find them by matching position/attributes
                    if (jsonGroups.length > 0 && groupsToProcess.length === 0) {
                        console.log('⚠️ Found groups in JSON but not in layer - trying to match by attributes...');
                        jsonGroups.forEach(jsonGroup => {
                            // Try to find matching group in layer by checking all children
                            layer.children.forEach(child => {
                                const isGroup = child.className === 'Group' || 
                                               (child.getType && child.getType() === 'Group');
                                if (isGroup) {
                                    // Check if attributes match (x, y, width, height)
                                    const jsonAttrs = jsonGroup.node.attrs || {};
                                    const childAttrs = child.attrs || {};
                                    if (Math.abs((jsonAttrs.x || 0) - (childAttrs.x || 0)) < 1 &&
                                        Math.abs((jsonAttrs.y || 0) - (childAttrs.y || 0)) < 1) {
                                        console.log('  ✅ Matched group by position, setting name and imageUrl');
                                        child.setAttr('name', 'decorative-image');
                                        child.name('decorative-image');
                                        if (child.attrs) {
                                            child.attrs.name = 'decorative-image';
                                        }
                                        if (jsonGroup.imageUrl) {
                                            child.setAttr('imageUrl', jsonGroup.imageUrl);
                                            child.attrs.imageUrl = jsonGroup.imageUrl;
                                        }
                                        if (!groupsToProcess.includes(child)) {
                                            groupsToProcess = groupsToProcess.concat([child]);
                                        }
                                    }
                                }
                            });
                        });
                    }
                    
                    if (groupsToProcess.length === 0) {
                        console.warn('⚠️ No decorative image groups found! Checking all groups for Image/Rect children...');
                        layer.children.forEach((child, idx) => {
                            // Check if it's a Group
                            const isGroup = child.className === 'Group' || 
                                           (child.getType && child.getType() === 'Group') ||
                                           (child.constructor && child.constructor.name === 'Group');
                            
                            if (isGroup) {
                                const nameViaGetAttr = child.getAttr ? child.getAttr('name') : null;
                                const nameViaName = child.name ? child.name() : null;
                                const nameViaAttrs = child.attrs ? child.attrs.name : null;
                                console.log(`Group ${idx}: name via getAttr="${nameViaGetAttr}", via name()="${nameViaName}", via attrs="${nameViaAttrs}"`);
                                
                                // Check if this might be a decorative image group by checking children
                                const hasImage = child.findOne ? child.findOne('Image') : null;
                                const hasPlaceholderRect = child.findOne ? child.findOne('Rect') : null;
                                if (hasImage || hasPlaceholderRect) {
                                    console.log(`  ⚠️ Group ${idx} has Image or Rect - might be decorative image but name is missing!`);
                                    // Try to fix it
                                    if (!nameViaGetAttr && !nameViaName && !nameViaAttrs) {
                                        console.log(`  🔧 Attempting to fix: setting name to 'decorative-image'`);
                                        child.setAttr('name', 'decorative-image');
                                        child.name('decorative-image');
                                        if (child.attrs) {
                                            child.attrs.name = 'decorative-image';
                                        }
                                        // Add to groups to process
                                        if (!groupsToProcess.includes(child)) {
                                            groupsToProcess = groupsToProcess.concat([child]);
                                        }
                                    }
                                }
                            }
                        });
                        
                        // Try searching again after potential fixes
                        const decorativeImageGroupsAfterFix = layer.find('[name="decorative-image"]');
                        if (decorativeImageGroupsAfterFix.length > 0) {
                            console.log('✅ Found', decorativeImageGroupsAfterFix.length, 'decorative image group(s) after fixing names');
                            // Merge with existing groupsToProcess
                            decorativeImageGroupsAfterFix.forEach(group => {
                                if (!groupsToProcess.includes(group)) {
                                    groupsToProcess = groupsToProcess.concat([group]);
                                }
                            });
                        }
                    }
                    
                    console.log('Final groupsToProcess count:', groupsToProcess.length);
                    
                    groupsToProcess.forEach(decorativeImageGroup => {
                        console.log('Found decorative image group:', decorativeImageGroup);
                        console.log('Group attrs:', decorativeImageGroup.attrs);
                        
                        // Check if there's an Image node already (from deserialization)
                        const existingImage = decorativeImageGroup.findOne('Image');
                        console.log('Existing image node:', existingImage);
                        if (existingImage) {
                            console.log('Existing image attrs:', existingImage.attrs);
                            // Check if it has valid image data
                            try {
                                const hasImageData = existingImage.image();
                                console.log('Existing image has data:', !!hasImageData);
                                if (!hasImageData) {
                                    // Image node exists but has no data - remove it
                                    console.log('Removing image node without data');
                                    existingImage.destroy();
                                }
                            } catch(e) {
                                console.log('Could not check image data, removing node:', e);
                                existingImage.destroy();
                            }
                        }
                        
                        // Try multiple methods to get imageUrl
                        let imageUrl = '';
                        try {
                            imageUrl = decorativeImageGroup.getAttr('imageUrl') || '';
                            console.log('Got imageUrl via getAttr:', imageUrl);
                        } catch(e) {
                            console.warn('Could not get imageUrl via getAttr:', e);
                        }
                        if (!imageUrl) {
                            imageUrl = decorativeImageGroup.attrs.imageUrl || '';
                            console.log('Got imageUrl from attrs.imageUrl:', imageUrl);
                        }
                        if (!imageUrl && decorativeImageGroup.attrs) {
                            imageUrl = decorativeImageGroup.attrs.imageUrl || '';
                        }
                        
                        // Also check the saved JSON directly if we still don't have it
                        if (!imageUrl && savedJson && savedJson.children) {
                            try {
                                // Search through the JSON structure for decorative-image groups
                                function findImageUrlInJson(node, targetName) {
                                    if (node.attrs && node.attrs.name === targetName) {
                                        return node.attrs.imageUrl || '';
                                    }
                                    if (node.children) {
                                        for (let child of node.children) {
                                            const url = findImageUrlInJson(child, targetName);
                                            if (url) return url;
                                        }
                                    }
                                    return '';
                                }
                                const jsonImageUrl = findImageUrlInJson(savedJson, 'decorative-image');
                                if (jsonImageUrl) {
                                    imageUrl = jsonImageUrl;
                                    // Also set it in the group for future use
                                    decorativeImageGroup.setAttr('imageUrl', imageUrl);
                                    decorativeImageGroup.attrs.imageUrl = imageUrl;
                                    console.log('Found imageUrl in saved JSON:', imageUrl);
                                }
                            } catch(e) {
                                console.warn('Could not search JSON for imageUrl:', e);
                            }
                        }
                        
                        console.log('Restoring decorative image, final URL:', imageUrl);
                        
                        // CRITICAL FIX: Ensure decorative image group is always visible, even without imageUrl
                        decorativeImageGroup.visible(true);
                        
                        if (imageUrl && imageUrl.trim() !== '') {
                            // Remove any existing image node (it won't have valid image data after deserialization)
                            if (existingImage) {
                                existingImage.destroy();
                            }
                            
                            // Remove placeholder elements
                            const rect = decorativeImageGroup.findOne('Rect');
                            const allTexts = decorativeImageGroup.find('Text');
                            allTexts.forEach(textNode => {
                                const text = textNode.text();
                                if (text.includes('Decorative') || text.includes('🖼️')) {
                                    textNode.destroy();
                                }
                            });
                            
                            if (rect) rect.destroy();
                            
                            // Create a temporary image to verify it loads
                            const tempImg = new Image();
                            tempImg.crossOrigin = 'anonymous';
                            
                            tempImg.onload = function() {
                                console.log('Decorative image verified, loading into Konva. Dimensions:', tempImg.width, 'x', tempImg.height);
                                
                                // Load the actual image
                                Konva.Image.fromURL(imageUrl, function(konvaImage) {
                                    const maxSize = 200;
                                    let width = konvaImage.width() || tempImg.width || 100;
                                    let height = konvaImage.height() || tempImg.height || 100;
                                    const aspectRatio = width / height;
                                    
                                    if (width > maxSize || height > maxSize) {
                                        if (width > height) {
                                            width = maxSize;
                                            height = maxSize / aspectRatio;
                                        } else {
                                            height = maxSize;
                                            width = maxSize * aspectRatio;
                                        }
                                    }
                                    
                                // Preserve transparency - don't set fill or opacity that would override image alpha
                                konvaImage.setAttrs({
                                    x: 0,
                                    y: 0,
                                    width: width,
                                    height: height,
                                    // Ensure transparency is preserved - Konva.Image preserves alpha by default
                                    opacity: 1.0,  // Don't override image's built-in alpha channel
                                    visible: true,  // Explicitly make it visible
                                    listening: true
                                });
                                
                                decorativeImageGroup.width(width);
                                decorativeImageGroup.height(height);
                                decorativeImageGroup.visible(true);  // Ensure group is visible
                                decorativeImageGroup.add(konvaImage);
                                
                                // Force a redraw and ensure the image is on top
                                layer.draw();
                                
                                // Also try drawing the stage to ensure everything is rendered
                                stage.draw();
                                
                                // Verify the image is actually in the group and visible
                                const imageInGroup = decorativeImageGroup.findOne('Image');
                                if (imageInGroup && imageInGroup === konvaImage) {
                                    console.log('✅ Decorative image restored successfully, dimensions:', width, 'x', height);
                                    console.log('✅ Image node is in group and visible');
                                } else {
                                    console.error('❌ Image node not found in group after adding!');
                                    console.log('Expected:', konvaImage);
                                    console.log('Found in group:', imageInGroup);
                                    console.log('Group children:', decorativeImageGroup.children.map(c => c.className));
                                    // Try adding again
                                    if (!decorativeImageGroup.children.includes(konvaImage)) {
                                        decorativeImageGroup.add(konvaImage);
                                        layer.draw();
                                        stage.draw();
                                    }
                                }
                                
                                console.log('Image visible:', konvaImage.visible());
                                console.log('Group visible:', decorativeImageGroup.visible());
                                console.log('Image in group:', decorativeImageGroup.children.includes(konvaImage));
                                
                                // Force another draw after a short delay to ensure rendering
                                setTimeout(() => {
                                    layer.draw();
                                    stage.draw();
                                }, 100);
                                }, function(error) {
                                    console.error('Failed to load decorative image into Konva:', error);
                                    // Restore placeholder if image fails to load
                                    const placeholderRect = new Konva.Rect({
                                        x: 0,
                                        y: 0,
                                        width: 100,
                                        height: 100,
                                        fill: '#f0f0f0',
                                        stroke: '#999',
                                        strokeWidth: 2,
                                        dash: [5, 5]
                                    });
                                    decorativeImageGroup.add(placeholderRect);
                                    layer.draw();
                                });
                            };
                            
                            tempImg.onerror = function() {
                                console.error('Failed to verify decorative image:', imageUrl);
                                // Restore placeholder on error
                                const placeholderRect = new Konva.Rect({
                                    x: 0,
                                    y: 0,
                                    width: 100,
                                    height: 100,
                                    fill: '#f0f0f0',
                                    stroke: '#999',
                                    strokeWidth: 2,
                                    dash: [5, 5]
                                });
                                decorativeImageGroup.add(placeholderRect);
                                decorativeImageGroup.visible(true);
                                layer.draw();
                                stage.draw();
                            };
                            
                            // Set src after all handlers are attached
                            tempImg.src = imageUrl;
                            
                            // Also try loading directly if tempImg fails silently
                            setTimeout(() => {
                                if (!tempImg.complete || tempImg.naturalWidth === 0) {
                                    console.warn('Temp image did not load, trying direct Konva load');
                                    // Try loading directly with Konva
                                    Konva.Image.fromURL(imageUrl, function(directImage) {
                                        if (directImage && directImage.image()) {
                                            const maxSize = 200;
                                            let width = directImage.width() || 100;
                                            let height = directImage.height() || 100;
                                            const aspectRatio = width / height;
                                            
                                            if (width > maxSize || height > maxSize) {
                                                if (width > height) {
                                                    width = maxSize;
                                                    height = maxSize / aspectRatio;
                                                } else {
                                                    height = maxSize;
                                                    width = maxSize * aspectRatio;
                                                }
                                            }
                                            
                                            directImage.setAttrs({
                                                x: 0,
                                                y: 0,
                                                width: width,
                                                height: height,
                                                opacity: 1.0,
                                                visible: true,
                                                listening: true
                                            });
                                            
                                            decorativeImageGroup.width(width);
                                            decorativeImageGroup.height(height);
                                            decorativeImageGroup.visible(true);
                                            decorativeImageGroup.add(directImage);
                                            layer.draw();
                                            stage.draw();
                                            console.log('Decorative image loaded directly via Konva');
                                        }
                                    }, function(error) {
                                        console.error('Direct Konva load also failed:', error);
                                    });
                                }
                            }, 2000);  // Wait 2 seconds before fallback
                        } else {
                            console.log('No imageUrl found for decorative image, ensuring placeholder is visible');
                            // Even if no imageUrl, ensure the group is visible and has placeholder elements
                            decorativeImageGroup.visible(true);
                            
                            // Check if placeholder elements exist, if not create them
                            const hasRect = decorativeImageGroup.findOne('Rect');
                            const hasPlaceholderText = decorativeImageGroup.find('Text').some(textNode => {
                                const text = textNode.text();
                                return text.includes('Decorative') || text.includes('🖼️');
                            });
                            
                            if (!hasRect || !hasPlaceholderText) {
                                console.log('Creating placeholder elements for decorative image without imageUrl');
                                
                                // Remove any existing image that might be there
                                const existingImage = decorativeImageGroup.findOne('Image');
                                if (existingImage) {
                                    existingImage.destroy();
                                }
                                
                                // Create placeholder rect if missing
                                if (!hasRect) {
                                    const placeholderRect = new Konva.Rect({
                                        x: 0,
                                        y: 0,
                                        width: 100,
                                        height: 100,
                                        fill: '#f0f0f0',
                                        stroke: '#999',
                                        strokeWidth: 2,
                                        dash: [5, 5]
                                    });
                                    decorativeImageGroup.add(placeholderRect);
                                }
                                
                                // Create placeholder text/icon if missing
                                if (!hasPlaceholderText) {
                                    const placeholderText = new Konva.Text({
                                        x: 10,
                                        y: 40,
                                        text: 'Decorative\nImage',
                                        fontSize: 12,
                                        fontFamily: 'Arial',
                                        fill: '#666',
                                        align: 'center',
                                        width: 80
                                    });
                                    decorativeImageGroup.add(placeholderText);
                                    
                                    const placeholderIcon = new Konva.Text({
                                        x: 35,
                                        y: 15,
                                        text: '🖼️',
                                        fontSize: 24,
                                        width: 30,
                                        align: 'center'
                                    });
                                    decorativeImageGroup.add(placeholderIcon);
                                }
                                
                                decorativeImageGroup.width(100);
                                decorativeImageGroup.height(100);
                            }
                            
                            layer.draw();
                            stage.draw();
                        }
                    });
                }, 300);  // Increased delay to ensure stage is fully loaded and all elements are ready
                
                // Re-setup selections for all elements (skip background and grid lines)
                layer.children.forEach(child => {
                    if (child !== background &&
                        child.className !== 'Transformer' &&
                        !(child.className === 'Line' && child.attrs.name === 'grid-line') &&
                        !(child.className === 'Rect' && child.attrs.name === 'page-border')) {
                        // Skip logo images as they're handled above
                        if (child.className === 'Image' && child.attrs.name === 'logo') {
                            return;
                        }
                        setupSelection(child);
                        // If it's a table Group, also setup selection on children
                        if (child.className === 'Group' && (child.attrs.name === 'items-table' || child.attrs.name === 'expenses-table' || child.attrs.name === 'quote-items-table')) {
                            child.children.forEach(grandChild => {
                                setupSelection(grandChild);
                            });
                        }
                        // If it's a decorative-image Group, also setup selection on children
                        if (child.className === 'Group' && child.attrs.name === 'decorative-image') {
                            child.children.forEach(grandChild => {
                                setupSelection(grandChild);
                            });
                        }
                        elements.push({ type: child.attrs.name || child.className, node: child });
                    }
                });
                
                layer.draw();
                
                // After setupSelection, try to restore decorative images again if they weren't found earlier
                // This is a fallback in case the first attempt ran too early
                setTimeout(() => {
                    const decorativeImageGroups = layer.find('[name="decorative-image"]');
                    if (decorativeImageGroups.length > 0) {
                        console.log('🔄 Fallback: Found', decorativeImageGroups.length, 'decorative image group(s) after setupSelection');
                        decorativeImageGroups.forEach(decorativeImageGroup => {
                            const existingImage = decorativeImageGroup.findOne('Image');
                            
                            // Try multiple methods to get imageUrl
                            let imageUrl = '';
                            try {
                                imageUrl = decorativeImageGroup.getAttr('imageUrl') || '';
                            } catch(e) {
                                console.warn('Fallback: Could not get imageUrl via getAttr:', e);
                            }
                            if (!imageUrl) {
                                imageUrl = decorativeImageGroup.attrs.imageUrl || '';
                            }
                            
                            // Also check the saved JSON directly if we still don't have it
                            if (!imageUrl && savedJson && savedJson.children) {
                                try {
                                    function findImageUrlInJson(node, targetName) {
                                        if (node.attrs && node.attrs.name === targetName) {
                                            return node.attrs.imageUrl || '';
                                        }
                                        if (node.children) {
                                            for (let child of node.children) {
                                                const url = findImageUrlInJson(child, targetName);
                                                if (url) return url;
                                            }
                                        }
                                        return '';
                                    }
                                    const jsonImageUrl = findImageUrlInJson(savedJson, 'decorative-image');
                                    if (jsonImageUrl) {
                                        imageUrl = jsonImageUrl;
                                        decorativeImageGroup.setAttr('imageUrl', imageUrl);
                                        decorativeImageGroup.attrs.imageUrl = imageUrl;
                                        console.log('🔄 Fallback: Found imageUrl in saved JSON:', imageUrl);
                                    }
                                } catch(e) {
                                    console.warn('Fallback: Could not search JSON for imageUrl:', e);
                                }
                            }
                            
                            // Only restore if we have an imageUrl but no visible image
                            if (imageUrl && imageUrl.trim() !== '') {
                                const hasValidImage = existingImage && existingImage.image();
                                if (!hasValidImage) {
                                    console.log('🔄 Fallback: Restoring image for group with URL:', imageUrl);
                                    
                                    // Remove existing image node if it doesn't have valid data
                                    if (existingImage && !existingImage.image()) {
                                        existingImage.destroy();
                                    }
                                    
                                    // Remove placeholder elements
                                    const rect = decorativeImageGroup.findOne('Rect');
                                    const allTexts = decorativeImageGroup.find('Text');
                                    allTexts.forEach(textNode => {
                                        const text = textNode.text();
                                        if (text.includes('Decorative') || text.includes('🖼️')) {
                                            textNode.destroy();
                                        }
                                    });
                                    if (rect) rect.destroy();
                                    
                                    Konva.Image.fromURL(imageUrl, function(konvaImage) {
                                        const maxSize = 200;
                                        let width = konvaImage.width() || 100;
                                        let height = konvaImage.height() || 100;
                                        const aspectRatio = width / height;
                                        
                                        if (width > maxSize || height > maxSize) {
                                            if (width > height) {
                                                width = maxSize;
                                                height = maxSize / aspectRatio;
                                            } else {
                                                height = maxSize;
                                                width = maxSize * aspectRatio;
                                            }
                                        }
                                        
                                        konvaImage.setAttrs({
                                            x: 0,
                                            y: 0,
                                            width: width,
                                            height: height,
                                            opacity: 1.0,
                                            visible: true,
                                            listening: true
                                        });
                                        
                                        decorativeImageGroup.width(width);
                                        decorativeImageGroup.height(height);
                                        decorativeImageGroup.visible(true);
                                        decorativeImageGroup.add(konvaImage);
                                        layer.draw();
                                        stage.draw();
                                        console.log('✅ Fallback: Decorative image restored successfully');
                                    }, function(error) {
                                        console.error('❌ Fallback: Failed to load image:', error);
                                        // Restore placeholder on error
                                        const placeholderRect = new Konva.Rect({
                                            x: 0,
                                            y: 0,
                                            width: 100,
                                            height: 100,
                                            fill: '#f0f0f0',
                                            stroke: '#999',
                                            strokeWidth: 2,
                                            dash: [5, 5]
                                        });
                                        decorativeImageGroup.add(placeholderRect);
                                        decorativeImageGroup.visible(true);
                                        layer.draw();
                                        stage.draw();
                                    });
                                } else {
                                    console.log('🔄 Fallback: Image already has valid image data, skipping restoration');
                                }
                            } else {
                                console.log('🔄 Fallback: No imageUrl found for decorative image group');
                            }
                        });
                    }
                }, 500);
                
                // Refit canvas after loading saved design
                setTimeout(() => {
                    fitCanvasToContainer();
                }, 150);
                
                console.log('✅ Saved design loaded successfully for size:', currentSize);
    };

    const MAX_QUOTE_EDITOR_HISTORY = 45;
    let quoteEditorHistoryStack = [];
    let quoteEditorHistoryIndex = -1;
    let quoteEditorHistorySuspended = false;
    let quoteEditorHistoryDebounceTimer = null;

    function getQuoteEditorSnapshotString() {
        try {
            if (!stage || typeof stage.toJSON !== 'function') return null;
            return JSON.stringify(stage.toJSON());
        } catch (e) {
            return null;
        }
    }

    function pushQuoteEditorHistory() {
        if (quoteEditorHistorySuspended || !stage) return;
        const snap = getQuoteEditorSnapshotString();
        if (!snap) return;
        if (quoteEditorHistoryIndex < quoteEditorHistoryStack.length - 1) {
            quoteEditorHistoryStack = quoteEditorHistoryStack.slice(0, quoteEditorHistoryIndex + 1);
        }
        const last = quoteEditorHistoryStack[quoteEditorHistoryStack.length - 1];
        if (last === snap) return;
        quoteEditorHistoryStack.push(snap);
        quoteEditorHistoryIndex = quoteEditorHistoryStack.length - 1;
        while (quoteEditorHistoryStack.length > MAX_QUOTE_EDITOR_HISTORY) {
            quoteEditorHistoryStack.shift();
            quoteEditorHistoryIndex--;
        }
    }

    function scheduleQuoteEditorHistoryPush() {
        if (quoteEditorHistorySuspended) return;
        clearTimeout(quoteEditorHistoryDebounceTimer);
        quoteEditorHistoryDebounceTimer = setTimeout(function() {
            pushQuoteEditorHistory();
        }, 160);
    }

    function applyQuoteEditorHistoryAtCurrentIndex() {
        const snap = quoteEditorHistoryStack[quoteEditorHistoryIndex];
        if (!snap) return;
        quoteEditorHistorySuspended = true;
        selectedElement = null;
        try {
            if (stage && stage.find) {
                stage.find('Transformer').forEach(function(t) { t.destroy(); });
            }
            window.__quoteReloadCanvasFromSnapshot(snap);
        } catch (e) {
            console.error('Quote history restore failed:', e);
        } finally {
            quoteEditorHistorySuspended = false;
        }
        if (typeof updateZoomDisplay === 'function') updateZoomDisplay();
        if (typeof fitCanvasToContainer === 'function') fitCanvasToContainer();
    }

    function undoQuoteEditor() {
        if (quoteEditorHistoryIndex <= 0) return;
        quoteEditorHistoryIndex--;
        applyQuoteEditorHistoryAtCurrentIndex();
    }

    function redoQuoteEditor() {
        if (quoteEditorHistoryIndex >= quoteEditorHistoryStack.length - 1) return;
        quoteEditorHistoryIndex++;
        applyQuoteEditorHistoryAtCurrentIndex();
    }

    // Load saved design or default layout
    setTimeout(() => {
        if (SAVED_DESIGN_JSON && SAVED_DESIGN_JSON.trim() !== '') {
            console.log('Loading saved design from database...');
            try {
                window.__quoteReloadCanvasFromSnapshot(SAVED_DESIGN_JSON);
            } catch (error) {
                console.error('Failed to load saved design:', error);
                console.log('Loading default layout instead...');
                loadDefaultLayout();
            }
        } else {
            console.log('No saved design found, loading default layout...');
            loadDefaultLayout();
        }
        setTimeout(function() {
            quoteEditorHistoryStack = [];
            quoteEditorHistoryIndex = -1;
            pushQuoteEditorHistory();
        }, 950);
    }, 100);
    
    function loadDefaultLayout() {
        console.log('Loading comprehensive default layout...');
        
        // ========== HEADER SECTION ==========
        // Logo (top left)
        if (LOGO_URL) {
            addElement('logo', 40, 30);
        }
        
        // Company Name and Address (top left)
        addElement('company-name', 40, 95);
        addElement('company-address', 40, 125);
        addElement('company-phone', 40, 155);
        addElement('company-email', 40, 175);
        
        // QUOTE Heading (center-right)
        addElement('heading', 320, 35);
        
        // Quote Details Box (top right)
        addElement('rectangle', 380, 85, null);  // Background box for invoice details
        const rect = layer.children[layer.children.length - 1];
        rect.width(175);
        rect.height(110);
        rect.fill('#f8f9fa');
        rect.stroke('#dee2e6');
        rect.strokeWidth(1);
        
        addElement('invoice-number', 395, 95);
        addElement('invoice-date', 395, 120);
        addElement('due-date', 395, 145);
        addElement('invoice-status', 395, 170);
        
        // ========== CLIENT & PROJECT SECTION ==========
        // Bill To Section (left)
        const billToLabel = new Konva.Text({
            x: 40,
            y: 230,
            text: 'BILL TO:',
            fontSize: 11,
            fontStyle: 'bold',
            fill: '#667eea',
            fontFamily: 'Arial',
            draggable: true,
            name: 'section-label'
        });
        layer.add(billToLabel);
        setupSelection(billToLabel);
        elements.push({ type: 'label', node: billToLabel });
        
        addElement('client-name', 40, 250);
        addElement('client-address', 40, 275);
        addElement('client-email', 40, 305);
        
        // Project Info (right)
        const projectLabel = new Konva.Text({
            x: 320,
            y: 230,
            text: 'PROJECT:',
            fontSize: 11,
            fontStyle: 'bold',
            fill: '#667eea',
            fontFamily: 'Arial',
            draggable: true,
            name: 'section-label'
        });
        layer.add(projectLabel);
        setupSelection(projectLabel);
        elements.push({ type: 'label', node: projectLabel });
        
        addElement('project-name', 320, 250);
        addElement('currency', 320, 275);
        
        // ========== QUOTE ITEMS TABLE ==========
        addElement('quote-items-table', 40, 350);
        
        // ========== TOTALS SECTION ==========
        // Totals Box (right side) - moved down to give table more space
        addElement('rectangle', 380, 500, null);
        const totalsBox = layer.children[layer.children.length - 1];
        totalsBox.width(175);
        totalsBox.height(90);
        totalsBox.fill('#f8f9fa');
        totalsBox.stroke('#dee2e6');
        totalsBox.strokeWidth(1);
        
        addElement('subtotal', 395, 512);
        addElement('tax', 395, 540);
        
        // Total with highlight
        addElement('rectangle', 385, 565, null);
        const totalHighlight = layer.children[layer.children.length - 1];
        totalHighlight.width(165);
        totalHighlight.height(27);
        totalHighlight.fill('#667eea');
        totalHighlight.stroke('#667eea');
        totalHighlight.strokeWidth(1);
        
        addElement('totals', 395, 570);
        // Find the totals text element by name
        const totalText = layer.findOne('[name="totals"]');
        if (totalText && totalText.className === 'Text') {
            totalText.fill('white');
            totalText.fontStyle('bold');
        }
        
        // ========== QUOTE INFORMATION SECTION ==========
        const quoteInfoLabel = new Konva.Text({
            x: 40,
            y: 610,
            text: 'QUOTE INFORMATION:',
            fontSize: 11,
            fontStyle: 'bold',
            fill: '#667eea',
            fontFamily: 'Arial',
            draggable: true,
            name: 'section-label'
        });
        layer.add(quoteInfoLabel);
        setupSelection(quoteInfoLabel);
        elements.push({ type: 'label', node: quoteInfoLabel });
        
        addElement('quote-status', 40, 635);
        addElement('valid-until', 40, 660);
        addElement('quote-title', 40, 685);
        
        // Bank Info (right side)
        addElement('bank-info', 320, 635);
        
        // ========== NOTES & TERMS ==========
        addElement('notes', 40, 738);
        addElement('terms', 40, 768);
        
        // ========== FOOTER ==========
        // Footer text (centered)
        const footerText = new Konva.Text({
            x: 40,
            y: 820,
            text: 'Thank you for your business!',
            fontSize: 10,
            fill: '#6c757d',
            fontFamily: 'Arial',
            width: 515,
            align: 'center',
            draggable: true,
            name: 'footer-text'
        });
        layer.add(footerText);
        setupSelection(footerText);
        elements.push({ type: 'footer', node: footerText });
        
        layer.draw();
        console.log('✅ Default layout loaded with ' + layer.children.length + ' elements');
    }
}

// Initialize when DOM is ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializePDFEditor);
} else {
    initializePDFEditor();
}

// Confirm reset function
async function confirmResetPdfLayout() {
    const confirmed = await showConfirm(
        '{{ _("Reset to defaults?") }}',
        {
            title: '{{ _("Reset PDF Layout") }}',
            confirmText: '{{ _("Reset") }}',
            cancelText: '{{ _("Cancel") }}',
            variant: 'danger'
        }
    );
    if (confirmed) {
        document.getElementById('form-reset').submit();
    }
}

// Import JSON handler
document.getElementById('import-json-file')?.addEventListener('change', function(e) {
    const file = e.target.files[0];
    if (!file) return;
    
    const formData = new FormData();
    formData.append('json_file', file);
    formData.append('page_size', '{{ page_size }}');
    formData.append('csrf_token', '{{ csrf_token() }}');
    
    fetch('{{ url_for("admin.quote_pdf_layout_import_json") }}', {
        method: 'POST',
        body: formData
    })
    .then(response => {
        if (response.ok) {
            window.location.reload();
        } else {
            return response.text().then(text => {
                throw new Error(text);
            });
        }
    })
    .catch(error => {
        console.error('Import error:', error);
        alert('{{ _("Error importing template") }}: ' + error.message);
    })
    .finally(() => {
        // Reset file input
        e.target.value = '';
    });
});
</script>
{% endblock %}
