CF7 Photo Editor Demo
/* CF7 Compatible Fixed Header - Following WordPress Admin Patterns */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background: #f0f0f0;
padding-top: 80px; /* Space for fixed header */
}
.cf7-fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: #23282d;
color: white;
padding: 15px 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
z-index: 9999;
border-bottom: 1px solid #32373c;
}
.cf7-header-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.cf7-header-brand {
display: flex;
align-items: center;
gap: 12px;
}
.cf7-header-logo {
width: 32px;
height: 32px;
background: #0073aa;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
color: white;
}
.cf7-header-title {
font-size: 18px;
font-weight: 600;
margin: 0;
color: #f1f1f1;
}
.cf7-header-subtitle {
font-size: 12px;
color: #a0a5aa;
margin: 0;
}
.cf7-header-nav {
display: flex;
gap: 20px;
align-items: center;
}
.cf7-nav-link {
color: #a0a5aa;
text-decoration: none;
font-size: 14px;
padding: 8px 12px;
border-radius: 3px;
transition: all 0.2s ease;
}
.cf7-nav-link:hover,
.cf7-nav-link.active {
color: #00a0d2;
background: rgba(0, 160, 210, 0.1);
}
/* Main Layout Container */
.cf7-main-layout {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 280px 1fr;
gap: 20px;
padding: 20px;
min-height: calc(100vh - 80px);
}
/* CF7 Pinned Posts Sidebar - Following WordPress Admin Patterns */
.cf7-pinned-sidebar {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: 1px solid #ddd;
height: fit-content;
position: sticky;
top: 100px; /* Below fixed header */
}
.cf7-sidebar-header {
padding: 16px 20px;
border-bottom: 1px solid #e1e1e1;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
}
.cf7-sidebar-title {
font-size: 16px;
font-weight: 600;
color: #23282d;
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.cf7-pin-icon {
width: 16px;
height: 16px;
background: #0073aa;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 10px;
font-weight: bold;
}
.cf7-pinned-posts {
padding: 0;
margin: 0;
list-style: none;
}
.cf7-pinned-post {
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s ease;
}
.cf7-pinned-post:last-child {
border-bottom: none;
}
.cf7-pinned-post:hover {
background: #f8f9fa;
}
.cf7-post-link {
display: block;
padding: 16px 20px;
text-decoration: none;
color: inherit;
}
.cf7-post-title {
font-size: 14px;
font-weight: 500;
color: #23282d;
margin: 0 0 6px 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.cf7-post-meta {
font-size: 12px;
color: #666;
display: flex;
align-items: center;
gap: 8px;
}
.cf7-post-date {
color: #0073aa;
}
.cf7-post-category {
background: #e7f3ff;
color: #0073aa;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
}
/* Main Content Area */
.cf7-main-content {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: 1px solid #ddd;
overflow: hidden;
}
/* Responsive Design - CF7 Compatible */
@media (max-width: 768px) {
.cf7-main-layout {
grid-template-columns: 1fr;
padding: 10px;
}
.cf7-pinned-sidebar {
order: 2;
position: static;
margin-top: 20px;
}
.cf7-main-content {
order: 1;
}
.cf7-header-content {
flex-direction: column;
gap: 10px;
text-align: center;
}
.cf7-header-nav {
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
body {
padding-top: 120px; /* More space for mobile header */
}
}
@media (max-width: 480px) {
.cf7-header-nav {
display: none; /* Hide nav on very small screens */
}
body {
padding-top: 80px;
}
}
.usage-info {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 4px;
padding: 15px;
margin: 20px 0;
}
.usage-info h3 {
margin: 0 0 10px 0;
color: #0073aa;
}
.code {
background: #f1f1f1;
padding: 10px;
border-radius: 4px;
font-family: monospace;
border-left: 4px solid #0073aa;
}
Choose Background
Clear Background
[cf7-add-image class:cf7-btn-image text:"Add Image"]
[cf7-clear-canvas class:cf7-btn-clear text:"Clear All"]
[cf7-add-text class:cf7-btn-text text:"Add Text"]
Font:
[cf7-font-family class:cf7-select-font]
Size:
[cf7-font-size class:cf7-input-size value:"16"]
Style:
[cf7-font-bold class:cf7-btn-style text:"B"]
[cf7-font-italic class:cf7-btn-style text:"I"]
Align:
[cf7-align-left class:cf7-btn-style text:"⬅"]
[cf7-align-center class:cf7-btn-style text:"⬌"]
[cf7-align-right class:cf7-btn-style text:"➡"]
[cf7-align-justify class:cf7-btn-style text:"≡"]
Color:
[cf7-font-color class:cf7-color-picker value:"#000000"]
Shadow:
[cf7-text-shadow-toggle class:cf7-btn-shadow text:"S"]
[cf7-shadow-color class:cf7-color-picker value:"#000000"]
[cf7-shadow-blur class:cf7-input-shadow-blur value:"2"]
[cf7-shadow-offset-x class:cf7-input-shadow-offset value:"2"]
[cf7-shadow-offset-y class:cf7-input-shadow-offset value:"2"]
Opacity:
[cf7-shadow-opacity-slider class:cf7-range-slider min:0 max:100 value:100]
100%
Ready to Order:
Proceed to Payment
Choose Background Image
Select a category and image for your billboard background
×
Step 1: Select Category
Step 2: Select Image
← Back to Categories
Cancel
Apply Background
📋 Review Your Billboard Order
Review your order details and agree to terms before proceeding to payment
×
📊 Order Summary
Billboard Location:
Loading...
Run Dates:
Loading...
Duration:
Loading...
Total Cost:
$0.00
📋 Agreement & Requirements
I agree to the terms and conditions
Click here to read full terms
I have NOT used photos of people smoking, drinking, hand symbols, showing too much skin.
My ad is not a business ad or promotional ad in any way.
The design above is exactly what my ad will look like. If it has to be changed, I will be charged $25.
If your ad does not comply it will not play and you will not be refunded.
Email me a copy of this ad
Cancel
Proceed to Payment
/* WordPress Contact Form 7 Override Styles */
.wpcf7-form * {
box-sizing: border-box !important;
}
/* Reset WordPress theme interference */
.wpcf7 input[type="button"],
.wpcf7 input[type="submit"],
.wpcf7 button,
.wpcf7 select {
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
background-image: none !important;
box-shadow: none !important;
text-shadow: none !important;
border-style: solid !important;
outline: none !important;
font-family: inherit !important;
line-height: normal !important;
}
/* Ensure our container styles override theme styles */
.wpcf7 .cf7-text-editor-container,
.cf7-text-editor-container {
max-width: 100% !important;
margin: 20px auto !important;
padding: 20px !important;
border: 2px solid #ddd !important;
border-radius: 8px !important;
background: #f9f9f9 !important;
font-family: Arial, sans-serif !important;
box-sizing: border-box !important;
}
.cf7-canvas {
position: relative;
border: 2px solid #333;
background: white;
margin: 0 auto 20px auto;
overflow: hidden;
cursor: crosshair;
/* Billboard aspect ratio 2:1 (20 wide x 10 tall feet) */
aspect-ratio: 2 / 1;
max-width: 800px;
width: 100%;
height: auto;
}
.cf7-canvas-background {
width: 100%;
height: 100%;
background: white;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
position: absolute;
top: 0;
left: 0;
z-index: 1;
transition: background-image 0.3s ease;
}
.cf7-elements-container {
position: relative;
width: 100%;
height: 100%;
z-index: 2;
}
/* CF7 Style Draggable Elements */
.cf7-draggable-text {
position: absolute;
cursor: move;
border: 2px solid transparent; /* Consistent border width prevents jumping */
background: transparent; /* Clean default - no background */
min-width: 50px;
min-height: 20px;
font-size: 16px;
font-family: Arial, sans-serif;
z-index: 10;
overflow: visible;
display: flex;
flex-direction: column;
box-sizing: border-box; /* Ensures border is included in element size */
}
/* Editable Content Area */
.cf7-editable-content {
flex: 1;
padding: 5px;
outline: none;
border: none;
background: transparent;
resize: none;
overflow: hidden;
word-wrap: break-word;
min-height: 20px;
width: 100%;
box-sizing: border-box;
font-size: inherit;
font-family: inherit;
line-height: 1.4;
}
.cf7-editable-content:focus {
background: rgba(255, 255, 255, 0.95);
box-shadow: inset 0 0 3px rgba(0, 123, 255, 0.3);
border-radius: 2px;
}
/* Empty state placeholder */
.cf7-editable-content.cf7-empty:before {
content: attr(data-placeholder);
color: #999;
font-style: italic;
pointer-events: none;
opacity: 0.7;
}
/* Editing state */
.cf7-draggable-text.cf7-editing {
border: 2px dashed #007cba !important; /* Clear editing border */
background: rgba(0, 124, 186, 0.15) !important; /* Editing background */
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 124, 186, 0.3);
cursor: text; /* CF7 Pattern: Visual feedback for editing mode */
}
.cf7-draggable-text.cf7-editing .cf7-delete-btn,
.cf7-draggable-text.cf7-editing .cf7-resize-handle {
opacity: 0.8;
pointer-events: auto;
}
/* CF7 Pattern: Dragging state visual feedback */
.cf7-draggable-text:not(.cf7-editing) {
cursor: move;
}
.cf7-draggable-text:not(.cf7-editing) .cf7-editable-content {
cursor: move;
pointer-events: none; /* Prevent text selection during drag */
}
.cf7-draggable-text.cf7-editing .cf7-editable-content {
pointer-events: auto; /* Re-enable text interaction in edit mode */
cursor: text;
}
/* CF7 Pattern: Hover states for better UX */
.cf7-draggable-text:not(.cf7-editing):hover .cf7-editable-content {
background: rgba(255, 255, 255, 0.8);
}
.cf7-draggable-text:not(.cf7-editing):not(.cf7-selected):hover::after {
content: "Double-click to edit";
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
white-space: nowrap;
pointer-events: none;
z-index: 1001;
}
.cf7-draggable-text:hover {
border: 2px dashed #005a87; /* Consistent width prevents jumping */
background: rgba(0, 124, 186, 0.1); /* Light background on hover */
}
.cf7-draggable-text.cf7-selected {
border: 2px solid #ff6900 !important; /* Strong border when selected */
background: rgba(255, 105, 0, 0.1) !important; /* Clear selection background */
}
/* Resize handles for text elements */
.cf7-resize-handle {
position: absolute;
background: #ff6900;
border: 1px solid #fff;
width: 8px;
height: 8px;
z-index: 20;
display: none;
}
.cf7-draggable-text.cf7-selected .cf7-resize-handle,
.cf7-draggable-image.cf7-selected .cf7-resize-handle {
display: block;
}
.cf7-resize-handle.cf7-resize-se {
bottom: -4px;
right: -4px;
cursor: se-resize;
}
.cf7-resize-handle.cf7-resize-sw {
bottom: -4px;
left: -4px;
cursor: sw-resize;
}
.cf7-resize-handle.cf7-resize-ne {
top: -4px;
right: -4px;
cursor: ne-resize;
}
.cf7-resize-handle.cf7-resize-nw {
top: -4px;
left: -4px;
cursor: nw-resize;
}
/* CF7 Style Image Elements - Clean default appearance */
.cf7-draggable-image {
position: absolute;
cursor: move;
border: 2px solid transparent; /* Consistent border width prevents jumping */
background: transparent; /* Clean default - no background */
min-width: 50px;
min-height: 50px;
user-select: none;
z-index: 10;
overflow: visible;
box-sizing: border-box; /* Ensures border is included in element size */
}
.cf7-draggable-image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
pointer-events: none;
}
.cf7-draggable-image:hover {
border: 2px dashed #005a87; /* Consistent width prevents jumping */
background: rgba(0, 124, 186, 0.1); /* Light background on hover */
}
.cf7-draggable-image.cf7-selected {
border: 2px solid #ff6900 !important; /* Strong border when selected */
background: rgba(255, 105, 0, 0.1) !important; /* Clear selection background */
}
.cf7-draggable-image:not(.cf7-selected):hover::after {
content: "Click to select";
position: absolute;
bottom: -20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
z-index: 1001;
}
/* CF7 Style Toolbar - WordPress Override */
.wpcf7 .cf7-toolbar,
.cf7-toolbar {
display: flex !important;
gap: 12px !important;
flex-wrap: wrap !important;
align-items: center !important;
justify-content: flex-start !important;
padding: 16px 20px !important;
background: white !important;
border: 1px solid #ddd !important;
border-radius: 4px !important;
min-height: 60px !important;
box-sizing: border-box !important;
width: 100% !important;
margin: 0 !important;
}
/* Font Controls - WordPress Override */
.wpcf7 .cf7-font-controls,
.cf7-font-controls {
display: flex !important;
gap: 12px !important;
align-items: flex-start !important;
justify-content: flex-start !important;
padding: 12px 16px !important;
background: #f5f5f5 !important;
border: 1px solid #ddd !important;
border-radius: 4px !important;
flex-wrap: wrap !important;
min-height: 60px !important;
box-sizing: border-box !important;
width: 100% !important;
margin: 0 !important;
}
/* Control Groups */
.wpcf7 .cf7-control-group,
.cf7-control-group {
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
gap: 4px !important;
min-width: fit-content !important;
box-sizing: border-box !important;
}
/* Style Buttons Container */
.wpcf7 .cf7-style-buttons,
.cf7-style-buttons {
display: flex !important;
gap: 4px !important;
align-items: center !important;
}
/* Shadow Controls Container */
.wpcf7 .cf7-shadow-controls,
.cf7-shadow-controls {
display: flex !important;
gap: 6px !important;
align-items: center !important;
flex-wrap: wrap !important;
}
/* Control Labels - WordPress Override */
.wpcf7 .cf7-control-label,
.cf7-control-label {
font-size: 11px !important;
font-weight: bold !important;
color: #555 !important;
margin: 0 0 2px 0 !important;
white-space: nowrap !important;
display: block !important;
min-width: fit-content !important;
line-height: 1.2 !important;
padding: 0 !important;
text-align: left !important;
}
/* Select Font - WordPress Override */
.wpcf7 .cf7-select-font,
.cf7-select-font {
padding: 6px 10px !important;
border: 1px solid #ccc !important;
border-radius: 3px !important;
font-size: 12px !important;
background: white !important;
background-color: white !important;
min-width: 130px !important;
height: 32px !important;
box-sizing: border-box !important;
display: inline-flex !important;
align-items: center !important;
margin: 0 2px !important;
vertical-align: top !important;
line-height: 1.2 !important;
}
/* Input Size - WordPress Override */
.wpcf7 .cf7-input-size,
.cf7-input-size {
padding: 6px 8px !important;
border: 1px solid #ccc !important;
border-radius: 3px !important;
font-size: 12px !important;
width: 55px !important;
text-align: center !important;
height: 32px !important;
box-sizing: border-box !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
margin: 0 2px !important;
vertical-align: top !important;
line-height: 1.2 !important;
background: white !important;
background-color: white !important;
}
/* Style Buttons - WordPress Override */
.wpcf7 .cf7-btn-style,
.cf7-btn-style {
padding: 6px 10px !important;
border: 1px solid #007cba !important;
background: white !important;
background-color: white !important;
color: #007cba !important;
border-radius: 3px !important;
cursor: pointer !important;
font-size: 12px !important;
font-weight: bold !important;
min-width: 28px !important;
text-align: center !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
height: 32px !important;
box-sizing: border-box !important;
margin: 0 2px !important;
vertical-align: top !important;
line-height: 1 !important;
}
.cf7-btn-style:hover {
background: #007cba;
color: white;
}
/* Active state for style buttons - Enhanced specificity */
.wpcf7 .cf7-btn-style.cf7-active,
.cf7-btn-style.cf7-active {
background: #007cba !important;
background-color: #007cba !important;
color: white !important;
border-color: #007cba !important;
}
/* Unicode alignment icons styling */
.cf7-btn-style {
font-family: Arial, sans-serif !important;
font-size: 14px !important;
line-height: 1 !important;
text-align: center !important;
}
/* Specific styling for alignment buttons with Unicode symbols */
.cf7-style-buttons .cf7-btn-style {
min-width: 32px !important;
height: 32px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
font-weight: normal !important;
}
/* Color Picker - WordPress Override */
.wpcf7 input[type="color"].cf7-color-picker,
input[type="color"].cf7-color-picker,
.cf7-color-picker {
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
padding: 0 !important;
border: 2px solid #ccc !important;
border-radius: 4px !important;
background: transparent !important;
width: 42px !important;
height: 32px !important;
cursor: pointer !important;
vertical-align: middle !important;
display: inline-block !important;
box-sizing: border-box !important;
outline: none !important;
margin: 0 2px !important;
min-width: 42px !important;
max-width: 42px !important;
min-height: 32px !important;
max-height: 32px !important;
}
/* Color picker webkit specific fixes */
.wpcf7 input[type="color"].cf7-color-picker::-webkit-color-swatch-wrapper,
input[type="color"].cf7-color-picker::-webkit-color-swatch-wrapper {
padding: 0 !important;
border: none !important;
border-radius: 2px !important;
}
.wpcf7 input[type="color"].cf7-color-picker::-webkit-color-swatch,
input[type="color"].cf7-color-picker::-webkit-color-swatch {
border: none !important;
border-radius: 2px !important;
margin: 0 !important;
padding: 0 !important;
}
/* Firefox color picker fixes */
.wpcf7 input[type="color"].cf7-color-picker::-moz-color-swatch,
input[type="color"].cf7-color-picker::-moz-color-swatch {
border: none !important;
border-radius: 2px !important;
}
/* Color Picker Hover/Focus - WordPress Override */
.wpcf7 input[type="color"].cf7-color-picker:hover,
input[type="color"].cf7-color-picker:hover,
.cf7-color-picker:hover {
border-color: #007cba !important;
box-shadow: 0 0 3px rgba(0, 124, 186, 0.3) !important;
}
.wpcf7 input[type="color"].cf7-color-picker:focus,
input[type="color"].cf7-color-picker:focus,
.cf7-color-picker:focus {
outline: none !important;
border-color: #007cba !important;
box-shadow: 0 0 5px rgba(0, 124, 186, 0.5) !important;
}
/* Text Shadow Controls */
.cf7-btn-shadow {
padding: 6px 10px;
border: 1px solid #007cba;
background: white;
color: #007cba;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
font-weight: bold;
min-width: 28px;
text-align: center;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 32px;
box-sizing: border-box;
}
.cf7-btn-shadow:hover {
background: #007cba;
color: white;
}
.cf7-btn-shadow.cf7-active {
background: #007cba;
color: white;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.cf7-input-shadow-blur,
.cf7-input-shadow-offset {
padding: 6px 8px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 12px;
width: 45px;
text-align: center;
height: 32px;
box-sizing: border-box;
display: inline-flex;
align-items: center;
}
.cf7-input-shadow-blur:focus,
.cf7-input-shadow-offset:focus {
outline: none;
border-color: #007cba;
box-shadow: 0 0 3px rgba(0, 124, 186, 0.3);
}
/* CF7 Range Slider Styling - WordPress Override */
.wpcf7 .cf7-slider-container,
.cf7-slider-container {
display: flex !important;
align-items: center !important;
margin: 0 !important;
gap: 6px !important;
min-width: 120px !important;
box-sizing: border-box !important;
}
.cf7-slider-label {
font-size: 12px;
font-weight: 500;
color: #333;
min-width: 60px;
text-align: left;
}
/* CF7 Disabled Controls - Following WordPress Admin Patterns */
.cf7-btn-style:disabled,
.cf7-btn-shadow:disabled,
.cf7-btn-style.cf7-disabled,
.cf7-btn-shadow.cf7-disabled {
background: #f6f7f7 !important;
border-color: #ddd !important;
color: #a0a5aa !important;
cursor: not-allowed !important;
opacity: 0.6;
pointer-events: none;
}
.cf7-input-size:disabled,
.cf7-input-shadow-blur:disabled,
.cf7-input-shadow-offset:disabled,
.cf7-input-size.cf7-disabled,
.cf7-input-shadow-blur.cf7-disabled,
.cf7-input-shadow-offset.cf7-disabled {
background: #f6f7f7 !important;
border-color: #ddd !important;
color: #a0a5aa !important;
cursor: not-allowed !important;
opacity: 0.6;
pointer-events: none;
}
.cf7-color-picker:disabled,
.cf7-color-picker.cf7-disabled {
opacity: 0.6;
cursor: not-allowed !important;
pointer-events: none;
}
.cf7-range-slider:disabled,
.cf7-range-slider.cf7-disabled {
opacity: 0.6;
cursor: not-allowed !important;
pointer-events: none;
}
.cf7-range-slider:disabled::-webkit-slider-thumb,
.cf7-range-slider.cf7-disabled::-webkit-slider-thumb {
background: #a0a5aa !important;
cursor: not-allowed !important;
}
.cf7-range-slider:disabled::-moz-range-thumb,
.cf7-range-slider.cf7-disabled::-moz-range-thumb {
background: #a0a5aa !important;
cursor: not-allowed !important;
}
.cf7-select-font:disabled,
.cf7-select-font.cf7-disabled {
background: #f6f7f7 !important;
border-color: #ddd !important;
color: #a0a5aa !important;
cursor: not-allowed !important;
opacity: 0.6;
pointer-events: none;
}
.cf7-range-slider {
-webkit-appearance: none;
appearance: none;
height: 6px;
background: #ddd;
border-radius: 3px;
outline: none;
flex: 1;
margin: 0 8px;
cursor: pointer;
transition: background 0.3s ease;
}
.cf7-range-slider:hover {
background: #bbb;
}
.cf7-range-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
background: #007cba;
border-radius: 50%;
cursor: pointer;
border: 2px solid #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.cf7-range-slider::-webkit-slider-thumb:hover {
background: #005a87;
transform: scale(1.1);
}
.cf7-range-slider::-moz-range-thumb {
width: 18px;
height: 18px;
background: #007cba;
border-radius: 50%;
cursor: pointer;
border: 2px solid #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
.cf7-range-slider::-moz-range-thumb:hover {
background: #005a87;
transform: scale(1.1);
}
.cf7-range-slider::-moz-range-track {
height: 6px;
background: #ddd;
border-radius: 3px;
border: none;
}
.cf7-slider-value {
font-size: 11px;
color: #666;
font-weight: 500;
min-width: 35px;
text-align: right;
background: #f9f9f9;
padding: 2px 6px;
border-radius: 3px;
border: 1px solid #e0e0e0;
}
/* Text Shadow Preview in Draggable Text */
.cf7-draggable-text.cf7-has-shadow {
/* Text shadow will be applied dynamically via JavaScript */
}
/* Export Controls - WordPress Override */
.wpcf7 .cf7-export-controls,
.cf7-export-controls {
display: flex !important;
gap: 8px !important;
align-items: center !important;
padding: 8px !important;
background: #f0f8ff !important;
border: 1px solid #00a32a !important;
border-radius: 4px !important;
flex-wrap: wrap !important;
margin-bottom: 10px !important;
box-sizing: border-box !important;
width: 100% !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
.cf7-export-options {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.cf7-select-quality,
.cf7-select-format {
padding: 5px 8px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 12px;
background: white;
min-width: 120px;
}
.cf7-select-quality:focus,
.cf7-select-format:focus {
outline: none;
border-color: #00a32a;
box-shadow: 0 0 3px rgba(0, 163, 42, 0.3);
}
/* Background Controls */
.cf7-background-controls {
display: flex;
gap: 8px;
align-items: center;
padding: 8px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.cf7-select-category,
.cf7-select-template {
padding: 5px 8px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 12px;
background: white;
min-width: 120px;
}
.cf7-btn-clear-bg {
padding: 5px 10px;
border: 1px solid #dc3232;
background: #dc3232;
color: white;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
text-decoration: none;
display: inline-block;
}
.cf7-btn-clear-bg:hover {
background: #a02622;
border-color: #a02622;
}
/* CF7 Style Buttons - WordPress Override */
.wpcf7 .cf7-btn-text,
.wpcf7 .cf7-btn-image,
.wpcf7 .cf7-btn-export,
.wpcf7 .cf7-btn-clear,
.cf7-btn-text,
.cf7-btn-image,
.cf7-btn-export,
.cf7-btn-clear {
padding: 12px 18px !important;
border: 1px solid #007cba !important;
background: #007cba !important;
background-color: #007cba !important;
color: white !important;
border-radius: 4px !important;
cursor: pointer !important;
font-size: 14px !important;
font-weight: bold !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
transition: all 0.3s ease !important;
min-height: 44px !important;
box-sizing: border-box !important;
white-space: nowrap !important;
margin: 2px !important;
vertical-align: top !important;
line-height: 1.2 !important;
}
.cf7-btn-text:hover,
.cf7-btn-image:hover,
.cf7-btn-export:hover,
.cf7-btn-clear:hover {
background: #005a87;
border-color: #005a87;
}
.cf7-btn-export {
background: #00a32a;
border-color: #00a32a;
}
.cf7-btn-export:hover {
background: #007a1f;
border-color: #007a1f;
}
.cf7-btn-clear {
background: #dc3232;
border-color: #dc3232;
}
.cf7-btn-clear:hover {
background: #a02622;
border-color: #a02622;
}
/* CF7 Style File Input */
input[type="file"].cf7-file-input {
display: none;
}
/* Delete button for elements - Enhanced with research-based improvements */
.cf7-delete-btn {
position: absolute;
top: -11px; /* Moved slightly further out */
right: -11px; /* Moved slightly further out */
width: 22px;
height: 22px;
background: #dc3232;
color: white;
border: 2px solid white;
border-radius: 50%;
cursor: pointer;
font-size: 14px;
font-weight: bold;
display: none;
z-index: 999; /* Much higher z-index to ensure it's always on top */
line-height: 1;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
transition: all 0.2s ease;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
/* Force new stacking context to ensure proper layering */
transform: translateZ(0);
will-change: transform;
}
.cf7-delete-btn:hover {
background: #a02622;
transform: scale(1.1);
box-shadow: 0 3px 6px rgba(0,0,0,0.4);
}
.cf7-delete-btn:active {
transform: scale(0.95);
box-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.cf7-draggable-text.cf7-selected .cf7-delete-btn,
.cf7-draggable-image.cf7-selected .cf7-delete-btn {
display: flex;
align-items: center;
justify-content: center;
}
/* Responsive */
@media (max-width: 768px) {
.cf7-toolbar {
flex-direction: column;
align-items: stretch;
gap: 8px;
padding: 12px 16px;
}
.cf7-btn-text,
.cf7-btn-image,
.cf7-btn-export,
.cf7-btn-clear {
width: 100%;
text-align: center;
margin: 1px 0;
min-height: 48px;
}
/* Billboard canvas responsive adjustments */
.cf7-canvas {
max-width: 600px; /* Maintain 2:1 aspect ratio on mobile */
}
.cf7-background-controls,
.cf7-export-controls,
.cf7-font-controls {
flex-direction: column;
align-items: stretch;
gap: 8px;
padding: 10px 12px;
}
/* Mobile responsive for control groups */
.cf7-control-group {
flex-direction: row !important;
align-items: center !important;
gap: 8px !important;
margin-bottom: 8px;
}
.cf7-control-label {
min-width: 50px !important;
margin-bottom: 0 !important;
}
.cf7-style-buttons,
.cf7-shadow-controls {
gap: 4px !important;
}
.cf7-slider-container {
min-width: 100px !important;
}
.cf7-select-category,
.cf7-select-template,
.cf7-select-quality,
.cf7-select-format {
width: 100%;
margin-bottom: 5px;
}
.cf7-export-options {
flex-direction: column;
align-items: stretch;
gap: 5px;
}
/* WordPress specific mobile overrides */
.wpcf7 .cf7-btn-text,
.wpcf7 .cf7-btn-image,
.wpcf7 .cf7-btn-export,
.wpcf7 .cf7-btn-clear {
width: 100% !important;
margin: 2px 0 !important;
}
.wpcf7 .cf7-control-label {
margin-bottom: 5px !important;
}
}
/* Export Mode - Clean styling for export without visual indicators */
.cf7-export-mode {
border: none !important;
background: transparent !important;
cursor: default !important;
outline: none !important;
box-shadow: none !important;
}
.cf7-export-mode:hover {
border: none !important;
background: transparent !important;
cursor: default !important;
outline: none !important;
box-shadow: none !important;
}
.cf7-export-mode::after {
display: none !important;
}
.cf7-export-mode .cf7-delete-btn,
.cf7-export-mode .cf7-resize-handle {
display: none !important;
}
/* Print Media Queries - Ensure backgrounds are preserved */
@media print {
.cf7-canvas-background {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
print-color-adjust: exact !important;
}
}
/* Additional CSS to ensure background images are properly rendered */
.cf7-canvas-background {
-webkit-print-color-adjust: exact;
color-adjust: exact;
print-color-adjust: exact;
}
/* Checkout Dialog now uses Background Modal structure - no custom styles needed */
/* Checkout Content Styles within Modal */
#checkoutDialog .order-summary {
background: #f9f9f9;
padding: 16px;
border-radius: 4px;
margin-bottom: 16px;
border: 1px solid #ddd;
}
#checkoutDialog .order-summary h3 {
margin: 0 0 12px 0;
color: #23282d;
font-size: 16px;
font-weight: 600;
font-family: Arial, sans-serif;
}
#checkoutDialog .summary-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #e1e1e1;
font-size: 14px;
}
#checkoutDialog .summary-row:last-child {
border-bottom: none;
font-weight: 600;
font-size: 16px;
color: #007cba;
margin-top: 8px;
padding-top: 12px;
border-top: 2px solid #e1e1e1;
}
#checkoutDialog .summary-label {
font-weight: 500;
color: #555;
}
#checkoutDialog .summary-value {
font-weight: 600;
color: #23282d;
}
#checkoutDialog .checkbox-section {
background: #f8f9fa;
padding: 16px;
border-radius: 4px;
border: 1px solid #ddd;
}
#checkoutDialog .checkbox-section h3 {
margin: 0 0 12px 0;
color: #23282d;
font-size: 16px;
font-weight: 600;
font-family: Arial, sans-serif;
}
#checkoutDialog .checkbox-item {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
gap: 8px;
}
#checkoutDialog .checkbox-item:last-child {
margin-bottom: 0;
}
#checkoutDialog .checkbox-item input[type="checkbox"] {
margin-top: 2px;
flex-shrink: 0;
width: 16px;
height: 16px;
}
#checkoutDialog .checkbox-text {
font-size: 14px;
line-height: 1.4;
color: #23282d;
cursor: pointer;
font-family: Arial, sans-serif;
}
#checkoutDialog .terms-link {
color: #007cba !important;
text-decoration: underline !important;
cursor: pointer !important;
font-weight: 500 !important;
}
#checkoutDialog .terms-link:hover {
color: #005a87 !important;
}
/* Old styles removed - now using background modal structure which already has WordPress compatibility */
/* Fix modal sizing and centering for checkout dialog */
#checkoutDialog {
max-width: 90vw !important;
max-height: 90vh !important;
width: auto !important;
height: auto !important;
margin: auto !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
position: fixed !important;
}
/* Ensure modal content has proper max width */
#checkoutDialog .cf7-modal-content {
max-width: 800px !important;
width: 100% !important;
margin: 0 !important;
}
/* Responsive modal sizing */
@media (max-width: 768px) {
#checkoutDialog {
max-width: 95vw !important;
max-height: 95vh !important;
margin: 10px !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
#checkoutDialog .cf7-modal-content {
max-width: 100% !important;
margin: 0 !important;
}
#checkoutDialog .cf7-modal-body {
max-height: 60vh !important;
overflow-y: auto !important;
}
}
/* WordPress Contact Form 7 Final Override - Ensure proper layout */
.wpcf7-form .cf7-text-editor-container {
all: initial !important;
font-family: Arial, sans-serif !important;
max-width: 100% !important;
margin: 20px auto !important;
padding: 20px !important;
border: 2px solid #ddd !important;
border-radius: 8px !important;
background: #f9f9f9 !important;
box-sizing: border-box !important;
display: block !important;
}
.wpcf7-form .cf7-text-editor-container * {
box-sizing: border-box !important;
}
/* Ensure buttons maintain proper spacing in WordPress */
.wpcf7 .cf7-toolbar > *,
.wpcf7 .cf7-font-controls > *,
.wpcf7 .cf7-export-controls > * {
margin: 2px !important;
flex-shrink: 0 !important;
}
/* Modal Fallback for WordPress - Force positioning */
.wpcf7 dialog.cf7-background-modal,
dialog.cf7-background-modal {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 999999 !important;
margin: 0 !important;
inset: auto !important;
}
/* Ensure modal shows above WordPress admin bar and other elements */
.wpcf7 .cf7-background-modal[open],
.cf7-background-modal[open] {
display: flex !important;
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 999999 !important;
visibility: visible !important;
opacity: 1 !important;
}
/* WordPress Modal Content Layout Fix */
.wpcf7 .cf7-background-modal .cf7-modal-content,
.cf7-background-modal .cf7-modal-content {
width: 100% !important;
height: 100% !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
}
/* Modal Steps - WordPress Override with proper step switching */
.wpcf7 .cf7-modal-step,
.cf7-modal-step {
width: 100% !important;
height: auto !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
box-sizing: border-box !important;
}
/* Step 1 - Category Selection (default visible) */
.wpcf7 .cf7-step-category,
.cf7-step-category {
display: block !important;
}
/* Step 2 - Template Selection (default hidden) */
.wpcf7 .cf7-step-template,
.cf7-step-template {
display: none !important;
}
/* When step is explicitly shown */
.wpcf7 .cf7-modal-step[style*="display: block"],
.cf7-modal-step[style*="display: block"] {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* When step is explicitly hidden */
.wpcf7 .cf7-modal-step[style*="display: none"],
.cf7-modal-step[style*="display: none"] {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
/* Fix for WordPress theme interference */
.wpcf7 .cf7-background-modal * {
box-sizing: border-box !important;
}
/* WordPress Range Slider Fixes */
.wpcf7 input[type="range"].cf7-range-slider,
input[type="range"].cf7-range-slider {
-webkit-appearance: none !important;
appearance: none !important;
width: 100% !important;
height: 6px !important;
border-radius: 3px !important;
background: #ddd !important;
outline: none !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
box-shadow: none !important;
}
/* Range slider thumb */
.wpcf7 input[type="range"].cf7-range-slider::-webkit-slider-thumb,
input[type="range"].cf7-range-slider::-webkit-slider-thumb {
-webkit-appearance: none !important;
appearance: none !important;
width: 18px !important;
height: 18px !important;
border-radius: 50% !important;
background: #007cba !important;
cursor: pointer !important;
border: none !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
}
.wpcf7 input[type="range"].cf7-range-slider::-moz-range-thumb,
input[type="range"].cf7-range-slider::-moz-range-thumb {
width: 18px !important;
height: 18px !important;
border-radius: 50% !important;
background: #007cba !important;
cursor: pointer !important;
border: none !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
}
/* CF7 Modal Styles - WordPress Compatible */
.wpcf7 .cf7-background-modal,
.cf7-background-modal {
border: none !important;
border-radius: 8px !important;
padding: 0 !important;
max-width: 90vw !important;
max-height: 90vh !important;
width: 800px !important;
background: white !important;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3) !important;
overflow: hidden !important;
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 999999 !important;
margin: 0 !important;
inset: auto !important;
}
/* Modal Backdrop - WordPress Override */
.wpcf7 .cf7-background-modal::backdrop,
.cf7-background-modal::backdrop {
background: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(2px) !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
z-index: 999998 !important;
}
/* Modal Content - WordPress Override */
.wpcf7 .cf7-modal-content,
.cf7-modal-content {
display: flex !important;
flex-direction: column !important;
height: 100% !important;
max-height: 80vh !important;
min-height: 400px !important;
width: 100% !important;
box-sizing: border-box !important;
overflow: hidden !important;
}
.cf7-modal-header {
position: relative;
padding: 20px;
border-bottom: 1px solid #ddd;
background: #f8f9fa;
}
.cf7-modal-title {
margin: 0 0 5px 0;
font-size: 1.5em;
color: #333;
font-weight: 600;
}
.cf7-modal-description {
margin: 0;
color: #666;
font-size: 0.9em;
}
.cf7-modal-close {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
padding: 5px;
border-radius: 4px;
color: #666;
transition: all 0.2s ease;
}
.cf7-modal-close:hover,
.cf7-modal-close:focus {
background: #e9ecef;
color: #333;
outline: 2px solid #007cba;
outline-offset: 2px;
}
/* Modal Body - WordPress Override */
.wpcf7 .cf7-modal-body,
.cf7-modal-body {
flex: 1 !important;
padding: 20px !important;
overflow-y: auto !important;
overflow-x: hidden !important;
max-height: calc(80vh - 140px) !important;
min-height: 300px !important;
box-sizing: border-box !important;
width: 100% !important;
}
.cf7-modal-step {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
.cf7-step-title {
margin: 0 0 20px 0;
font-size: 1.2em;
color: #333;
font-weight: 500;
}
.cf7-back-btn {
background: #f8f9fa;
border: 1px solid #ddd;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-bottom: 20px;
font-size: 0.9em;
color: #666;
transition: all 0.2s ease;
}
.cf7-back-btn:hover,
.cf7-back-btn:focus {
background: #e9ecef;
border-color: #007cba;
color: #333;
outline: 2px solid #007cba;
outline-offset: 2px;
}
/* Category and Template Grid - WordPress Override */
.wpcf7 .cf7-category-grid,
.wpcf7 .cf7-template-grid,
.cf7-category-grid,
.cf7-template-grid {
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important;
gap: 15px !important;
margin-top: 10px !important;
width: 100% !important;
box-sizing: border-box !important;
padding: 0 !important;
}
/* Category and Template Items - WordPress Override */
.wpcf7 .cf7-category-item,
.wpcf7 .cf7-template-item,
.cf7-category-item,
.cf7-template-item {
position: relative !important;
border: 2px solid #ddd !important;
border-radius: 8px !important;
padding: 15px !important;
cursor: pointer !important;
transition: all 0.2s ease !important;
background: white !important;
text-align: center !important;
box-sizing: border-box !important;
width: 100% !important;
min-height: 80px !important;
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
}
.cf7-category-item:hover,
.cf7-template-item:hover {
border-color: #007cba;
box-shadow: 0 2px 8px rgba(0, 124, 186, 0.2);
}
.cf7-category-item:focus,
.cf7-template-item:focus {
outline: 2px solid #007cba;
outline-offset: 2px;
border-color: #007cba;
}
.cf7-category-item.selected,
.cf7-template-item.selected {
border-color: #007cba;
background: #f0f8ff;
box-shadow: 0 2px 8px rgba(0, 124, 186, 0.3);
}
.cf7-category-item h4,
.cf7-template-item h4 {
margin: 0 0 5px 0;
font-size: 1.1em;
color: #333;
}
.cf7-template-item img {
width: 100%;
height: 120px;
object-fit: cover;
border-radius: 4px;
margin-bottom: 10px;
}
/* Modal Footer - WordPress Override */
.wpcf7 .cf7-modal-footer,
.cf7-modal-footer {
padding: 20px !important;
border-top: 1px solid #ddd !important;
background: #f8f9fa !important;
display: flex !important;
justify-content: flex-end !important;
gap: 10px !important;
flex-shrink: 0 !important;
width: 100% !important;
box-sizing: border-box !important;
min-height: 70px !important;
align-items: center !important;
}
.cf7-btn-cancel,
.cf7-btn-apply {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: all 0.2s ease;
}
.cf7-btn-cancel {
background: #6c757d;
color: white;
}
.cf7-btn-cancel:hover,
.cf7-btn-cancel:focus {
background: #5a6268;
outline: 2px solid #007cba;
outline-offset: 2px;
}
.cf7-btn-apply {
background: #007cba;
color: white;
}
.cf7-btn-apply:hover:not(:disabled),
.cf7-btn-apply:focus:not(:disabled) {
background: #005a87;
outline: 2px solid #007cba;
outline-offset: 2px;
}
.cf7-btn-apply:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
.cf7-btn-background-modal {
background: #007cba;
color: white;
border: none;
padding: 10px 18px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: all 0.2s ease;
margin-right: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 40px;
box-sizing: border-box;
white-space: nowrap;
}
.cf7-btn-background-modal:hover,
.cf7-btn-background-modal:focus {
background: #005a87;
outline: 2px solid #007cba;
outline-offset: 2px;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.cf7-background-modal {
width: 95vw;
max-height: 95vh;
}
.cf7-category-grid,
.cf7-template-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
}
.cf7-modal-header,
.cf7-modal-body,
.cf7-modal-footer {
padding: 15px;
}
.cf7-modal-footer {
flex-direction: column;
}
.cf7-btn-cancel,
.cf7-btn-apply {
width: 100%;
}
}
/* CF7 Font Preview Dropdown Styles */
.cf7-font-preview-dropdown {
position: relative;
display: inline-block;
min-width: 180px;
}
.cf7-font-preview-button {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 5px 8px;
border: 1px solid #ccc;
border-radius: 3px;
background: white;
cursor: pointer;
font-size: 12px;
text-align: left;
transition: all 0.2s ease;
}
.cf7-font-preview-button:hover {
border-color: #007cba;
box-shadow: 0 0 3px rgba(0, 124, 186, 0.3);
}
.cf7-font-preview-button:focus {
outline: none;
border-color: #007cba;
box-shadow: 0 0 3px rgba(0, 124, 186, 0.3);
}
.cf7-selected-font {
flex: 1;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cf7-dropdown-arrow {
margin-left: 8px;
font-size: 10px;
color: #666;
transition: transform 0.2s ease;
}
.cf7-font-preview-button[aria-expanded="true"] .cf7-dropdown-arrow {
transform: rotate(180deg);
}
.cf7-font-preview-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
max-height: 200px;
overflow-y: auto;
z-index: 1000;
margin: 2px 0 0 0;
padding: 0;
list-style: none;
}
.cf7-font-preview-list.cf7-hidden {
display: none;
}
.cf7-font-preview-option {
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cf7-font-preview-option:last-child {
border-bottom: none;
}
.cf7-font-preview-option:hover {
background-color: #f8f9fa;
}
.cf7-font-preview-option.cf7-selected {
background-color: #e7f3ff;
color: #0073aa;
font-weight: 500;
}
.cf7-font-preview-option.cf7-selected::after {
content: " ✓";
float: right;
color: #0073aa;
font-weight: bold;
}
/* Custom scrollbar for font dropdown */
.cf7-font-preview-list::-webkit-scrollbar {
width: 6px;
}
.cf7-font-preview-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.cf7-font-preview-list::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
.cf7-font-preview-list::-webkit-scrollbar-thumb:hover {
background: #999;
}
/* Disabled state for font preview dropdown */
.cf7-font-preview-dropdown.cf7-disabled .cf7-font-preview-button {
background: #f6f7f7 !important;
border-color: #ddd !important;
color: #a0a5aa !important;
cursor: not-allowed !important;
opacity: 0.6;
pointer-events: none;
}
.cf7-font-preview-dropdown.cf7-disabled .cf7-selected-font {
color: #a0a5aa !important;
}
.cf7-font-preview-dropdown.cf7-disabled .cf7-dropdown-arrow {
color: #a0a5aa !important;
}
/* Responsive adjustments for font preview dropdown */
@media (max-width: 768px) {
.cf7-font-preview-dropdown {
min-width: 150px;
}
.cf7-font-preview-option {
padding: 10px 12px;
font-size: 13px;
}
}
// CF7 Text Editor Class - Following CF7 patterns
class CF7TextEditor {
constructor(container) {
this.container = container;
this.canvas = container.querySelector('#cf7-canvas');
this.elementsContainer = container.querySelector('#cf7-elements');
this.selectedElement = null;
this.dragData = { isDragging: false, startX: 0, startY: 0, elementX: 0, elementY: 0 };
this.resizeData = {
isResizing: false,
originalMouseX: 0,
originalMouseY: 0,
originalWidth: 0,
originalHeight: 0,
originalX: 0,
originalY: 0,
handle: null,
element: null
};
this.elementCounter = 0;
this.fontControls = {};
this.backgroundControls = {};
// Billboard image data structure
this.backgroundTemplates = {
'Anniversary': [
{ id: 'anniversary1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-1.png', name: 'Anniversary Image 1' },
{ id: 'anniversary2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-2.png', name: 'Anniversary Image 2' },
{ id: 'anniversary3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-3.png', name: 'Anniversary Image 3' },
{ id: 'anniversary4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-6.png', name: 'Anniversary Image 4' },
{ id: 'anniversary5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-8.png', name: 'Anniversary Image 5' },
{ id: 'anniversary6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-9.png', name: 'Anniversary Image 6' },
{ id: 'anniversary7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-11.png', name: 'Anniversary Image 7' },
{ id: 'anniversary8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-12.png', name: 'Anniversary Image 8' },
{ id: 'anniversary9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-13.png', name: 'Anniversary Image 9' },
{ id: 'anniversary10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-14.png', name: 'Anniversary Image 10' },
{ id: 'anniversary11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-15.png', name: 'Anniversary Image 11' },
{ id: 'anniversary12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-4.png', name: 'Anniversary Image 12' },
{ id: 'anniversary13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-5.png', name: 'Anniversary Image 13' },
{ id: 'anniversary14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-10.png', name: 'Anniversary Image 14' },
{ id: 'anniversary15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Anniversary-7.png', name: 'Anniversary Image 15' }
],
'Benefit': [
{ id: 'benefit1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-3.png', name: 'Benefit Image 1' },
{ id: 'benefit2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-4.png', name: 'Benefit Image 2' },
{ id: 'benefit3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-5.png', name: 'Benefit Image 3' },
{ id: 'benefit4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-6.png', name: 'Benefit Image 4' },
{ id: 'benefit5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-8.png', name: 'Benefit Image 5' },
{ id: 'benefit6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-9.png', name: 'Benefit Image 6' },
{ id: 'benefit7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-11.png', name: 'Benefit Image 7' },
{ id: 'benefit8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-12.png', name: 'Benefit Image 8' },
{ id: 'benefit9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-13.png', name: 'Benefit Image 9' },
{ id: 'benefit10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-14.png', name: 'Benefit Image 10' },
{ id: 'benefit11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-10.png', name: 'Benefit Image 11' },
{ id: 'benefit12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-7.png', name: 'Benefit Image 12' },
{ id: 'benefit13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-2.png', name: 'Benefit Image 13' },
{ id: 'benefit14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-1.png', name: 'Benefit Image 14' },
{ id: 'benefit15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Benefit-15.png', name: 'Benefit Image 15' }
],
'Christian': [
{ id: 'christian1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-1.png', name: 'Christian Image 1' },
{ id: 'christian2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-2.png', name: 'Christian Image 2' },
{ id: 'christian3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-3.png', name: 'Christian Image 3' },
{ id: 'christian4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-4.png', name: 'Christian Image 4' },
{ id: 'christian5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-5.png', name: 'Christian Image 5' },
{ id: 'christian6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-6.png', name: 'Christian Image 6' },
{ id: 'christian7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-8.png', name: 'Christian Image 7' },
{ id: 'christian8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-9.png', name: 'Christian Image 8' },
{ id: 'christian9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-10.png', name: 'Christian Image 9' },
{ id: 'christian10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-11.png', name: 'Christian Image 10' },
{ id: 'christian11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-12.png', name: 'Christian Image 11' },
{ id: 'christian12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-13.png', name: 'Christian Image 12' },
{ id: 'christian13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-14.png', name: 'Christian Image 13' },
{ id: 'christian14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-15.png', name: 'Christian Image 14' },
{ id: 'christian15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Christian-7.png', name: 'Christian Image 15' }
],
'Graduation': [
{ id: 'graduation1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-1.png', name: 'Graduation Image 1' },
{ id: 'graduation2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-2.png', name: 'Graduation Image 2' },
{ id: 'graduation3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-4.png', name: 'Graduation Image 3' },
{ id: 'graduation4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-5.png', name: 'Graduation Image 4' },
{ id: 'graduation5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-7.png', name: 'Graduation Image 5' },
{ id: 'graduation6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-8.png', name: 'Graduation Image 6' },
{ id: 'graduation7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-9.png', name: 'Graduation Image 7' },
{ id: 'graduation8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-10.png', name: 'Graduation Image 8' },
{ id: 'graduation9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-11.png', name: 'Graduation Image 9' },
{ id: 'graduation10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-12.png', name: 'Graduation Image 10' },
{ id: 'graduation11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-13.png', name: 'Graduation Image 11' },
{ id: 'graduation12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-14.png', name: 'Graduation Image 12' },
{ id: 'graduation13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-15.png', name: 'Graduation Image 13' },
{ id: 'graduation14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-6.png', name: 'Graduation Image 14' },
{ id: 'graduation15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Graduation-3.png', name: 'Graduation Image 15' }
],
'Holiday': [
{ id: 'holiday1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-1.png', name: 'Holiday Image 1' },
{ id: 'holiday2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-2.png', name: 'Holiday Image 2' },
{ id: 'holiday3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-3.png', name: 'Holiday Image 3' },
{ id: 'holiday4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-4.png', name: 'Holiday Image 4' },
{ id: 'holiday5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-5.png', name: 'Holiday Image 5' },
{ id: 'holiday6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-6.png', name: 'Holiday Image 6' },
{ id: 'holiday7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-7.png', name: 'Holiday Image 7' },
{ id: 'holiday8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-8.png', name: 'Holiday Image 8' },
{ id: 'holiday9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-9.png', name: 'Holiday Image 9' },
{ id: 'holiday10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-10.png', name: 'Holiday Image 10' },
{ id: 'holiday11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-11.png', name: 'Holiday Image 11' },
{ id: 'holiday12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-12.png', name: 'Holiday Image 12' },
{ id: 'holiday13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-13.png', name: 'Holiday Image 13' },
{ id: 'holiday14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-14.png', name: 'Holiday Image 14' },
{ id: 'holiday15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Holiday-15.png', name: 'Holiday Image 15' }
],
'Local School': [
{ id: 'localschool1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-1.png', name: 'Local Schools Image 1' },
{ id: 'localschool2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-2.png', name: 'Local Schools Image 2' },
{ id: 'localschool3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-3.png', name: 'Local Schools Image 3' },
{ id: 'localschool4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-4.png', name: 'Local Schools Image 4' },
{ id: 'localschool5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-5.png', name: 'Local Schools Image 5' },
{ id: 'localschool6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-6.png', name: 'Local Schools Image 6' },
{ id: 'localschool7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-7.png', name: 'Local Schools Image 7' },
{ id: 'localschool8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-8.png', name: 'Local Schools Image 8' },
{ id: 'localschool9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-9.png', name: 'Local Schools Image 9' },
{ id: 'localschool10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/07/Local-Schools-10.png', name: 'Local Schools Image 10' }
],
'Love': [
{ id: 'love1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-2.png', name: 'Love Image 1' },
{ id: 'love2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-5.png', name: 'Love Image 2' },
{ id: 'love3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-6.png', name: 'Love Image 3' },
{ id: 'love4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-7.png', name: 'Love Image 4' },
{ id: 'love5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-8.png', name: 'Love Image 5' },
{ id: 'love6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-9.png', name: 'Love Image 6' },
{ id: 'love7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-10.png', name: 'Love Image 7' },
{ id: 'love8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-11.png', name: 'Love Image 8' },
{ id: 'love9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-1.png', name: 'Love Image 9' },
{ id: 'love10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-3.png', name: 'Love Image 10' },
{ id: 'love11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-4.png', name: 'Love Image 11' },
{ id: 'love12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-12.png', name: 'Love Image 12' },
{ id: 'love13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-13.png', name: 'Love Image 13' },
{ id: 'love14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-14.png', name: 'Love Image 14' },
{ id: 'love15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Love-15.png', name: 'Love Image 15' }
],
'Marry Me': [
{ id: 'marryme1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-1.png', name: 'Marry Me Image 1' },
{ id: 'marryme2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-2.png', name: 'Marry Me Image 2' },
{ id: 'marryme3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-3.png', name: 'Marry Me Image 3' },
{ id: 'marryme4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-4.png', name: 'Marry Me Image 4' },
{ id: 'marryme5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-5.png', name: 'Marry Me Image 5' },
{ id: 'marryme6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-6.png', name: 'Marry Me Image 6' },
{ id: 'marryme7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-7.png', name: 'Marry Me Image 7' },
{ id: 'marryme8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-8.png', name: 'Marry Me Image 8' },
{ id: 'marryme9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-11.png', name: 'Marry Me Image 9' },
{ id: 'marryme10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-14.png', name: 'Marry Me Image 10' },
{ id: 'marryme11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-15.png', name: 'Marry Me Image 11' },
{ id: 'marryme12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-10.png', name: 'Marry Me Image 12' },
{ id: 'marryme13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-12.png', name: 'Marry Me Image 13' },
{ id: 'marryme14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-13.png', name: 'Marry Me Image 14' },
{ id: 'marryme15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Marry-Me-9.png', name: 'Marry Me Image 15' }
],
'New Born': [
{ id: 'newborn1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-1.png', name: 'New Born Image 1' },
{ id: 'newborn2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-2.png', name: 'New Born Image 2' },
{ id: 'newborn3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-3.png', name: 'New Born Image 3' },
{ id: 'newborn4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-6.png', name: 'New Born Image 4' },
{ id: 'newborn5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-8.png', name: 'New Born Image 5' },
{ id: 'newborn6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-9.png', name: 'New Born Image 6' },
{ id: 'newborn7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-12.png', name: 'New Born Image 7' },
{ id: 'newborn8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-13.png', name: 'New Born Image 8' },
{ id: 'newborn9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-14.png', name: 'New Born Image 9' },
{ id: 'newborn10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-15.png', name: 'New Born Image 10' },
{ id: 'newborn11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-4.png', name: 'New Born Image 11' },
{ id: 'newborn12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-5.png', name: 'New Born Image 12' },
{ id: 'newborn13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-7.png', name: 'New Born Image 13' },
{ id: 'newborn14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-10.png', name: 'New Born Image 14' },
{ id: 'newborn15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/New-Born-11.png', name: 'New Born Image 15' }
],
'Obituary': [
{ id: 'obituary1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-1.png', name: 'Obituary Image 1' },
{ id: 'obituary2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-2.png', name: 'Obituary Image 2' },
{ id: 'obituary3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-3.png', name: 'Obituary Image 3' },
{ id: 'obituary4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-4.png', name: 'Obituary Image 4' },
{ id: 'obituary5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-6.png', name: 'Obituary Image 5' },
{ id: 'obituary6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-7.png', name: 'Obituary Image 6' },
{ id: 'obituary7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-9.png', name: 'Obituary Image 7' },
{ id: 'obituary8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-10.png', name: 'Obituary Image 8' },
{ id: 'obituary9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-11.png', name: 'Obituary Image 9' },
{ id: 'obituary10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-12.png', name: 'Obituary Image 10' },
{ id: 'obituary11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-13.png', name: 'Obituary Image 11' },
{ id: 'obituary12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-14.png', name: 'Obituary Image 12' },
{ id: 'obituary13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-15.png', name: 'Obituary Image 13' },
{ id: 'obituary14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-5.png', name: 'Obituary Image 14' },
{ id: 'obituary15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Obituary-8.png', name: 'Obituary Image 15' }
],
'Other': [
{ id: 'other1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-2.png', name: 'Other Image 1' },
{ id: 'other2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-3.png', name: 'Other Image 2' },
{ id: 'other3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-7.png', name: 'Other Image 3' },
{ id: 'other4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-11.png', name: 'Other Image 4' },
{ id: 'other5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-12.png', name: 'Other Image 5' },
{ id: 'other6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-13.png', name: 'Other Image 6' },
{ id: 'other7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-14.png', name: 'Other Image 7' },
{ id: 'other8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-15.png', name: 'Other Image 8' },
{ id: 'other9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-1.png', name: 'Other Image 9' },
{ id: 'other10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-4.png', name: 'Other Image 10' },
{ id: 'other11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-5.png', name: 'Other Image 11' },
{ id: 'other12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-6.png', name: 'Other Image 12' },
{ id: 'other13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-8.png', name: 'Other Image 13' },
{ id: 'other14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-9.png', name: 'Other Image 14' },
{ id: 'other15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Other-10.png', name: 'Other Image 15' }
],
'Pet': [
{ id: 'pet1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-1.png', name: 'Pet Image 1' },
{ id: 'pet2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-2.png', name: 'Pet Image 2' },
{ id: 'pet3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-3.png', name: 'Pet Image 3' },
{ id: 'pet4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-4.png', name: 'Pet Image 4' },
{ id: 'pet5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-5.png', name: 'Pet Image 5' },
{ id: 'pet6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-6.png', name: 'Pet Image 6' },
{ id: 'pet7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-7.png', name: 'Pet Image 7' },
{ id: 'pet8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-8.png', name: 'Pet Image 8' },
{ id: 'pet9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-9.png', name: 'Pet Image 9' },
{ id: 'pet10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-10.png', name: 'Pet Image 10' },
{ id: 'pet11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-11.png', name: 'Pet Image 11' },
{ id: 'pet12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-12.png', name: 'Pet Image 12' },
{ id: 'pet13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-13.png', name: 'Pet Image 13' },
{ id: 'pet14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-14.png', name: 'Pet Image 14' },
{ id: 'pet15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Pet-15.png', name: 'Pet Image 15' }
],
'Prayer': [
{ id: 'prayer1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-1.png', name: 'Prayer Image 1' },
{ id: 'prayer2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-2.png', name: 'Prayer Image 2' },
{ id: 'prayer3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-3.png', name: 'Prayer Image 3' },
{ id: 'prayer4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-4.png', name: 'Prayer Image 4' },
{ id: 'prayer5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-5.png', name: 'Prayer Image 5' },
{ id: 'prayer6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-7.png', name: 'Prayer Image 6' },
{ id: 'prayer7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-8.png', name: 'Prayer Image 7' },
{ id: 'prayer8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-11.png', name: 'Prayer Image 8' },
{ id: 'prayer9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-12.png', name: 'Prayer Image 9' },
{ id: 'prayer10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-13.png', name: 'Prayer Image 10' },
{ id: 'prayer11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-14.png', name: 'Prayer Image 11' },
{ id: 'prayer12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-15.png', name: 'Prayer Image 12' },
{ id: 'prayer13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-10.png', name: 'Prayer Image 13' },
{ id: 'prayer14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-6.png', name: 'Prayer Image 14' },
{ id: 'prayer15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Prayer-9.png', name: 'Prayer Image 15' }
],
'Retirement': [
{ id: 'retirement1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-3.png', name: 'Retirement Image 1' },
{ id: 'retirement2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-4.png', name: 'Retirement Image 2' },
{ id: 'retirement3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-5.png', name: 'Retirement Image 3' },
{ id: 'retirement4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-6.png', name: 'Retirement Image 4' },
{ id: 'retirement5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-7.png', name: 'Retirement Image 5' },
{ id: 'retirement6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-8.png', name: 'Retirement Image 6' },
{ id: 'retirement7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-9.png', name: 'Retirement Image 7' },
{ id: 'retirement8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-10.png', name: 'Retirement Image 8' },
{ id: 'retirement9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-11.png', name: 'Retirement Image 9' },
{ id: 'retirement10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-12.png', name: 'Retirement Image 10' },
{ id: 'retirement11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-13.png', name: 'Retirement Image 11' },
{ id: 'retirement12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-14.png', name: 'Retirement Image 12' },
{ id: 'retirement13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-15.png', name: 'Retirement Image 13' },
{ id: 'retirement14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-1.png', name: 'Retirement Image 14' },
{ id: 'retirement15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Retirement-2.png', name: 'Retirement Image 15' }
],
'Wedding': [
{ id: 'wedding1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-1.png', name: 'Wedding Image 1' },
{ id: 'wedding2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-2.png', name: 'Wedding Image 2' },
{ id: 'wedding3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-3.png', name: 'Wedding Image 3' },
{ id: 'wedding4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-4.png', name: 'Wedding Image 4' },
{ id: 'wedding5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-5.png', name: 'Wedding Image 5' },
{ id: 'wedding6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-6.png', name: 'Wedding Image 6' },
{ id: 'wedding7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-7.png', name: 'Wedding Image 7' },
{ id: 'wedding8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-8.png', name: 'Wedding Image 8' },
{ id: 'wedding9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-10.png', name: 'Wedding Image 9' },
{ id: 'wedding10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-11.png', name: 'Wedding Image 10' },
{ id: 'wedding11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-12.png', name: 'Wedding Image 11' },
{ id: 'wedding12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-13.png', name: 'Wedding Image 12' },
{ id: 'wedding13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-15.png', name: 'Wedding Image 13' },
{ id: 'wedding14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-14.png', name: 'Wedding Image 14' },
{ id: 'wedding15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Wedding-9.png', name: 'Wedding Image 15' }
],
'Welcome': [
{ id: 'welcome1', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-5.png', name: 'Welcome Image 1' },
{ id: 'welcome2', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-6.png', name: 'Welcome Image 2' },
{ id: 'welcome3', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-8.png', name: 'Welcome Image 3' },
{ id: 'welcome4', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-9.png', name: 'Welcome Image 4' },
{ id: 'welcome5', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-13.png', name: 'Welcome Image 5' },
{ id: 'welcome6', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-14.png', name: 'Welcome Image 6' },
{ id: 'welcome7', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-1.png', name: 'Welcome Image 7' },
{ id: 'welcome8', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-2.png', name: 'Welcome Image 8' },
{ id: 'welcome9', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-3.png', name: 'Welcome Image 9' },
{ id: 'welcome10', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-4.png', name: 'Welcome Image 10' },
{ id: 'welcome11', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-7.png', name: 'Welcome Image 11' },
{ id: 'welcome12', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-10.png', name: 'Welcome Image 12' },
{ id: 'welcome13', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-11.png', name: 'Welcome Image 13' },
{ id: 'welcome14', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-12.png', name: 'Welcome Image 14' },
{ id: 'welcome15', url: 'https://www.borgesmedia.com/wp-content/uploads/2025/06/Welcome-15.png', name: 'Welcome Image 15' }
],
};
this.init();
}
init() {
this.setupCanvas();
this.setupToolbar();
this.setupEventListeners();
// Initialize with disabled font controls - CF7 Pattern
this.disableFontControls();
}
setupCanvas() {
const width = this.canvas.dataset.width || 800;
const height = this.canvas.dataset.height || 400;
// Billboard aspect ratio is handled by CSS, but we set max dimensions
this.canvas.style.maxWidth = width + 'px';
this.canvas.style.maxHeight = height + 'px';
}
setupToolbar() {
// Convert CF7 shortcodes to functional buttons
this.convertShortcodeToButton('[cf7-add-text', 'cf7-btn-text', () => this.addTextElement());
this.convertShortcodeToButton('[cf7-add-image', 'cf7-btn-image', () => this.triggerImageUpload());
this.convertShortcodeToButton('[cf7-export-canvas', 'cf7-btn-export', () => this.exportCanvasAsPNG());
this.convertShortcodeToButton('[cf7-clear-canvas', 'cf7-btn-clear', () => this.clearCanvas());
// Setup background controls
this.setupBackgroundControls();
// Setup font controls
this.setupFontControls();
}
setupBackgroundControls() {
// Get modal elements
const modalTrigger = document.getElementById('cf7-bg-modal-trigger');
const modal = document.getElementById('cf7-bg-modal');
const modalClose = document.getElementById('cf7-modal-close');
const modalCancel = document.getElementById('cf7-modal-cancel');
const modalApply = document.getElementById('cf7-modal-apply');
const clearButton = document.getElementById('cf7-clear-bg');
if (!modalTrigger || !modal || !clearButton) {
console.error('Modal elements not found');
return;
}
// Store modal references
this.modal = {
element: modal,
trigger: modalTrigger,
close: modalClose,
cancel: modalCancel,
apply: modalApply,
selectedCategory: null,
selectedTemplate: null,
focusedElementBeforeModal: null
};
// Set up event listeners
modalTrigger.addEventListener('click', () => this.openBackgroundModal());
modalClose.addEventListener('click', () => this.closeBackgroundModal());
modalCancel.addEventListener('click', () => this.closeBackgroundModal());
modalApply.addEventListener('click', () => this.applySelectedBackground());
clearButton.addEventListener('click', () => this.clearBackground());
// Setup modal content
this.setupModalContent();
this.setupModalKeyboardNavigation();
}
setupFontControls() {
// Enhanced Font family dropdown with Google Fonts - CF7 Compatible with Font Preview
this.createFontPreviewDropdown('[cf7-font-family', 'cf7-select-font', [
// Sans-serif fonts (most readable for digital)
{ value: 'Inter, Arial, sans-serif', text: 'Inter (Modern)', family: 'Inter' },
{ value: 'Roboto, Arial, sans-serif', text: 'Roboto (Clean)', family: 'Roboto' },
{ value: 'Open Sans, Arial, sans-serif', text: 'Open Sans (Friendly)', family: 'Open Sans' },
{ value: 'Lato, Arial, sans-serif', text: 'Lato (Professional)', family: 'Lato' },
{ value: 'Montserrat, Arial, sans-serif', text: 'Montserrat (Geometric)', family: 'Montserrat' },
{ value: 'Poppins, Arial, sans-serif', text: 'Poppins (Rounded)', family: 'Poppins' },
{ value: 'Source Sans Pro, Arial, sans-serif', text: 'Source Sans Pro', family: 'Source Sans Pro' },
{ value: 'Nunito, Arial, sans-serif', text: 'Nunito (Soft)', family: 'Nunito' },
{ value: 'Raleway, Arial, sans-serif', text: 'Raleway (Elegant)', family: 'Raleway' },
// Serif fonts (traditional, formal)
{ value: 'Playfair Display, Georgia, serif', text: 'Playfair Display (Elegant)', family: 'Playfair Display' },
{ value: 'Merriweather, Georgia, serif', text: 'Merriweather (Readable)', family: 'Merriweather' },
{ value: 'Georgia, serif', text: 'Georgia (Classic)', family: 'Georgia' },
{ value: 'Times New Roman, serif', text: 'Times New Roman', family: 'Times New Roman' },
// Display fonts (headlines, impact)
{ value: 'Oswald, Impact, sans-serif', text: 'Oswald (Bold Headlines)', family: 'Oswald' },
{ value: 'Impact, sans-serif', text: 'Impact (Strong)', family: 'Impact' },
// Script/Decorative fonts (special occasions)
{ value: 'Dancing Script, cursive', text: 'Dancing Script (Handwritten)', family: 'Dancing Script' },
{ value: 'Pacifico, cursive', text: 'Pacifico (Playful)', family: 'Pacifico' },
{ value: 'Lobster, cursive', text: 'Lobster (Retro)', family: 'Lobster' },
// System fallbacks
{ value: 'Arial, sans-serif', text: 'Arial (System)', family: 'Arial' },
{ value: 'Helvetica, sans-serif', text: 'Helvetica (System)', family: 'Helvetica' },
{ value: 'Verdana, sans-serif', text: 'Verdana (System)', family: 'Verdana' },
{ value: 'Courier New, monospace', text: 'Courier New (Monospace)', family: 'Courier New' }
], (value) => this.updateSelectedFont('fontFamily', value));
// Font size input
this.convertShortcodeToInput('[cf7-font-size', 'cf7-input-size', 'number', '16',
(value) => this.updateSelectedFont('fontSize', value + 'px'));
// Font style buttons
this.convertShortcodeToToggle('[cf7-font-bold', 'cf7-btn-style',
() => this.toggleSelectedFont('fontWeight', 'bold', 'normal'));
this.convertShortcodeToToggle('[cf7-font-italic', 'cf7-btn-style',
() => this.toggleSelectedFont('fontStyle', 'italic', 'normal'));
// Text alignment buttons
console.log('Setting up text alignment buttons...');
this.convertShortcodeToToggle('[cf7-align-left', 'cf7-btn-style',
() => {
console.log('Left align clicked');
this.setTextAlignment('left');
});
this.convertShortcodeToToggle('[cf7-align-center', 'cf7-btn-style',
() => {
console.log('Center align clicked');
this.setTextAlignment('center');
});
this.convertShortcodeToToggle('[cf7-align-right', 'cf7-btn-style',
() => {
console.log('Right align clicked');
this.setTextAlignment('right');
});
this.convertShortcodeToToggle('[cf7-align-justify', 'cf7-btn-style',
() => {
console.log('Justify align clicked');
this.setTextAlignment('justify');
});
// Font color picker
this.convertShortcodeToColorPicker('[cf7-font-color', 'cf7-color-picker', '#000000',
(value) => this.updateSelectedFont('color', value));
// Text Shadow Controls
this.convertShortcodeToToggle('[cf7-text-shadow-toggle', 'cf7-btn-shadow',
() => this.toggleTextShadow());
this.convertShortcodeToColorPicker('[cf7-shadow-color', 'cf7-color-picker', '#000000',
(value) => this.updateTextShadow('color', value));
this.convertShortcodeToInput('[cf7-shadow-blur', 'cf7-input-shadow-blur', 'number', '2',
(value) => this.updateTextShadow('blur', value));
this.convertShortcodeToInput('[cf7-shadow-offset-x', 'cf7-input-shadow-offset', 'number', '2',
(value) => this.updateTextShadow('offsetX', value));
this.convertShortcodeToInput('[cf7-shadow-offset-y', 'cf7-input-shadow-offset', 'number', '2',
(value) => this.updateTextShadow('offsetY', value));
// Text Shadow Opacity Slider - CF7 Compatible
this.convertShortcodeToRangeSlider('[cf7-shadow-opacity-slider', 'cf7-range-slider', 0, 100, 100,
(value) => this.updateTextShadow('opacity', value), 'cf7-opacity-value', '%');
}
convertShortcodeToButton(shortcode, className, callback) {
const walker = document.createTreeWalker(
this.container,
NodeFilter.SHOW_TEXT,
null,
false
);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
const parent = textNode.parentElement;
const text = this.extractShortcodeAttribute(textNode.textContent, 'text') || 'Button';
const button = document.createElement('button');
button.textContent = text;
button.className = className;
button.type = 'button'; // Prevent form submission in WordPress
// WordPress-compatible event handling
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
callback();
});
parent.appendChild(button);
textNode.textContent = '';
break;
}
}
}
convertShortcodeToSelect(shortcode, className, options, callback) {
const walker = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
const parent = textNode.parentElement;
const select = document.createElement('select');
select.className = className;
options.forEach(option => {
const optionEl = document.createElement('option');
optionEl.value = option.value;
optionEl.textContent = option.text;
select.appendChild(optionEl);
});
select.onchange = (e) => callback(e.target.value);
parent.appendChild(select);
// Track controls based on shortcode type
if (shortcode.includes('font-family')) {
this.fontControls.fontFamily = select;
} else if (shortcode.includes('font-size')) {
this.fontControls.fontSize = select;
} else if (shortcode.includes('background-category')) {
this.backgroundControls.category = select;
} else if (shortcode.includes('background-template')) {
this.backgroundControls.template = select;
}
textNode.textContent = '';
break;
}
}
}
// CF7 Font Preview Dropdown - Shows each font in its own typeface
createFontPreviewDropdown(shortcode, className, options, callback) {
const walker = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
const parent = textNode.parentElement;
// Create custom dropdown container
const dropdownContainer = document.createElement('div');
dropdownContainer.className = 'cf7-font-preview-dropdown';
// Create button that shows selected font
const selectButton = document.createElement('button');
selectButton.type = 'button';
selectButton.className = 'cf7-font-preview-button';
selectButton.innerHTML = `
Select Font
▼
`;
// Create dropdown list
const dropdownList = document.createElement('ul');
dropdownList.className = 'cf7-font-preview-list cf7-hidden';
dropdownList.setAttribute('role', 'listbox');
// Add options to dropdown
options.forEach((option, index) => {
const listItem = document.createElement('li');
listItem.className = 'cf7-font-preview-option';
listItem.setAttribute('role', 'option');
listItem.setAttribute('data-value', option.value);
listItem.style.fontFamily = option.family || option.value;
listItem.textContent = option.text;
// Handle option selection
listItem.addEventListener('click', () => {
// Update button text and font
const selectedSpan = selectButton.querySelector('.cf7-selected-font');
selectedSpan.textContent = option.text;
selectedSpan.style.fontFamily = option.family || option.value;
// Remove selected class from all options
dropdownList.querySelectorAll('.cf7-font-preview-option').forEach(opt => {
opt.classList.remove('cf7-selected');
});
// Add selected class to current option
listItem.classList.add('cf7-selected');
// Close dropdown
dropdownList.classList.add('cf7-hidden');
selectButton.setAttribute('aria-expanded', 'false');
// Call callback
callback(option.value);
});
dropdownList.appendChild(listItem);
});
// Toggle dropdown functionality
selectButton.addEventListener('click', (e) => {
e.preventDefault();
// Don't open if disabled
if (dropdownContainer.classList.contains('cf7-disabled') || selectButton.disabled) {
return;
}
const isOpen = !dropdownList.classList.contains('cf7-hidden');
// Close all other font dropdowns first
document.querySelectorAll('.cf7-font-preview-list').forEach(list => {
if (list !== dropdownList) {
list.classList.add('cf7-hidden');
}
});
// Toggle current dropdown
dropdownList.classList.toggle('cf7-hidden');
selectButton.setAttribute('aria-expanded', !isOpen);
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!dropdownContainer.contains(e.target)) {
dropdownList.classList.add('cf7-hidden');
selectButton.setAttribute('aria-expanded', 'false');
}
});
// Assemble dropdown
dropdownContainer.appendChild(selectButton);
dropdownContainer.appendChild(dropdownList);
parent.appendChild(dropdownContainer);
// Track control
if (shortcode.includes('font-family')) {
this.fontControls.fontFamily = dropdownContainer;
}
textNode.textContent = '';
break;
}
}
}
convertShortcodeToInput(shortcode, className, type, defaultValue, callback) {
const walker = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
const parent = textNode.parentElement;
const input = document.createElement('input');
input.type = type;
input.className = className;
input.value = defaultValue;
// Set appropriate min/max based on input type
if (className.includes('cf7-input-size')) {
input.min = '8';
input.max = '72';
this.fontControls.fontSize = input;
} else if (className.includes('cf7-input-shadow-blur')) {
input.min = '0';
input.max = '20';
input.setAttribute('data-shadow-blur', 'true');
} else if (className.includes('cf7-input-shadow-offset')) {
input.min = '-20';
input.max = '20';
if (shortcode.includes('offset-x')) {
input.setAttribute('data-axis', 'x');
} else if (shortcode.includes('offset-y')) {
input.setAttribute('data-axis', 'y');
}
}
input.oninput = (e) => callback(e.target.value);
parent.appendChild(input);
textNode.textContent = '';
break;
}
}
}
convertShortcodeToToggle(shortcode, className, callback) {
console.log('convertShortcodeToToggle called with shortcode:', shortcode);
const walker = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
console.log('Found shortcode in text:', textNode.textContent);
const parent = textNode.parentElement;
const text = this.extractShortcodeAttribute(textNode.textContent, 'text') || 'Toggle';
const button = document.createElement('button');
button.textContent = text;
button.className = className;
button.type = 'button'; // Prevent form submission in WordPress
// WordPress-compatible event handling
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
callback();
});
parent.appendChild(button);
if (text === 'B') {
this.fontControls.bold = button;
// Add direct test for Bold button
console.log('Bold button created:', button);
console.log('Bold button classes:', button.className);
}
if (text === 'I') this.fontControls.italic = button;
if (text === 'S') this.fontControls.textShadow = button;
// Text alignment buttons - check for Unicode symbols
if (text === '⬅') {
this.fontControls.alignLeft = button;
console.log('Left align button created:', button);
}
if (text === '⬌') {
this.fontControls.alignCenter = button;
console.log('Center align button created:', button);
}
if (text === '➡') {
this.fontControls.alignRight = button;
console.log('Right align button created:', button);
}
if (text === '≡') {
this.fontControls.alignJustify = button;
console.log('Justify align button created:', button);
}
textNode.textContent = '';
break;
}
}
}
convertShortcodeToColorPicker(shortcode, className, defaultValue, callback) {
const walker = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
const parent = textNode.parentElement;
const value = this.extractShortcodeAttribute(textNode.textContent, 'value') || defaultValue;
const colorPicker = document.createElement('input');
colorPicker.type = 'color';
colorPicker.className = className;
colorPicker.value = value;
// Set appropriate title and data attributes based on shortcode
if (shortcode.includes('cf7-font-color')) {
colorPicker.title = 'Choose font color';
this.fontControls.color = colorPicker;
} else if (shortcode.includes('cf7-shadow-color')) {
colorPicker.title = 'Choose shadow color';
colorPicker.setAttribute('data-shadow', 'color');
}
colorPicker.onchange = (e) => callback(e.target.value);
parent.appendChild(colorPicker);
textNode.textContent = '';
break;
}
}
}
// CF7 Range Slider Converter - Contact Form 7 Compatible
convertShortcodeToRangeSlider(shortcode, className, min, max, defaultValue, callback, valueDisplayId, unit) {
const walker = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
if (textNode.textContent.includes(shortcode)) {
const parent = textNode.parentElement;
const value = this.extractShortcodeAttribute(textNode.textContent, 'value') || defaultValue;
const rangeSlider = document.createElement('input');
rangeSlider.type = 'range';
rangeSlider.className = className;
rangeSlider.min = min;
rangeSlider.max = max;
rangeSlider.value = value;
// WordPress-specific fixes for range slider
rangeSlider.setAttribute('min', min);
rangeSlider.setAttribute('max', max);
rangeSlider.setAttribute('value', value);
rangeSlider.defaultValue = value;
// Set data attributes for shadow control identification
if (shortcode.includes('opacity')) {
rangeSlider.setAttribute('data-shadow-opacity', 'true');
}
// WordPress-compatible event handling for range slider
rangeSlider.addEventListener('input', (e) => {
e.preventDefault();
e.stopPropagation();
const currentValue = e.target.value;
callback(currentValue);
// Update value display
const valueDisplay = document.getElementById(valueDisplayId);
if (valueDisplay) {
valueDisplay.textContent = currentValue + unit;
}
});
// Initialize value display
const valueDisplay = document.getElementById(valueDisplayId);
if (valueDisplay) {
valueDisplay.textContent = value + unit;
}
parent.appendChild(rangeSlider);
// WordPress compatibility fix - Ensure correct value after DOM insertion
setTimeout(() => {
rangeSlider.value = value;
rangeSlider.setAttribute('value', value);
if (valueDisplay) {
valueDisplay.textContent = value + unit;
}
}, 10);
textNode.textContent = '';
break;
}
}
}
extractShortcodeAttribute(shortcode, attribute) {
const regex = new RegExp(`${attribute}:"([^"]*)"`, 'i');
const match = shortcode.match(regex);
return match ? match[1] : null;
}
setupEventListeners() {
// Canvas click to deselect elements
this.canvas.addEventListener('click', (e) => {
if (e.target === this.canvas || e.target === this.elementsContainer) {
this.deselectAll();
}
});
// Global mouse events for dragging and resizing
document.addEventListener('mousemove', (e) => {
this.handleMouseMove(e);
this.handleResizeMove(e);
});
document.addEventListener('mouseup', () => {
this.handleMouseUp();
this.handleResizeUp();
});
// Test button for alignment
const testButton = document.getElementById('test-align-left');
if (testButton) {
testButton.addEventListener('click', () => {
console.log('Test button clicked!');
this.setTextAlignment('left');
});
}
}
addTextElement() {
this.elementCounter++;
// Create container element (non-editable)
const textElement = document.createElement('div');
textElement.className = 'cf7-draggable-text';
textElement.style.left = '50px';
textElement.style.top = '50px';
textElement.style.width = '150px';
textElement.style.height = '40px';
textElement.setAttribute('data-element-id', `text-${this.elementCounter}`);
// Create separate editable content area
const editableContent = document.createElement('div');
editableContent.className = 'cf7-editable-content';
editableContent.contentEditable = true;
editableContent.textContent = `Text ${this.elementCounter}`;
editableContent.setAttribute('data-placeholder', 'Enter text...');
// Add delete button with enhanced functionality
const deleteBtn = document.createElement('button');
deleteBtn.className = 'cf7-delete-btn';
deleteBtn.innerHTML = '×';
deleteBtn.title = 'Delete text element';
deleteBtn.setAttribute('aria-label', 'Delete text element');
deleteBtn.setAttribute('contenteditable', 'false'); // Prevent editing
// Enhanced event handling for delete button
deleteBtn.onmousedown = (e) => {
e.stopPropagation(); // Prevent drag start
e.preventDefault();
};
deleteBtn.onclick = (e) => {
e.stopPropagation();
e.preventDefault();
this.deleteElement(textElement);
};
// Add resize handles
this.addResizeHandles(textElement);
// Append elements in correct order
textElement.appendChild(editableContent);
textElement.appendChild(deleteBtn);
// Setup enhanced element events
this.setupElementEvents(textElement);
this.setupTextEditingEvents(textElement, editableContent);
this.elementsContainer.appendChild(textElement);
this.selectElement(textElement);
}
addResizeHandles(element) {
const handles = ['nw', 'ne', 'sw', 'se'];
handles.forEach(direction => {
const handle = document.createElement('div');
handle.className = `cf7-resize-handle cf7-resize-${direction}`;
handle.setAttribute('contenteditable', 'false'); // Prevent editing
handle.setAttribute('data-no-edit', 'true'); // Additional protection
// Enhanced event handling
handle.addEventListener('mousedown', (e) => {
e.stopPropagation(); // Prevent text selection
this.handleResizeDown(e, element, direction);
});
// Prevent text editing interference
handle.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
});
element.appendChild(handle);
});
}
setupTextEditingEvents(textElement, editableContent) {
// Store reference to editable content
textElement._editableContent = editableContent;
// Enhanced text editing event handling
editableContent.addEventListener('focus', (e) => {
e.stopPropagation();
textElement.classList.add('cf7-editing');
this.selectElement(textElement);
});
editableContent.addEventListener('blur', (e) => {
e.stopPropagation();
textElement.classList.remove('cf7-editing');
this.preserveElementStructure(textElement);
});
// CF7 Pattern: Conditional event handling for drag vs edit
editableContent.addEventListener('mousedown', (e) => {
// Only stop propagation if actively editing (focused)
if (document.activeElement === editableContent) {
e.stopPropagation();
} else {
// Allow drag to work by not stopping propagation
// This follows CF7 pattern of conditional event handling
}
});
// Handle text changes with structure preservation
editableContent.addEventListener('input', (e) => {
this.handleTextInput(textElement, editableContent);
});
// Handle keyboard events
editableContent.addEventListener('keydown', (e) => {
this.handleTextKeydown(e, textElement, editableContent);
});
// CF7 Pattern: Double click to enter edit mode
textElement.addEventListener('dblclick', (e) => {
if (!e.target.classList.contains('cf7-editable-content')) {
e.preventDefault();
e.stopPropagation();
editableContent.focus();
// Set cursor at end of text
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(editableContent);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
});
// CF7 Pattern: Single click behavior for editable content
editableContent.addEventListener('click', (e) => {
// If not in editing mode, single click should focus for editing
if (!textElement.classList.contains('cf7-editing')) {
e.preventDefault();
e.stopPropagation();
editableContent.focus();
} else {
// In editing mode, allow normal text cursor positioning
e.stopPropagation();
}
});
}
triggerImageUpload() {
// Create a hidden file input following CF7 patterns
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.className = 'cf7-file-input';
fileInput.style.display = 'none';
// Add event listener for file selection
fileInput.addEventListener('change', this.addImageElement());
// Trigger file selection
document.body.appendChild(fileInput);
fileInput.click();
document.body.removeChild(fileInput);
}
addImageElement() {
// This will be called when file is selected
return (event) => {
const file = event.target.files[0];
if (!file || !file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = (e) => {
this.elementCounter++;
const imageElement = document.createElement('div');
imageElement.className = 'cf7-draggable-image';
imageElement.style.left = '50px';
imageElement.style.top = '50px';
imageElement.style.width = '150px';
imageElement.style.height = '150px';
const img = document.createElement('img');
img.src = e.target.result;
img.alt = `Image ${this.elementCounter}`;
imageElement.appendChild(img);
// Add delete button with enhanced functionality
const deleteBtn = document.createElement('button');
deleteBtn.className = 'cf7-delete-btn';
deleteBtn.innerHTML = '×';
deleteBtn.title = 'Delete image element';
deleteBtn.setAttribute('aria-label', 'Delete image element');
// Enhanced event handling
deleteBtn.onmousedown = (e) => {
e.stopPropagation(); // Prevent drag start
e.preventDefault();
};
deleteBtn.onclick = (e) => {
e.stopPropagation();
e.preventDefault();
this.deleteElement(imageElement);
};
imageElement.appendChild(deleteBtn);
// Add resize handles for images (following CF7 patterns)
this.addResizeHandles(imageElement);
this.setupElementEvents(imageElement);
this.elementsContainer.appendChild(imageElement);
this.selectElement(imageElement);
};
reader.readAsDataURL(file);
// Reset file input
event.target.value = '';
};
}
setupElementEvents(element) {
// CF7 Pattern: Main element mouse events
element.addEventListener('mousedown', (e) => this.handleMouseDown(e, element));
element.addEventListener('click', (e) => {
// CF7 Pattern: Conditional event handling
// Only stop propagation if not clicking on editable content or controls
if (!e.target.classList.contains('cf7-editable-content') &&
!e.target.classList.contains('cf7-delete-btn') &&
!e.target.classList.contains('cf7-resize-handle')) {
e.stopPropagation();
this.selectElement(element);
}
});
// Handle image resizing
if (element.classList.contains('cf7-draggable-image')) {
// Double click to reset size
element.addEventListener('dblclick', () => {
element.style.width = '150px';
element.style.height = '150px';
});
}
}
handleTextInput(textElement, editableContent) {
// Ensure structure is preserved after text input
this.preserveElementStructure(textElement);
// Handle empty text placeholder
if (editableContent.textContent.trim() === '') {
editableContent.classList.add('cf7-empty');
} else {
editableContent.classList.remove('cf7-empty');
}
}
handleTextKeydown(e, textElement, editableContent) {
// Handle special keys
if (e.key === 'Escape') {
e.preventDefault();
editableContent.blur();
return;
}
// Prevent certain keys that might affect structure
if (e.key === 'Delete' && editableContent.textContent.trim() === '') {
e.preventDefault();
return;
}
// Allow normal text editing
e.stopPropagation();
}
preserveElementStructure(textElement) {
// Ensure all required child elements are present
const editableContent = textElement.querySelector('.cf7-editable-content');
const deleteBtn = textElement.querySelector('.cf7-delete-btn');
const resizeHandles = textElement.querySelectorAll('.cf7-resize-handle');
// Re-add delete button if missing
if (!deleteBtn) {
const newDeleteBtn = document.createElement('button');
newDeleteBtn.className = 'cf7-delete-btn';
newDeleteBtn.innerHTML = '×';
newDeleteBtn.title = 'Delete text element';
newDeleteBtn.setAttribute('aria-label', 'Delete text element');
newDeleteBtn.setAttribute('contenteditable', 'false');
newDeleteBtn.onmousedown = (e) => {
e.stopPropagation();
e.preventDefault();
};
newDeleteBtn.onclick = (e) => {
e.stopPropagation();
e.preventDefault();
this.deleteElement(textElement);
};
textElement.appendChild(newDeleteBtn);
}
// Re-add resize handles if missing
if (resizeHandles.length handle.remove());
// Re-add all handles
this.addResizeHandles(textElement);
}
// Ensure editable content is properly configured
if (editableContent) {
editableContent.setAttribute('contenteditable', 'true');
if (!editableContent.hasAttribute('data-placeholder')) {
editableContent.setAttribute('data-placeholder', 'Enter text...');
}
}
}
handleMouseDown(e, element) {
// CF7 Pattern: Skip drag for control elements
if (e.target.classList.contains('cf7-delete-btn')) return;
if (e.target.classList.contains('cf7-resize-handle')) return;
// CF7 Pattern: Skip drag if clicking on editable content that's focused
if (e.target.classList.contains('cf7-editable-content')) {
const editableContent = e.target;
// Only allow drag if not actively editing
if (document.activeElement === editableContent || element.classList.contains('cf7-editing')) {
return; // Let text editing take precedence
}
}
// Check if clicking on resize area for images
if (element.classList.contains('cf7-draggable-image')) {
const rect = element.getBoundingClientRect();
const isResizing = (
e.clientX > rect.right - 20 &&
e.clientY > rect.bottom - 20
);
if (isResizing) {
// Let CSS handle the resize, don't start dragging
return;
}
}
// CF7 Pattern: Initialize drag data
this.dragData.isDragging = true;
this.dragData.startX = e.clientX;
this.dragData.startY = e.clientY;
this.dragData.elementX = parseInt(element.style.left) || 0;
this.dragData.elementY = parseInt(element.style.top) || 0;
this.dragData.element = element;
this.selectElement(element);
e.preventDefault();
}
handleMouseMove(e) {
if (!this.dragData.isDragging || !this.dragData.element) return;
const deltaX = e.clientX - this.dragData.startX;
const deltaY = e.clientY - this.dragData.startY;
const newX = this.dragData.elementX + deltaX;
const newY = this.dragData.elementY + deltaY;
// Keep element within canvas bounds
const canvasRect = this.canvas.getBoundingClientRect();
const elementRect = this.dragData.element.getBoundingClientRect();
const maxX = canvasRect.width - elementRect.width;
const maxY = canvasRect.height - elementRect.height;
this.dragData.element.style.left = Math.max(0, Math.min(newX, maxX)) + 'px';
this.dragData.element.style.top = Math.max(0, Math.min(newY, maxY)) + 'px';
}
handleMouseUp() {
this.dragData.isDragging = false;
this.dragData.element = null;
}
handleResizeDown(e, element, direction) {
e.stopPropagation();
e.preventDefault();
// Store original values when resize starts
this.resizeData.isResizing = true;
this.resizeData.originalMouseX = e.clientX;
this.resizeData.originalMouseY = e.clientY;
this.resizeData.originalWidth = parseInt(element.style.width) || element.offsetWidth;
this.resizeData.originalHeight = parseInt(element.style.height) || element.offsetHeight;
this.resizeData.originalX = parseInt(element.style.left) || 0;
this.resizeData.originalY = parseInt(element.style.top) || 0;
this.resizeData.element = element;
this.resizeData.handle = direction;
this.selectElement(element);
}
handleResizeMove(e) {
if (!this.resizeData.isResizing || !this.resizeData.element) return;
// Calculate mouse movement delta from original position
const deltaX = e.clientX - this.resizeData.originalMouseX;
const deltaY = e.clientY - this.resizeData.originalMouseY;
let newWidth, newHeight, newLeft, newTop;
// Use the formulas from the research - calculate based on original values + delta
switch (this.resizeData.handle) {
case 'se': // Southeast (bottom-right)
newWidth = Math.max(50, this.resizeData.originalWidth + deltaX);
newHeight = Math.max(20, this.resizeData.originalHeight + deltaY);
newLeft = this.resizeData.originalX;
newTop = this.resizeData.originalY;
break;
case 'sw': // Southwest (bottom-left)
newWidth = Math.max(50, this.resizeData.originalWidth - deltaX);
newHeight = Math.max(20, this.resizeData.originalHeight + deltaY);
newLeft = this.resizeData.originalX + deltaX;
newTop = this.resizeData.originalY;
// Prevent width from going below minimum
if (newWidth === 50) {
newLeft = this.resizeData.originalX + this.resizeData.originalWidth - 50;
}
break;
case 'ne': // Northeast (top-right)
newWidth = Math.max(50, this.resizeData.originalWidth + deltaX);
newHeight = Math.max(20, this.resizeData.originalHeight - deltaY);
newLeft = this.resizeData.originalX;
newTop = this.resizeData.originalY + deltaY;
// Prevent height from going below minimum
if (newHeight === 20) {
newTop = this.resizeData.originalY + this.resizeData.originalHeight - 20;
}
break;
case 'nw': // Northwest (top-left)
newWidth = Math.max(50, this.resizeData.originalWidth - deltaX);
newHeight = Math.max(20, this.resizeData.originalHeight - deltaY);
newLeft = this.resizeData.originalX + deltaX;
newTop = this.resizeData.originalY + deltaY;
// Prevent dimensions from going below minimum
if (newWidth === 50) {
newLeft = this.resizeData.originalX + this.resizeData.originalWidth - 50;
}
if (newHeight === 20) {
newTop = this.resizeData.originalY + this.resizeData.originalHeight - 20;
}
break;
}
// Keep element within canvas bounds
const canvasRect = this.canvas.getBoundingClientRect();
const maxX = canvasRect.width - newWidth;
const maxY = canvasRect.height - newHeight;
newLeft = Math.max(0, Math.min(newLeft, maxX));
newTop = Math.max(0, Math.min(newTop, maxY));
// Apply the calculated values
this.resizeData.element.style.width = newWidth + 'px';
this.resizeData.element.style.height = newHeight + 'px';
this.resizeData.element.style.left = newLeft + 'px';
this.resizeData.element.style.top = newTop + 'px';
}
handleResizeUp() {
this.resizeData.isResizing = false;
this.resizeData.element = null;
this.resizeData.handle = null;
}
selectElement(element) {
this.deselectAll();
element.classList.add('cf7-selected');
this.selectedElement = element;
this.updateFontControlsFromElement(element);
// Enable font controls if text element is selected - CF7 Pattern
if (element.classList.contains('cf7-draggable-text')) {
this.enableFontControls();
}
}
deselectAll() {
const selected = this.container.querySelectorAll('.cf7-selected');
selected.forEach(el => el.classList.remove('cf7-selected'));
this.selectedElement = null;
this.resetFontControls();
}
updateFontControlsFromElement(element) {
if (!element.classList.contains('cf7-draggable-text')) {
// If not a text element, disable font controls
this.disableFontControls();
return;
}
// Enable font controls for text elements - CF7 Pattern
this.enableFontControls();
// Get styles from editable content if available, otherwise from element
const editableContent = element.querySelector('.cf7-editable-content');
const targetElement = editableContent || element;
const computedStyle = window.getComputedStyle(targetElement);
// Update font family
if (this.fontControls.fontFamily) {
this.fontControls.fontFamily.value = computedStyle.fontFamily;
}
// Update font size
if (this.fontControls.fontSize) {
this.fontControls.fontSize.value = parseInt(computedStyle.fontSize);
}
// Update bold button
if (this.fontControls.bold) {
const isBold = computedStyle.fontWeight === 'bold' || parseInt(computedStyle.fontWeight) >= 700;
this.fontControls.bold.classList.toggle('cf7-active', isBold);
}
// Update italic button
if (this.fontControls.italic) {
const isItalic = computedStyle.fontStyle === 'italic';
this.fontControls.italic.classList.toggle('cf7-active', isItalic);
}
// Update color picker
if (this.fontControls.color) {
const color = computedStyle.color;
// Convert RGB to hex for color picker
const hexColor = this.rgbToHex(color);
this.fontControls.color.value = hexColor;
}
// Update text alignment buttons
const currentAlignment = computedStyle.textAlign || 'left';
// Clear all alignment button active states
if (this.fontControls.alignLeft) this.fontControls.alignLeft.classList.remove('cf7-active');
if (this.fontControls.alignCenter) this.fontControls.alignCenter.classList.remove('cf7-active');
if (this.fontControls.alignRight) this.fontControls.alignRight.classList.remove('cf7-active');
if (this.fontControls.alignJustify) this.fontControls.alignJustify.classList.remove('cf7-active');
// Set the current alignment button as active
if (currentAlignment === 'left' && this.fontControls.alignLeft) {
this.fontControls.alignLeft.classList.add('cf7-active');
} else if (currentAlignment === 'center' && this.fontControls.alignCenter) {
this.fontControls.alignCenter.classList.add('cf7-active');
} else if (currentAlignment === 'right' && this.fontControls.alignRight) {
this.fontControls.alignRight.classList.add('cf7-active');
} else if (currentAlignment === 'justify' && this.fontControls.alignJustify) {
this.fontControls.alignJustify.classList.add('cf7-active');
}
// Update text shadow controls
this.updateTextShadowControls(element, computedStyle);
}
updateTextShadowControls(element, computedStyle) {
// Check editable content for text shadow first
const editableContent = element.querySelector('.cf7-editable-content');
const targetElement = editableContent || element;
const targetStyle = editableContent ? window.getComputedStyle(editableContent) : computedStyle;
const textShadow = targetStyle.textShadow || targetElement.style.textShadow;
const shadowToggle = this.container.querySelector('.cf7-btn-shadow');
const shadowColorPicker = this.container.querySelector('.cf7-color-picker[data-shadow="color"]');
const shadowBlurInput = this.container.querySelector('.cf7-input-shadow-blur');
const shadowOffsetXInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="x"]');
const shadowOffsetYInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="y"]');
const shadowOpacitySlider = this.container.querySelector('.cf7-range-slider[data-shadow-opacity="true"]');
// Check if element has shadow applied (including stored config)
const hasStoredShadow = element.shadowConfig && element.shadowConfig.applied !== false;
const hasVisibleShadow = textShadow && textShadow !== 'none';
if (hasVisibleShadow || hasStoredShadow) {
// Enhanced regex to match both rgb and rgba patterns
const shadowMatch = textShadow ? textShadow.match(/(-?\d+(?:\.\d+)?)px\s+(-?\d+(?:\.\d+)?)px\s+(-?\d+(?:\.\d+)?)px\s+(.+)/) : null;
if ((shadowMatch || hasStoredShadow) && shadowToggle) {
// Element has shadow - set button to active
shadowToggle.classList.add('cf7-active');
if (shadowMatch) {
const [, offsetX, offsetY, blur, color] = shadowMatch;
// Extract opacity from rgba if present
let opacity = '100';
if (color.includes('rgba')) {
const rgbaMatch = color.match(/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/);
if (rgbaMatch) {
opacity = Math.round(parseFloat(rgbaMatch[1]) * 100).toString();
}
}
// Update input controls
if (shadowOffsetXInput) shadowOffsetXInput.value = offsetX;
if (shadowOffsetYInput) shadowOffsetYInput.value = offsetY;
if (shadowBlurInput) shadowBlurInput.value = blur;
if (shadowColorPicker) shadowColorPicker.value = this.rgbToHex(color.trim());
// Update opacity slider
if (shadowOpacitySlider) {
shadowOpacitySlider.value = opacity;
const valueDisplay = document.getElementById('cf7-opacity-value');
if (valueDisplay) valueDisplay.textContent = opacity + '%';
}
// Store shadow config on element
element.shadowConfig = {
color: this.rgbToHex(color.trim()),
blur: blur,
offsetX: offsetX,
offsetY: offsetY,
opacity: opacity,
applied: true
};
} else if (hasStoredShadow && element.shadowConfig) {
// Use stored config to update controls
const config = element.shadowConfig;
if (shadowOffsetXInput) shadowOffsetXInput.value = config.offsetX || '2';
if (shadowOffsetYInput) shadowOffsetYInput.value = config.offsetY || '2';
if (shadowBlurInput) shadowBlurInput.value = config.blur || '2';
if (shadowColorPicker) shadowColorPicker.value = config.color || '#000000';
if (shadowOpacitySlider) {
shadowOpacitySlider.value = config.opacity || '100';
const valueDisplay = document.getElementById('cf7-opacity-value');
if (valueDisplay) valueDisplay.textContent = (config.opacity || '100') + '%';
}
}
}
} else {
// Element has no shadow - set button to inactive (like bold/italic)
if (shadowToggle) shadowToggle.classList.remove('cf7-active');
// Reset to default values
if (shadowOffsetXInput) shadowOffsetXInput.value = '2';
if (shadowOffsetYInput) shadowOffsetYInput.value = '2';
if (shadowBlurInput) shadowBlurInput.value = '2';
if (shadowColorPicker) shadowColorPicker.value = '#000000';
// Reset opacity slider
if (shadowOpacitySlider) {
shadowOpacitySlider.value = '100';
const valueDisplay = document.getElementById('cf7-opacity-value');
if (valueDisplay) valueDisplay.textContent = '100%';
}
}
}
resetFontControls() {
if (this.fontControls.fontFamily) this.fontControls.fontFamily.value = 'Arial, sans-serif';
if (this.fontControls.fontSize) this.fontControls.fontSize.value = '16';
if (this.fontControls.bold) this.fontControls.bold.classList.remove('cf7-active');
if (this.fontControls.italic) this.fontControls.italic.classList.remove('cf7-active');
if (this.fontControls.color) this.fontControls.color.value = '#000000';
// Reset text alignment buttons
if (this.fontControls.alignLeft) this.fontControls.alignLeft.classList.remove('cf7-active');
if (this.fontControls.alignCenter) this.fontControls.alignCenter.classList.remove('cf7-active');
if (this.fontControls.alignRight) this.fontControls.alignRight.classList.remove('cf7-active');
if (this.fontControls.alignJustify) this.fontControls.alignJustify.classList.remove('cf7-active');
// Reset text shadow controls to defaults - CF7 Compatible
// Note: Don't reset shadow button state here - it should be managed per element
const shadowColorPicker = this.container.querySelector('.cf7-color-picker[data-shadow="color"]');
const shadowBlurInput = this.container.querySelector('.cf7-input-shadow-blur');
const shadowOffsetXInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="x"]');
const shadowOffsetYInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="y"]');
if (shadowColorPicker) shadowColorPicker.value = '#000000';
if (shadowBlurInput) shadowBlurInput.value = '2';
if (shadowOffsetXInput) shadowOffsetXInput.value = '2';
if (shadowOffsetYInput) shadowOffsetYInput.value = '2';
// Only reset shadow button when no text is selected (disable state)
if (this.fontControls.textShadow) this.fontControls.textShadow.classList.remove('cf7-active');
// Disable all font controls when no text is selected - CF7 Pattern
this.disableFontControls();
}
// CF7 Compatible: Enable font controls when text is selected
enableFontControls() {
// Enable font family dropdown (custom font preview dropdown)
if (this.fontControls.fontFamily) {
// Check if it's the new custom font preview dropdown
if (this.fontControls.fontFamily.classList.contains('cf7-font-preview-dropdown')) {
this.fontControls.fontFamily.classList.remove('cf7-disabled');
const button = this.fontControls.fontFamily.querySelector('.cf7-font-preview-button');
if (button) {
button.disabled = false;
}
} else {
// Handle regular select dropdown
this.fontControls.fontFamily.disabled = false;
this.fontControls.fontFamily.classList.remove('cf7-disabled');
}
}
// Enable font size input
if (this.fontControls.fontSize) {
this.fontControls.fontSize.disabled = false;
this.fontControls.fontSize.classList.remove('cf7-disabled');
}
// Enable style buttons
if (this.fontControls.bold) {
this.fontControls.bold.disabled = false;
this.fontControls.bold.classList.remove('cf7-disabled');
}
if (this.fontControls.italic) {
this.fontControls.italic.disabled = false;
this.fontControls.italic.classList.remove('cf7-disabled');
}
// Enable text alignment buttons
if (this.fontControls.alignLeft) {
this.fontControls.alignLeft.disabled = false;
this.fontControls.alignLeft.classList.remove('cf7-disabled');
}
if (this.fontControls.alignCenter) {
this.fontControls.alignCenter.disabled = false;
this.fontControls.alignCenter.classList.remove('cf7-disabled');
}
if (this.fontControls.alignRight) {
this.fontControls.alignRight.disabled = false;
this.fontControls.alignRight.classList.remove('cf7-disabled');
}
if (this.fontControls.alignJustify) {
this.fontControls.alignJustify.disabled = false;
this.fontControls.alignJustify.classList.remove('cf7-disabled');
}
// Enable color picker
if (this.fontControls.color) {
this.fontControls.color.disabled = false;
this.fontControls.color.classList.remove('cf7-disabled');
}
// Enable text shadow controls
if (this.fontControls.textShadow) {
this.fontControls.textShadow.disabled = false;
this.fontControls.textShadow.classList.remove('cf7-disabled');
}
// Enable shadow controls
const shadowColorPicker = this.container.querySelector('.cf7-color-picker[data-shadow="color"]');
const shadowBlurInput = this.container.querySelector('.cf7-input-shadow-blur');
const shadowOffsetXInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="x"]');
const shadowOffsetYInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="y"]');
const shadowOpacitySlider = this.container.querySelector('.cf7-range-slider[data-shadow-opacity="true"]');
if (shadowColorPicker) {
shadowColorPicker.disabled = false;
shadowColorPicker.classList.remove('cf7-disabled');
}
if (shadowBlurInput) {
shadowBlurInput.disabled = false;
shadowBlurInput.classList.remove('cf7-disabled');
}
if (shadowOffsetXInput) {
shadowOffsetXInput.disabled = false;
shadowOffsetXInput.classList.remove('cf7-disabled');
}
if (shadowOffsetYInput) {
shadowOffsetYInput.disabled = false;
shadowOffsetYInput.classList.remove('cf7-disabled');
}
if (shadowOpacitySlider) {
shadowOpacitySlider.disabled = false;
shadowOpacitySlider.classList.remove('cf7-disabled');
}
}
// CF7 Compatible: Disable font controls when no text is selected
disableFontControls() {
// Disable font family dropdown (custom font preview dropdown)
if (this.fontControls.fontFamily) {
// Check if it's the new custom font preview dropdown
if (this.fontControls.fontFamily.classList.contains('cf7-font-preview-dropdown')) {
this.fontControls.fontFamily.classList.add('cf7-disabled');
const button = this.fontControls.fontFamily.querySelector('.cf7-font-preview-button');
if (button) {
button.disabled = true;
}
// Also close the dropdown if it's open
const dropdownList = this.fontControls.fontFamily.querySelector('.cf7-font-preview-list');
if (dropdownList) {
dropdownList.classList.add('cf7-hidden');
button.setAttribute('aria-expanded', 'false');
}
} else {
// Handle regular select dropdown
this.fontControls.fontFamily.disabled = true;
this.fontControls.fontFamily.classList.add('cf7-disabled');
}
}
// Disable font size input
if (this.fontControls.fontSize) {
this.fontControls.fontSize.disabled = true;
this.fontControls.fontSize.classList.add('cf7-disabled');
}
// Disable style buttons
if (this.fontControls.bold) {
this.fontControls.bold.disabled = true;
this.fontControls.bold.classList.add('cf7-disabled');
}
if (this.fontControls.italic) {
this.fontControls.italic.disabled = true;
this.fontControls.italic.classList.add('cf7-disabled');
}
// Disable text alignment buttons
if (this.fontControls.alignLeft) {
this.fontControls.alignLeft.disabled = true;
this.fontControls.alignLeft.classList.add('cf7-disabled');
}
if (this.fontControls.alignCenter) {
this.fontControls.alignCenter.disabled = true;
this.fontControls.alignCenter.classList.add('cf7-disabled');
}
if (this.fontControls.alignRight) {
this.fontControls.alignRight.disabled = true;
this.fontControls.alignRight.classList.add('cf7-disabled');
}
if (this.fontControls.alignJustify) {
this.fontControls.alignJustify.disabled = true;
this.fontControls.alignJustify.classList.add('cf7-disabled');
}
// Disable color picker
if (this.fontControls.color) {
this.fontControls.color.disabled = true;
this.fontControls.color.classList.add('cf7-disabled');
}
// Disable text shadow controls
if (this.fontControls.textShadow) {
this.fontControls.textShadow.disabled = true;
this.fontControls.textShadow.classList.add('cf7-disabled');
}
// Disable shadow controls
const shadowColorPicker = this.container.querySelector('.cf7-color-picker[data-shadow="color"]');
const shadowBlurInput = this.container.querySelector('.cf7-input-shadow-blur');
const shadowOffsetXInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="x"]');
const shadowOffsetYInput = this.container.querySelector('.cf7-input-shadow-offset[data-axis="y"]');
const shadowOpacitySlider = this.container.querySelector('.cf7-range-slider[data-shadow-opacity="true"]');
if (shadowColorPicker) {
shadowColorPicker.disabled = true;
shadowColorPicker.classList.add('cf7-disabled');
}
if (shadowBlurInput) {
shadowBlurInput.disabled = true;
shadowBlurInput.classList.add('cf7-disabled');
}
if (shadowOffsetXInput) {
shadowOffsetXInput.disabled = true;
shadowOffsetXInput.classList.add('cf7-disabled');
}
if (shadowOffsetYInput) {
shadowOffsetYInput.disabled = true;
shadowOffsetYInput.classList.add('cf7-disabled');
}
if (shadowOpacitySlider) {
shadowOpacitySlider.disabled = true;
shadowOpacitySlider.classList.add('cf7-disabled');
}
}
rgbToHex(rgb) {
// Handle hex colors that are already in the right format
if (rgb.startsWith('#')) return rgb;
// Handle rgb() format
const result = rgb.match(/\d+/g);
if (result && result.length >= 3) {
const r = parseInt(result[0]);
const g = parseInt(result[1]);
const b = parseInt(result[2]);
return "#" + ((1 << 24) + (r << 16) + (g < ${newValue}`);
targetElement.style[property] = newValue;
// Don't call updateFontControlsFromElement as it might override our button state
// Instead, manually update the button state
if (button) {
const isActive = (newValue === activeValue);
button.classList.toggle('cf7-active', isActive);
console.log(`Button ${property} active state:`, isActive);
}
}
// CF7 Compatible: Set text alignment for selected text element
setTextAlignment(alignment) {
console.log('setTextAlignment called with:', alignment);
console.log('Selected element:', this.selectedElement);
// Get the alignment buttons for visual feedback
const alignButtons = {
left: this.fontControls.alignLeft,
center: this.fontControls.alignCenter,
right: this.fontControls.alignRight,
justify: this.fontControls.alignJustify
};
console.log('Alignment buttons:', alignButtons);
// If no element is selected, just update button visual state
if (!this.selectedElement || !this.selectedElement.classList.contains('cf7-draggable-text')) {
// Clear all alignment button active states
Object.values(alignButtons).forEach(btn => {
if (btn) btn.classList.remove('cf7-active');
});
// Set the clicked button as active
if (alignButtons[alignment]) {
alignButtons[alignment].classList.add('cf7-active');
}
return;
}
const editableContent = this.selectedElement.querySelector('.cf7-editable-content');
const targetElement = editableContent || this.selectedElement;
// Apply text alignment with !important to override any conflicting styles
targetElement.style.setProperty('text-align', alignment, 'important');
console.log(`Text alignment set to: ${alignment}`);
// For justify, add additional properties
if (alignment === 'justify') {
targetElement.style.setProperty('text-align-last', 'left', 'important');
targetElement.style.setProperty('word-spacing', 'normal', 'important');
targetElement.style.setProperty('letter-spacing', 'normal', 'important');
console.log('Justify alignment applied with additional properties');
} else {
// Remove justify-specific properties for other alignments
targetElement.style.removeProperty('text-align-last');
}
console.log('Element computed textAlign:', window.getComputedStyle(targetElement).textAlign);
console.log('Element style textAlign:', targetElement.style.textAlign);
// Update button visual states - only one alignment can be active at a time
console.log('Updating button states for alignment:', alignment);
Object.entries(alignButtons).forEach(([align, btn]) => {
if (btn) {
const shouldBeActive = align === alignment;
console.log(`Button ${align}: ${shouldBeActive ? 'ACTIVE' : 'inactive'}`);
if (shouldBeActive) {
btn.classList.add('cf7-active');
} else {
btn.classList.remove('cf7-active');
}
}
});
}
// Debug method to test button toggle
testBoldButtonToggle() {
if (this.fontControls.bold) {
console.log('Testing Bold button toggle...');
console.log('Before toggle - has cf7-active:', this.fontControls.bold.classList.contains('cf7-active'));
this.fontControls.bold.classList.toggle('cf7-active');
console.log('After toggle - has cf7-active:', this.fontControls.bold.classList.contains('cf7-active'));
console.log('Button classes:', this.fontControls.bold.className);
} else {
console.log('Bold button not found!');
}
}
// Text Shadow Methods - Following CF7 patterns
toggleTextShadow() {
if (!this.selectedElement || !this.selectedElement.classList.contains('cf7-draggable-text')) return;
const shadowToggle = this.container.querySelector('.cf7-btn-shadow');
if (!shadowToggle) return;
const isActive = shadowToggle.classList.contains('cf7-active');
const editableContent = this.selectedElement.querySelector('.cf7-editable-content');
const targetElement = editableContent || this.selectedElement;
if (isActive) {
// Remove text shadow
targetElement.style.textShadow = 'none';
this.selectedElement.classList.remove('cf7-has-shadow');
shadowToggle.classList.remove('cf7-active');
// Mark shadow as not applied
if (this.selectedElement.shadowConfig) {
this.selectedElement.shadowConfig.applied = false;
}
} else {
// Apply text shadow with current settings
this.applyTextShadowToSelected();
this.selectedElement.classList.add('cf7-has-shadow');
shadowToggle.classList.add('cf7-active');
}
}
updateTextShadow(property, value) {
if (!this.selectedElement || !this.selectedElement.classList.contains('cf7-draggable-text')) return;
const shadowToggle = this.container.querySelector('.cf7-btn-shadow');
if (!shadowToggle || !shadowToggle.classList.contains('cf7-active')) return;
// Store shadow properties on the element - CF7 Compatible
if (!this.selectedElement.shadowConfig) {
this.selectedElement.shadowConfig = {
color: '#000000',
blur: '2',
offsetX: '2',
offsetY: '2',
opacity: '100'
};
}
this.selectedElement.shadowConfig[property] = value;
this.applyTextShadowToSelected();
}
applyTextShadowToSelected() {
if (!this.selectedElement || !this.selectedElement.classList.contains('cf7-draggable-text')) return;
const config = this.selectedElement.shadowConfig || {
color: this.getShadowControlValue('color') || '#000000',
blur: this.getShadowControlValue('blur') || '2',
offsetX: this.getShadowControlValue('offsetX') || '2',
offsetY: this.getShadowControlValue('offsetY') || '2',
opacity: this.getShadowControlValue('opacity') || '100'
};
// Get current values from controls - CF7 Compatible
const shadowColor = config.color || '#000000';
const shadowBlur = config.blur || '2';
const shadowOffsetX = config.offsetX || '2';
const shadowOffsetY = config.offsetY || '2';
const shadowOpacity = config.opacity || '100';
// Convert hex color to rgba with opacity
const rgba = this.hexToRgba(shadowColor, shadowOpacity / 100);
// Apply text shadow using CSS text-shadow property with opacity
const textShadow = `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px ${rgba}`;
// Apply to editable content if it exists, otherwise to the element
const editableContent = this.selectedElement.querySelector('.cf7-editable-content');
if (editableContent) {
editableContent.style.textShadow = textShadow;
} else {
this.selectedElement.style.textShadow = textShadow;
}
// Store the shadow config on the element for persistence
this.selectedElement.shadowConfig = {
...config,
applied: true
};
}
getShadowControlValue(property) {
const controls = {
color: this.container.querySelector('.cf7-color-picker[data-shadow="color"]'),
blur: this.container.querySelector('.cf7-input-shadow-blur'),
offsetX: this.container.querySelector('.cf7-input-shadow-offset[data-axis="x"]'),
offsetY: this.container.querySelector('.cf7-input-shadow-offset[data-axis="y"]'),
opacity: this.container.querySelector('.cf7-range-slider[data-shadow-opacity="true"]')
};
const control = controls[property];
if (!control) return null;
return control.value || null;
}
// CF7 Helper: Convert hex color to rgba with opacity
hexToRgba(hex, opacity = 1) {
// Remove # if present
hex = hex.replace('#', '');
// Parse hex values
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
// Background Template Methods
updateTemplateOptions(category) {
console.log('Updating template options for category:', category);
if (!category) {
// Reset template dropdown
if (this.backgroundControls.template) {
this.backgroundControls.template.innerHTML = 'Select Template...';
}
return;
}
const templates = this.backgroundTemplates[category] || [];
console.log('Templates found:', templates);
const templateOptions = [
{ value: '', text: 'Select Template...' },
...templates.map(template => ({ value: template.url, text: template.name }))
];
if (this.backgroundControls.template) {
this.backgroundControls.template.innerHTML = '';
templateOptions.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.text;
this.backgroundControls.template.appendChild(optionElement);
});
console.log('Template dropdown updated with', templateOptions.length, 'options');
}
}
setBackgroundImage(imageUrl) {
console.log('Setting background image:', imageUrl);
const backgroundElement = document.getElementById('cf7-canvas-bg');
console.log('Background element found:', backgroundElement);
if (backgroundElement) {
if (imageUrl) {
backgroundElement.style.backgroundImage = `url("${imageUrl}")`;
console.log('Background image set to:', backgroundElement.style.backgroundImage);
} else {
backgroundElement.style.backgroundImage = '';
console.log('Background image cleared');
}
} else {
console.error('Background element not found!');
}
}
clearBackground() {
this.setBackgroundImage('');
if (this.modal) {
this.modal.selectedCategory = null;
this.modal.selectedTemplate = null;
}
}
// Modal Management Methods
openBackgroundModal() {
if (!this.modal.element) return;
// Store the currently focused element
this.modal.focusedElementBeforeModal = document.activeElement;
// Show the modal
this.modal.element.showModal();
// WordPress compatibility fix - Force center positioning and content visibility
setTimeout(() => {
if (this.modal.element) {
// Fix modal positioning
this.modal.element.style.position = 'fixed';
this.modal.element.style.top = '50%';
this.modal.element.style.left = '50%';
this.modal.element.style.transform = 'translate(-50%, -50%)';
this.modal.element.style.zIndex = '999999';
this.modal.element.style.margin = '0';
this.modal.element.style.inset = 'auto';
this.modal.element.style.visibility = 'visible';
this.modal.element.style.opacity = '1';
// Fix modal content visibility
const modalContent = this.modal.element.querySelector('.cf7-modal-content');
if (modalContent) {
modalContent.style.display = 'flex';
modalContent.style.flexDirection = 'column';
modalContent.style.height = '100%';
modalContent.style.width = '100%';
}
// Fix modal body visibility
const modalBody = this.modal.element.querySelector('.cf7-modal-body');
if (modalBody) {
modalBody.style.display = 'block';
modalBody.style.visibility = 'visible';
modalBody.style.opacity = '1';
modalBody.style.overflow = 'auto';
}
// Fix category grid visibility
const categoryGrid = this.modal.element.querySelector('.cf7-category-grid');
if (categoryGrid) {
categoryGrid.style.display = 'grid';
categoryGrid.style.visibility = 'visible';
categoryGrid.style.opacity = '1';
}
}
}, 10);
// Reset modal state
this.resetModalState();
// Focus the first focusable element in the modal
this.focusFirstModalElement();
// Populate categories if not already done
this.populateCategories();
}
closeBackgroundModal() {
if (!this.modal.element) return;
// Close the modal
this.modal.element.close();
// Return focus to the trigger button
if (this.modal.focusedElementBeforeModal) {
this.modal.focusedElementBeforeModal.focus();
}
// Reset modal state
this.resetModalState();
}
resetModalState() {
// Reset selections
this.modal.selectedCategory = null;
this.modal.selectedTemplate = null;
// WordPress-compatible step reset - Show category step, hide template step
const categoryStep = document.getElementById('cf7-step-category');
const templateStep = document.getElementById('cf7-step-template');
// Ensure category step is fully visible
if (categoryStep) {
categoryStep.style.display = 'block';
categoryStep.style.visibility = 'visible';
categoryStep.style.opacity = '1';
categoryStep.style.position = 'relative';
categoryStep.style.left = 'auto';
categoryStep.style.height = 'auto';
categoryStep.style.overflow = 'visible';
}
// Ensure template step is fully hidden
if (templateStep) {
templateStep.style.display = 'none';
templateStep.style.visibility = 'hidden';
templateStep.style.opacity = '0';
templateStep.style.position = 'absolute';
templateStep.style.left = '-9999px';
templateStep.style.height = '0';
templateStep.style.overflow = 'hidden';
}
// Disable apply button
if (this.modal.apply) {
this.modal.apply.disabled = true;
}
// Clear selections
this.clearModalSelections();
}
clearModalSelections() {
// Clear category selections
const categoryItems = document.querySelectorAll('.cf7-category-item');
categoryItems.forEach(item => item.classList.remove('selected'));
// Clear template selections
const templateItems = document.querySelectorAll('.cf7-template-item');
templateItems.forEach(item => item.classList.remove('selected'));
}
focusFirstModalElement() {
const firstFocusable = this.modal.element.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (firstFocusable) {
firstFocusable.focus();
}
}
setupModalContent() {
// Setup back button
const backButton = document.getElementById('cf7-back-to-categories');
if (backButton) {
backButton.addEventListener('click', () => this.showCategoryStep());
}
}
setupModalKeyboardNavigation() {
if (!this.modal.element) return;
this.modal.element.addEventListener('keydown', (e) => {
// Close modal on Escape key
if (e.key === 'Escape') {
e.preventDefault();
this.closeBackgroundModal();
return;
}
// Handle Tab key for focus trapping
if (e.key === 'Tab') {
this.trapFocus(e);
}
});
}
trapFocus(e) {
const focusableElements = this.modal.element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
// Tab
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}
// Modal Content Population Methods
populateCategories() {
const categoryGrid = document.getElementById('cf7-category-grid');
if (!categoryGrid) return;
// Clear existing categories
categoryGrid.innerHTML = '';
// Create category items
const categories = Object.keys(this.backgroundTemplates);
categories.forEach((category, index) => {
const categoryItem = document.createElement('div');
categoryItem.className = 'cf7-category-item';
categoryItem.setAttribute('role', 'radio');
categoryItem.setAttribute('aria-checked', 'false');
categoryItem.setAttribute('tabindex', index === 0 ? '0' : '-1');
categoryItem.dataset.category = category;
categoryItem.innerHTML = `
${category}
${this.backgroundTemplates[category].length} templates
`;
// Add click handler
categoryItem.addEventListener('click', () => this.selectCategory(category, categoryItem));
// Add keyboard handler
categoryItem.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.selectCategory(category, categoryItem);
}
this.handleGridNavigation(e, categoryGrid);
});
categoryGrid.appendChild(categoryItem);
});
}
selectCategory(category, categoryElement) {
// Update selection state
this.modal.selectedCategory = category;
this.modal.selectedTemplate = null;
// Update UI
this.clearModalSelections();
categoryElement.classList.add('selected');
categoryElement.setAttribute('aria-checked', 'true');
// Show template step
this.showTemplateStep(category);
}
showTemplateStep(category) {
const categoryStep = document.getElementById('cf7-step-category');
const templateStep = document.getElementById('cf7-step-template');
// WordPress-compatible step switching - Hide category step completely
if (categoryStep) {
categoryStep.style.display = 'none';
categoryStep.style.visibility = 'hidden';
categoryStep.style.opacity = '0';
categoryStep.style.position = 'absolute';
categoryStep.style.left = '-9999px';
categoryStep.style.height = '0';
categoryStep.style.overflow = 'hidden';
}
// Show template step completely
if (templateStep) {
templateStep.style.display = 'block';
templateStep.style.visibility = 'visible';
templateStep.style.opacity = '1';
templateStep.style.position = 'relative';
templateStep.style.left = 'auto';
templateStep.style.height = 'auto';
templateStep.style.overflow = 'visible';
}
// Populate templates
this.populateTemplates(category);
// Focus the back button
const backButton = document.getElementById('cf7-back-to-categories');
if (backButton) {
setTimeout(() => backButton.focus(), 100);
}
}
showCategoryStep() {
const categoryStep = document.getElementById('cf7-step-category');
const templateStep = document.getElementById('cf7-step-template');
// WordPress-compatible step switching - Show category step completely
if (categoryStep) {
categoryStep.style.display = 'block';
categoryStep.style.visibility = 'visible';
categoryStep.style.opacity = '1';
categoryStep.style.position = 'relative';
categoryStep.style.left = 'auto';
categoryStep.style.height = 'auto';
categoryStep.style.overflow = 'visible';
}
// Hide template step completely
if (templateStep) {
templateStep.style.display = 'none';
templateStep.style.visibility = 'hidden';
templateStep.style.opacity = '0';
templateStep.style.position = 'absolute';
templateStep.style.left = '-9999px';
templateStep.style.height = '0';
templateStep.style.overflow = 'hidden';
}
// Reset template selection
this.modal.selectedTemplate = null;
if (this.modal.apply) {
this.modal.apply.disabled = true;
}
// Focus the selected category
const selectedCategory = document.querySelector('.cf7-category-item.selected');
if (selectedCategory) {
setTimeout(() => selectedCategory.focus(), 100);
}
}
populateTemplates(category) {
const templateGrid = document.getElementById('cf7-template-grid');
if (!templateGrid) return;
// Clear existing templates
templateGrid.innerHTML = '';
// Get templates for category
const templates = this.backgroundTemplates[category] || [];
// Create template items
templates.forEach((template, index) => {
const templateItem = document.createElement('div');
templateItem.className = 'cf7-template-item';
templateItem.setAttribute('role', 'radio');
templateItem.setAttribute('aria-checked', 'false');
templateItem.setAttribute('tabindex', index === 0 ? '0' : '-1');
templateItem.dataset.templateUrl = template.url;
templateItem.innerHTML = `
${template.name}
`;
// Add click handler
templateItem.addEventListener('click', () => this.selectTemplate(template.url, templateItem));
// Add keyboard handler
templateItem.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.selectTemplate(template.url, templateItem);
}
this.handleGridNavigation(e, templateGrid);
});
templateGrid.appendChild(templateItem);
});
}
selectTemplate(templateUrl, templateElement) {
// Update selection state
this.modal.selectedTemplate = templateUrl;
// Update UI
const templateItems = document.querySelectorAll('.cf7-template-item');
templateItems.forEach(item => {
item.classList.remove('selected');
item.setAttribute('aria-checked', 'false');
});
templateElement.classList.add('selected');
templateElement.setAttribute('aria-checked', 'true');
// Enable apply button
if (this.modal.apply) {
this.modal.apply.disabled = false;
}
}
handleGridNavigation(e, grid) {
const items = Array.from(grid.children);
const currentIndex = items.indexOf(document.activeElement);
let newIndex = currentIndex;
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
newIndex = (currentIndex + 1) % items.length;
break;
case 'ArrowLeft':
e.preventDefault();
newIndex = (currentIndex - 1 + items.length) % items.length;
break;
case 'ArrowDown':
e.preventDefault();
// Move to next row (approximate)
newIndex = Math.min(currentIndex + 3, items.length - 1);
break;
case 'ArrowUp':
e.preventDefault();
// Move to previous row (approximate)
newIndex = Math.max(currentIndex - 3, 0);
break;
case 'Home':
e.preventDefault();
newIndex = 0;
break;
case 'End':
e.preventDefault();
newIndex = items.length - 1;
break;
}
if (newIndex !== currentIndex && items[newIndex]) {
// Update tabindex
items.forEach((item, index) => {
item.setAttribute('tabindex', index === newIndex ? '0' : '-1');
});
items[newIndex].focus();
}
}
applySelectedBackground() {
if (this.modal.selectedTemplate) {
this.setBackgroundImage(this.modal.selectedTemplate);
this.closeBackgroundModal();
}
}
deleteElement(element) {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
if (this.selectedElement === element) {
this.selectedElement = null;
this.resetFontControls();
}
}
clearCanvas() {
if (confirm('Are you sure you want to clear all elements?')) {
this.elementsContainer.innerHTML = '';
this.selectedElement = null;
this.elementCounter = 0;
}
}
// CF7 Export Functionality - Configurable High Quality Export with Visual Fidelity
async exportCanvasAsPNG() {
try {
// Get export configuration from UI
const qualitySelect = document.getElementById('cf7-export-quality');
const formatSelect = document.getElementById('cf7-export-format');
const quality = qualitySelect ? qualitySelect.value : 'standard';
const format = formatSelect ? formatSelect.value : 'png';
// Show loading indicator
this.showExportProgress('Preparing export...');
// Handle background image embedding first
await this.prepareBackgroundForExport();
// Temporarily hide selection indicators and resize handles for clean export
const selectedElements = this.canvas.querySelectorAll('.cf7-selected');
const resizeHandles = this.canvas.querySelectorAll('.cf7-resize-handle');
const deleteButtons = this.canvas.querySelectorAll('.cf7-delete-btn');
const editingElements = this.canvas.querySelectorAll('.cf7-editing');
// Store original states and clean up all visual editing indicators
const originalStates = [];
const allDraggableElements = this.canvas.querySelectorAll('.cf7-draggable-text, .cf7-draggable-image');
// Store and clean up all draggable elements
[...selectedElements, ...resizeHandles, ...deleteButtons, ...editingElements, ...allDraggableElements].forEach(el => {
originalStates.push({
element: el,
className: el.className,
display: el.style.display,
border: el.style.border,
background: el.style.background,
cursor: el.style.cursor,
outline: el.style.outline,
boxShadow: el.style.boxShadow
});
});
// Remove all editing and selection states
[...selectedElements, ...editingElements].forEach(el => {
el.classList.remove('cf7-selected', 'cf7-editing');
});
// Hide UI control elements
[...resizeHandles, ...deleteButtons].forEach(el => {
el.style.display = 'none';
});
// Clean up ALL visual editing indicators from draggable elements
allDraggableElements.forEach(el => {
// Remove all editor-related classes temporarily
el.classList.remove('cf7-selected', 'cf7-editing');
// Clear all visual editing styles
el.style.border = 'none';
el.style.background = 'transparent';
el.style.cursor = 'default';
el.style.outline = 'none';
el.style.boxShadow = 'none';
// Remove hover effects by adding a temporary class
el.classList.add('cf7-export-mode');
});
// Get canvas dimensions for proper scaling
const canvasRect = this.canvas.getBoundingClientRect();
const canvasWidth = parseInt(this.canvas.dataset.width) || 800;
const canvasHeight = parseInt(this.canvas.dataset.height) || 400;
this.showExportProgress('Capturing image...');
// Configure quality settings
let pixelRatio, canvasMultiplier, jpegQuality;
switch (quality) {
case 'high':
pixelRatio = 3;
canvasMultiplier = 3;
jpegQuality = 0.95;
break;
case 'web':
pixelRatio = 1;
canvasMultiplier = 1;
jpegQuality = 0.8;
break;
default: // standard
pixelRatio = 2;
canvasMultiplier = 2;
jpegQuality = 0.9;
}
// Configure export options for maximum visual fidelity
const exportOptions = {
quality: jpegQuality,
pixelRatio: pixelRatio,
backgroundColor: format === 'png' ? 'transparent' : '#ffffff',
cacheBust: true,
width: canvasWidth,
height: canvasHeight,
canvasWidth: canvasWidth * canvasMultiplier,
canvasHeight: canvasHeight * canvasMultiplier,
style: {
// Preserve all visual styles exactly as they appear
transform: 'scale(1)',
transformOrigin: 'top left',
width: canvasWidth + 'px',
height: canvasHeight + 'px'
},
filter: (node) => {
// Exclude UI elements that shouldn't be in the export
if (node.classList) {
return !node.classList.contains('cf7-delete-btn') &&
!node.classList.contains('cf7-resize-handle') &&
!node.classList.contains('cf7-selected') &&
!node.classList.contains('cf7-editing') &&
!node.hasAttribute('data-cf7-ui-element');
}
return true;
},
// Handle CORS issues gracefully
fontEmbedCSS: null,
includeQueryParams: false,
skipAutoScale: false,
// Use a placeholder for failed images instead of breaking
imagePlaceholder: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjBmMGYwIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkJhY2tncm91bmQgSW1hZ2U8L3RleHQ+PC9zdmc+',
// Don't use CORS for external images to avoid blocking
useCORS: false
};
// Export based on selected format
let dataUrl;
if (format === 'jpeg') {
dataUrl = await htmlToImage.toJpeg(this.canvas, exportOptions);
} else {
dataUrl = await htmlToImage.toPng(this.canvas, exportOptions);
}
this.showExportProgress('Downloading...');
// Create download link with descriptive filename
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
const qualityLabel = quality.charAt(0).toUpperCase() + quality.slice(1);
const link = document.createElement('a');
link.download = `cf7-billboard-${qualityLabel}-${timestamp}.${format}`;
link.href = dataUrl;
// Trigger download
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Restore original states
originalStates.forEach(state => {
state.element.className = state.className;
state.element.style.display = state.display;
state.element.style.border = state.border;
state.element.style.background = state.background;
state.element.style.cursor = state.cursor;
state.element.style.outline = state.outline;
state.element.style.boxShadow = state.boxShadow;
});
// Remove export mode class from all elements
const exportModeElements = this.canvas.querySelectorAll('.cf7-export-mode');
exportModeElements.forEach(el => {
el.classList.remove('cf7-export-mode');
});
// Restore background after export
await this.restoreBackgroundAfterExport();
this.hideExportProgress();
// Check if we have a background image and show appropriate message
const backgroundElement = document.getElementById('cf7-canvas-bg');
const hasBackgroundImage = backgroundElement &&
backgroundElement.style.backgroundImage &&
backgroundElement.style.backgroundImage !== 'none';
if (hasBackgroundImage) {
this.showExportWarning(`${format.toUpperCase()} exported (${qualityLabel} quality). Note: Background images from external sources may not appear due to browser security restrictions. For best results, use local images or serve from the same domain.`);
} else {
this.showExportSuccess(`${format.toUpperCase()} exported successfully (${qualityLabel} quality)`);
}
console.log(`CF7 Export: ${format.toUpperCase()} exported successfully with ${quality} quality`);
} catch (error) {
console.error('CF7 Export Error:', error);
this.hideExportProgress();
this.showExportError(error.message);
// Restore states in case of error
if (originalStates && originalStates.length > 0) {
originalStates.forEach(state => {
state.element.className = state.className;
state.element.style.display = state.display;
state.element.style.border = state.border;
state.element.style.background = state.background;
state.element.style.cursor = state.cursor;
state.element.style.outline = state.outline;
state.element.style.boxShadow = state.boxShadow;
});
} else {
// Fallback restoration
const allElements = this.canvas.querySelectorAll('*');
allElements.forEach(el => {
el.style.display = '';
el.style.border = '';
el.style.background = '';
el.style.cursor = '';
el.style.outline = '';
el.style.boxShadow = '';
el.classList.remove('cf7-export-mode');
});
}
// Remove export mode class from all elements
const exportModeElements = this.canvas.querySelectorAll('.cf7-export-mode');
exportModeElements.forEach(el => {
el.classList.remove('cf7-export-mode');
});
// Restore background in case of error
await this.restoreBackgroundAfterExport();
}
}
// Export progress and feedback methods
showExportProgress(message) {
// Remove existing progress indicator
this.hideExportProgress();
const progressDiv = document.createElement('div');
progressDiv.id = 'cf7-export-progress';
progressDiv.className = 'cf7-export-progress-message';
progressDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #007cba, #0056b3);
color: white;
padding: 20px 25px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 124, 186, 0.3);
z-index: 999999 !important;
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: 600;
animation: slideInRight 0.5s ease-out;
max-width: 300px;
`;
progressDiv.innerHTML = `
Processing...
${message}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
// Ensure animation keyframes are available
if (!document.querySelector('#cf7-chip-animations')) {
const style = document.createElement('style');
style.id = 'cf7-chip-animations';
style.textContent = `
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(progressDiv);
}
hideExportProgress() {
const progressDiv = document.getElementById('cf7-export-progress');
if (progressDiv) {
progressDiv.remove();
}
}
showExportSuccess(message) {
// Remove any existing success messages
const existingMessages = document.querySelectorAll('.cf7-export-success-message');
existingMessages.forEach(msg => msg.remove());
const successDiv = document.createElement('div');
successDiv.className = 'cf7-export-success-message';
successDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
z-index: 999999 !important;
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: 600;
animation: slideInRight 0.5s ease-out;
max-width: 300px;
`;
successDiv.innerHTML = `
✅
Export Success!
${message || 'Image exported successfully!'}
`;
// Add animation keyframes if not already added
if (!document.querySelector('#cf7-chip-animations')) {
const style = document.createElement('style');
style.id = 'cf7-chip-animations';
style.textContent = `
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(successDiv);
// Auto-remove after 4 seconds with fade out
setTimeout(() => {
successDiv.style.animation = 'fadeOut 0.5s ease-out';
setTimeout(() => {
if (successDiv.parentNode) {
successDiv.remove();
}
}, 500);
}, 4000);
}
showExportWarning(message) {
// Remove any existing warning messages
const existingMessages = document.querySelectorAll('.cf7-export-warning-message');
existingMessages.forEach(msg => msg.remove());
const warningDiv = document.createElement('div');
warningDiv.className = 'cf7-export-warning-message';
warningDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #ffc107, #ffca2c);
color: #212529;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.3);
z-index: 999999 !important;
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: 600;
animation: slideInRight 0.5s ease-out;
max-width: 300px;
`;
warningDiv.innerHTML = `
⚠️
Export Notice
${message}
`;
// Ensure animation keyframes are available
if (!document.querySelector('#cf7-chip-animations')) {
const style = document.createElement('style');
style.id = 'cf7-chip-animations';
style.textContent = `
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(warningDiv);
// Auto-remove after 6 seconds with fade out
setTimeout(() => {
warningDiv.style.animation = 'fadeOut 0.5s ease-out';
setTimeout(() => {
if (warningDiv.parentNode) {
warningDiv.remove();
}
}, 500);
}, 6000);
}
showExportError(errorMessage) {
// Remove any existing error messages
const existingMessages = document.querySelectorAll('.cf7-export-error-message');
existingMessages.forEach(msg => msg.remove());
const errorDiv = document.createElement('div');
errorDiv.className = 'cf7-export-error-message';
errorDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #dc3545, #c82333);
color: white;
padding: 15px 25px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
z-index: 999999 !important;
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: 600;
animation: slideInRight 0.5s ease-out;
max-width: 300px;
`;
errorDiv.innerHTML = `
❌
Export Failed
${errorMessage}
`;
// Ensure animation keyframes are available
if (!document.querySelector('#cf7-chip-animations')) {
const style = document.createElement('style');
style.id = 'cf7-chip-animations';
style.textContent = `
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(errorDiv);
// Auto-remove after 8 seconds with fade out
setTimeout(() => {
errorDiv.style.animation = 'fadeOut 0.5s ease-out';
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.remove();
}
}, 500);
}, 8000);
}
// Background image handling for export
async prepareBackgroundForExport() {
const backgroundElement = document.getElementById('cf7-canvas-bg');
if (!backgroundElement) return;
const currentBackgroundImage = backgroundElement.style.backgroundImage;
if (!currentBackgroundImage || currentBackgroundImage === 'none') return;
// Extract URL from CSS background-image property
const urlMatch = currentBackgroundImage.match(/url\(["']?([^"')]+)["']?\)/);
if (!urlMatch) return;
const imageUrl = urlMatch[1];
console.log('Preparing background image for export:', imageUrl);
try {
// Store original background settings
this.originalBackgroundSettings = {
backgroundImage: currentBackgroundImage,
backgroundSize: backgroundElement.style.backgroundSize || 'cover',
backgroundPosition: backgroundElement.style.backgroundPosition || 'center',
backgroundRepeat: backgroundElement.style.backgroundRepeat || 'no-repeat',
innerHTML: backgroundElement.innerHTML
};
// For CORS-restricted images, use a different approach
// Create an img element that loads without CORS restrictions
const img = document.createElement('img');
img.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
z-index: 0;
pointer-events: none;
`;
// Load image - for external images, this may not work due to CORS
img.src = imageUrl;
// Wait for image to load
await new Promise((resolve, reject) => {
img.onload = () => {
console.log('Background image loaded successfully for export');
resolve();
};
img.onerror = () => {
console.warn('Background image failed to load, using placeholder');
// Create a placeholder if image fails to load
img.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48cGF0dGVybiBpZD0iZ3JpZCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNIDIwIDAgTCAwIDAgMCAyMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZGRkIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE4IiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+QmFja2dyb3VuZCBJbWFnZTwvdGV4dD48L3N2Zz4=';
resolve();
};
// Set a timeout to avoid hanging
setTimeout(() => {
if (!img.complete) {
console.warn('Background image load timeout, using placeholder');
img.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48cGF0dGVybiBpZD0iZ3JpZCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNIDIwIDAgTCAwIDAgMCAyMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZGRkIiBzdHJva2Utd2lkdGg9IjEiLz48L3BhdHRlcm4+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JpZCkiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE4IiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+QmFja2dyb3VuZCBJbWFnZTwvdGV4dD48L3N2Zz4=';
resolve();
}
}, 5000);
});
// Clear background image and add img element
backgroundElement.style.backgroundImage = 'none';
backgroundElement.appendChild(img);
console.log('Background image replaced with img element for export');
} catch (error) {
console.warn('Failed to prepare background image for export:', error);
// Continue with original - html-to-image might still capture it
}
}
async restoreBackgroundAfterExport() {
if (!this.originalBackgroundSettings) return;
const backgroundElement = document.getElementById('cf7-canvas-bg');
if (!backgroundElement) return;
// Remove any img elements that were added for export
const exportImages = backgroundElement.querySelectorAll('img');
exportImages.forEach(img => img.remove());
// Restore original background settings
backgroundElement.style.backgroundImage = this.originalBackgroundSettings.backgroundImage;
backgroundElement.style.backgroundSize = this.originalBackgroundSettings.backgroundSize;
backgroundElement.style.backgroundPosition = this.originalBackgroundSettings.backgroundPosition;
backgroundElement.style.backgroundRepeat = this.originalBackgroundSettings.backgroundRepeat;
backgroundElement.innerHTML = this.originalBackgroundSettings.innerHTML;
// Clear stored settings
this.originalBackgroundSettings = null;
console.log('Background image restored after export');
}
// Convert image URL to data URL for reliable embedding
async imageToDataUrl(url) {
return new Promise((resolve, reject) => {
const img = new Image();
// Try with CORS first
img.crossOrigin = 'anonymous';
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
try {
const dataUrl = canvas.toDataURL('image/png');
resolve(dataUrl);
} catch (error) {
// If canvas is tainted, try without CORS
console.warn('Canvas tainted, retrying without CORS');
reject(error);
}
};
img.onerror = function() {
// If CORS fails, try without crossOrigin
console.warn('CORS failed, retrying without crossOrigin');
const img2 = new Image();
img2.onload = function() {
// For non-CORS images, we can't convert to data URL
// but html-to-image might still capture them
reject(new Error('CORS not available for image: ' + url));
};
img2.onerror = function() {
reject(new Error('Failed to load image: ' + url));
};
img2.src = url;
};
// Add cache busting parameter
const separator = url.includes('?') ? '&' : '?';
img.src = url + separator + '_export=' + Date.now();
});
}
removeBackgroundImage() {
const backgroundElement = this.container.querySelector('#cf7-canvas-bg');
backgroundElement.style.backgroundImage = '';
}
}
// CF7 Style Initialization - Following CF7 patterns
document.addEventListener('DOMContentLoaded', function() {
// Initialize all CF7 text editors on the page
const editors = document.querySelectorAll('.cf7-text-editor-container');
editors.forEach(container => {
const editor = new CF7TextEditor(container);
// Add to window for debugging
if (!window.cf7Editors) window.cf7Editors = [];
window.cf7Editors.push(editor);
});
// Add global test function
window.testBoldButton = function() {
if (window.cf7Editors && window.cf7Editors[0]) {
window.cf7Editors[0].testBoldButtonToggle();
} else {
console.log('No CF7 editors found');
}
};
// Add test function for alignment buttons
window.testAlignButtons = function() {
if (window.cf7Editors && window.cf7Editors[0]) {
const editor = window.cf7Editors[0];
console.log('Font controls object:', editor.fontControls);
console.log('Align Left button:', editor.fontControls.alignLeft);
console.log('Align Center button:', editor.fontControls.alignCenter);
console.log('Align Right button:', editor.fontControls.alignRight);
console.log('Align Justify button:', editor.fontControls.alignJustify);
} else {
console.log('No CF7 editors found');
}
};
// Add test function to directly call setTextAlignment
window.testSetAlignment = function(alignment) {
if (window.cf7Editors && window.cf7Editors[0]) {
console.log('Calling setTextAlignment with:', alignment);
window.cf7Editors[0].setTextAlignment(alignment);
} else {
console.log('No CF7 editors found');
}
};
// Add test function specifically for justify
window.testJustify = function() {
if (window.cf7Editors && window.cf7Editors[0]) {
const editor = window.cf7Editors[0];
if (editor.selectedElement) {
const element = editor.selectedElement;
const editableContent = element.querySelector('.cf7-editable-content');
const targetElement = editableContent || element;
console.log('Testing justify on element:', targetElement);
console.log('Current text:', targetElement.textContent);
console.log('Element width:', targetElement.offsetWidth);
console.log('Element height:', targetElement.offsetHeight);
// Force justify with important
targetElement.style.setProperty('text-align', 'justify', 'important');
targetElement.style.setProperty('text-align-last', 'left', 'important');
console.log('After setting justify:');
console.log('Computed textAlign:', window.getComputedStyle(targetElement).textAlign);
console.log('Style textAlign:', targetElement.style.textAlign);
} else {
console.log('No element selected');
}
} else {
console.log('No CF7 editors found');
}
};
});
// Checkout Dialog Functions
function openCheckoutDialog() {
console.log('Opening checkout dialog...');
// Validate that we have the required data from the location form
const requiredData = {
purpose: localStorage.getItem('selectedPurpose') || 'custom',
state: localStorage.getItem('selectedState'),
city: localStorage.getItem('selectedCity'),
location: localStorage.getItem('billboardLocation'),
dates: localStorage.getItem('runDates')
};
console.log('Checkout data validation:', requiredData);
// Check for missing required fields
const missingFields = [];
if (!requiredData.state) missingFields.push('State');
if (!requiredData.city) missingFields.push('City');
if (!requiredData.location) missingFields.push('Billboard Location');
if (!requiredData.dates) missingFields.push('Run Dates');
if (missingFields.length > 0) {
alert(`Please complete the following required information first:\n\n• ${missingFields.join('\n• ')}\n\nPlease fill out the location and dates form above before proceeding to checkout.`);
return;
}
// Capture the current canvas image with high quality
try {
const canvas = document.getElementById('cf7-canvas');
if (canvas) {
// Create a temporary canvas for high-quality export
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
// Set high resolution (2x for quality)
const scale = 2;
tempCanvas.width = canvas.offsetWidth * scale;
tempCanvas.height = canvas.offsetHeight * scale;
tempCtx.scale(scale, scale);
// Draw the canvas content
const canvasRect = canvas.getBoundingClientRect();
tempCtx.drawImage(canvas, 0, 0, canvas.offsetWidth, canvas.offsetHeight);
// Get high-quality image data
const imageDataURL = tempCanvas.toDataURL('image/png', 1.0);
localStorage.setItem('billboardCanvasImage', imageDataURL);
localStorage.setItem('adPreviewImage', imageDataURL);
console.log('Canvas image captured for checkout:', imageDataURL.length, 'bytes');
}
} catch (error) {
console.error('Error capturing canvas image:', error);
}
// Update dialog content
updateCheckoutDialogContent(requiredData);
// Show the dialog using modal structure (same as background modal)
const dialog = document.getElementById('checkoutDialog');
if (dialog) {
try {
dialog.showModal();
// Ensure proper centering
dialog.style.top = '50%';
dialog.style.left = '50%';
dialog.style.transform = 'translate(-50%, -50%)';
dialog.style.margin = 'auto';
console.log('Checkout dialog displayed using modal structure');
} catch (error) {
// Fallback for browsers that don't support showModal
dialog.style.display = 'flex';
dialog.style.visibility = 'visible';
dialog.style.opacity = '1';
dialog.style.top = '50%';
dialog.style.left = '50%';
dialog.style.transform = 'translate(-50%, -50%)';
dialog.style.margin = 'auto';
document.body.style.overflow = 'hidden';
console.log('Checkout dialog displayed using fallback method');
}
}
}
function updateCheckoutDialogContent(requiredData) {
try {
// Purpose field is hidden - no need to update it
// Update location
const locationElement = document.getElementById('dialogSummaryLocation');
if (locationElement) {
const fullLocation = requiredData.location || `${requiredData.state}, ${requiredData.city}`;
locationElement.textContent = fullLocation;
}
// Update dates
const datesElement = document.getElementById('dialogSummaryDates');
if (datesElement) {
datesElement.textContent = requiredData.dates || 'Not specified';
}
// Calculate duration
const durationElement = document.getElementById('dialogSummaryDuration');
if (durationElement) {
const days = calculateDays(requiredData.dates);
durationElement.textContent = `${days} day${days !== 1 ? 's' : ''}`;
}
// Calculate cost (basic calculation - $50 per day)
const costElement = document.getElementById('dialogSummaryCost');
if (costElement) {
const days = calculateDays(requiredData.dates);
const dailyRate = 50; // $50 per day
const total = days * dailyRate;
costElement.textContent = `$${total.toFixed(2)}`;
// Store total cost for payment page
localStorage.setItem('totalCost', total.toFixed(2));
}
console.log('Checkout dialog content updated');
} catch (error) {
console.error('Error updating checkout dialog content:', error);
}
}
function calculateDays(dateRange) {
if (!dateRange || dateRange === 'Not selected' || dateRange === 'Not specified') return 7;
try {
const dates = dateRange.split(' - ');
if (dates.length === 2) {
const startDate = new Date(dates[0]);
const endDate = new Date(dates[1]);
const timeDiff = endDate.getTime() - startDate.getTime();
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)) + 1; // +1 to include both start and end dates
return Math.max(1, daysDiff); // Minimum 1 day
}
} catch (error) {
console.error('Error calculating days:', error);
}
return 7; // Default to 7 days if calculation fails
}
function closeCheckoutDialog() {
const dialog = document.getElementById('checkoutDialog');
if (dialog) {
try {
dialog.close();
console.log('Checkout dialog closed using modal structure');
} catch (error) {
// Fallback for browsers that don't support close
dialog.style.display = 'none';
dialog.style.visibility = 'hidden';
dialog.style.opacity = '0';
document.body.style.overflow = '';
console.log('Checkout dialog closed using fallback method');
}
}
}
function proceedToPayment() {
console.log('Proceeding to payment...');
// Validate all required checkboxes are checked
const requiredCheckboxes = [
'dialog_terms_agreement',
'dialog_content_compliance',
'dialog_business_ad_compliance',
'dialog_ad_preview_confirmation',
'dialog_refund_policy_agreement'
];
const missingCheckboxes = [];
for (const checkboxId of requiredCheckboxes) {
const checkbox = document.getElementById(checkboxId);
if (!checkbox || !checkbox.checked) {
missingCheckboxes.push(checkboxId.replace('dialog_', '').replace(/_/g, ' '));
}
}
if (missingCheckboxes.length > 0) {
alert(`Please check all required agreement boxes before proceeding:\n\n• ${missingCheckboxes.join('\n• ')}`);
return;
}
// Store email copy preference
const emailCopyCheckbox = document.getElementById('dialog_email_copy');
localStorage.setItem('emailCopyRequested', emailCopyCheckbox ? emailCopyCheckbox.checked : false);
// Show loading state
const proceedButton = document.getElementById('checkout-modal-proceed');
if (proceedButton) {
proceedButton.textContent = 'Redirecting to Payment...';
proceedButton.disabled = true;
}
// Close dialog
closeCheckoutDialog();
// Redirect to payment page
setTimeout(() => {
window.location.href = 'https://www.borgesmedia.com/custom-billboard-ad-pay/';
}, 500);
}
function openTermsModal() {
// Simple terms modal - you can enhance this
alert('Terms and Conditions:\n\n1. All billboard content must comply with local regulations\n2. No refunds for non-compliant content\n3. Design changes after approval incur additional fees\n4. Payment is required before billboard display\n5. Content must be appropriate for public display');
}
// Make functions globally accessible
window.openCheckoutDialog = openCheckoutDialog;
window.closeCheckoutDialog = closeCheckoutDialog;
window.proceedToPayment = proceedToPayment;
window.openTermsModal = openTermsModal;