/* Target the paragraph containing the country field */ .wpcf7-form p:has(#country_field) { position: absolute !important; opacity: 0 !important; width: 0 !important; height: 0 !important; overflow: hidden !important; } /* Fallback: Directly target the country dropdown */ #country_field { display: none !important; } /* Set all label text to black */ .wpcf7-form label { color: black !important; } /* Form layout styling */ .form-row { display: flex; gap: 15px; margin-bottom: 15px; width: 100%; } .form-col { flex: 1; min-width: 0; /* Prevents flex items from overflowing */ } .form-col label { display: block; margin-bottom: 5px; } .form-col select, .form-col input { width: 100%; height: 40px; border-radius: 4px; border: 1px solid #ddd; padding: 8px 12px; font-size: 14px; background-color: white; } /* Improved daterangepicker styling */ #date_range { height: 40px; border-radius: 4px; border: 1px solid #ddd; padding: 8px 12px; width: 100%; font-size: 14px; background-color: white; cursor: pointer; } /* Submit button styling - ensure it's not white */ .wpcf7-submit { background-color: #0D7EE8 !important; color: #ffffff !important; border: none !important; font-weight: 500 !important; cursor: pointer !important; } .wpcf7-submit:hover { background-color: #0A6AC7 !important; } /* Fixed daterangepicker styling to prevent page disruption */ .daterangepicker { position: absolute !important; font-family: inherit; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); border: none; width: 320px !important; /* Narrower without ranges */ max-width: 95vw; padding: 0; z-index: 9999 !important; background-color: white; margin-top: 5px; overflow: visible !important; /* Prevent scrolling */ } /* Calendar styling */ .daterangepicker .drp-calendar.left { clear: none !important; float: none !important; padding: 15px; width: 100%; } /* Hide the right calendar */ .daterangepicker .drp-calendar.right { display: none !important; } /* Hide the ranges completely */ .daterangepicker .ranges { display: none !important; } /* Calendar table styling */ .daterangepicker .calendar-table { border-radius: 6px; padding: 0; } .daterangepicker .calendar-table table { width: 100%; border-spacing: 0; border-collapse: collapse; } .daterangepicker td, .daterangepicker th { width: 36px; height: 36px; text-align: center; vertical-align: middle; min-width: 36px; padding: 0; line-height: 36px; } .daterangepicker td.active, .daterangepicker td.active:hover { background-color: #0D7EE8 !important; } .daterangepicker td.in-range { background-color: #E6F3FF; color: #333; } /* Button area styling */ .daterangepicker .drp-buttons { padding: 15px; border-top: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; clear: both; } .daterangepicker .drp-buttons .btn { border-radius: 4px; font-weight: 500; margin: 0 5px; padding: 8px 16px; } .daterangepicker .drp-buttons .btn.btn-primary { background-color: #0D7EE8; border-color: #0D7EE8; } .daterangepicker .drp-selected { font-size: 13px; color: #666; margin-right: 10px; } /* Responsive fixes */ @media (max-width: 767px) { .form-row { flex-direction: column; gap: 10px; } } Select Your State* Select Your City* Select Your Billboard Location* Please select state and city first Select Run Dates* document.addEventListener('DOMContentLoaded', function() { var countryField = document.getElementById('country_field'); var stateField = document.getElementById('state_field'); var cityField = document.getElementById('city_field'); var dateRangeField = document.getElementById('date_range'); // Initialize daterangepicker with single calendar if(dateRangeField) { jQuery(dateRangeField).daterangepicker({ autoUpdateInput: false, minDate: new Date(), opens: 'center', showDropdowns: true, singleDatePicker: false, autoApply: false, linkedCalendars: false, locale: { cancelLabel: 'Clear', format: 'MM/DD/YYYY', applyLabel: 'Apply', daysOfWeek: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] }, // Remove the ranges option completely }); // Professional date handling with immediate localStorage saving jQuery(dateRangeField).on('apply.daterangepicker', function(ev, picker) { const dateRange = picker.startDate.format('MM/DD/YYYY') + ' - ' + picker.endDate.format('MM/DD/YYYY'); jQuery(this).val(dateRange); // Immediately save to localStorage localStorage.setItem('runDates', dateRange); console.log('Date range selected and saved to localStorage:', dateRange); // Trigger validation check validateFormData(); }); jQuery(dateRangeField).on('cancel.daterangepicker', function() { jQuery(this).val(''); // Remove from localStorage when cleared localStorage.removeItem('runDates'); console.log('Date range cleared from localStorage'); // Trigger validation check validateFormData(); }); // Add calendar icon to input jQuery(dateRangeField).css('background-image', 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'16\' height=\'16\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'%23666\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'%3E%3Crect x=\'3\' y=\'4\' width=\'18\' height=\'18\' rx=\'2\' ry=\'2\'%3E%3C/rect%3E%3Cline x1=\'16\' y1=\'2\' x2=\'16\' y2=\'6\'%3E%3C/line%3E%3Cline x1=\'8\' y1=\'2\' x2=\'8\' y2=\'6\'%3E%3C/line%3E%3Cline x1=\'3\' y1=\'10\' x2=\'21\' y2=\'10\'%3E%3C/line%3E%3C/svg%3E")'); jQuery(dateRangeField).css('background-repeat', 'no-repeat'); jQuery(dateRangeField).css('background-position', 'right 10px center'); jQuery(dateRangeField).css('background-size', '16px'); jQuery(dateRangeField).css('padding-right', '36px'); // Configure the datepicker on show jQuery(dateRangeField).on('show.daterangepicker', function(ev, picker) { // Get the position of the input field var $input = jQuery(this); var inputPos = $input.offset(); var inputHeight = $input.outerHeight(); var inputWidth = $input.outerWidth(); // Position the picker directly below the input var topPos = inputPos.top + inputHeight + 5; var leftPos = inputPos.left + (inputWidth / 2) - (320 / 2); // Center based on 320px width // Adjust if too close to edges var windowWidth = jQuery(window).width(); if (leftPos windowWidth - 10) { leftPos = windowWidth - 320 - 10; } // Apply the position picker.container.css({ top: topPos + 'px', left: leftPos + 'px', overflow: 'visible' }); // Hide right calendar picker.container.find('.drp-calendar.right').hide(); // Hide ranges completely picker.container.find('.ranges').hide(); // Make left calendar take full width picker.container.find('.drp-calendar.left').css({ 'float': 'none', 'width': '100%', 'padding': '15px' }); // Fix button layout picker.container.find('.drp-buttons').css({ 'display': 'flex', 'justify-content': 'space-between', 'align-items': 'center', 'padding': '15px', 'clear': 'both' }); }); } // Function to initialize country, state, city fields function initializeLocationFields() { // Check if fields exist if (!countryField) { console.log('Country field not found, retrying in 500ms'); setTimeout(initializeLocationFields, 500); return; } // Set country to US jQuery(countryField).val('US'); // Trigger change event using jQuery jQuery(countryField).trigger('change'); // Check if state field populated after a delay setTimeout(function() { if (!stateField || stateField.options.length <= 1) { console.log('State field not populated with "US", trying "United States"'); jQuery(countryField).val('United States').trigger('change'); // Check again after another delay setTimeout(function() { if (!stateField || stateField.options.length { jQuery('select[name="state"], select.state_auto').val(existingData.state); }, 1000); } if (existingData.city) { setTimeout(() => { jQuery('select[name="city"], select.city_auto').val(existingData.city); }, 1500); } if (existingData.location) { setTimeout(() => { // First update billboard locations, then set the value updateBillboardLocations(); setTimeout(() => { jQuery('select[name="Billboard-Location"]').val(existingData.location); }, 200); }, 500); } if (existingData.dates) { jQuery('#date_range').val(existingData.dates); } // Run initial validation and update billboard locations setTimeout(() => { updateBillboardLocations(); validateFormData(); }, 2000); } // Initialize form data after a delay to ensure all fields are loaded setTimeout(initializeFormData, 1000); // Professional real-time data saving for all fields // Function to update billboard locations based on selected state and city function updateBillboardLocations() { const selectedState = jQuery('select.state_auto, select[name="state"]').val(); const selectedCity = jQuery('select.city_auto, select[name="city"]').val(); const billboardLocationSelect = jQuery('#billboard_location_field'); console.log('Updating billboard locations for:', { state: selectedState, city: selectedCity }); // Clear existing options billboardLocationSelect.empty(); // Define billboard locations data const billboardLocations = { 'Texas': { 'Stephenville': [ { value: 'Texas > Stephenville > 2095 West South loop, Stephenville, TX 76401', text: 'Texas > Stephenville > 2095 West South loop, Stephenville, TX 76401' } ] } }; // Check if we have locations for the selected state and city if (selectedState && selectedCity && billboardLocations[selectedState] && billboardLocations[selectedState][selectedCity]) { // Add default option billboardLocationSelect.append('Select a Billboard Location'); // Add available locations for this state/city combination const locations = billboardLocations[selectedState][selectedCity]; locations.forEach(location => { billboardLocationSelect.append(`${location.text}`); }); console.log(`Added ${locations.length} billboard location(s) for ${selectedState} > ${selectedCity}`); } else { // No locations available or state/city not selected if (!selectedState || !selectedCity) { billboardLocationSelect.append('Please select state and city first'); } else { billboardLocationSelect.append('No billboard locations available for this area'); } } // Clear any previously selected billboard location since options changed localStorage.removeItem('billboardLocation'); } // Add event listener for state field changes with immediate saving jQuery(document).on('change', 'select.state_auto, select[name="state"]', function() { const selectedState = jQuery(this).val(); console.log('State changed to:', selectedState); if (selectedState) { localStorage.setItem('selectedState', selectedState); console.log('State saved to localStorage:', selectedState); } // Update billboard locations when state changes updateBillboardLocations(); validateFormData(); }); // Add event listener for city field changes with immediate saving jQuery(document).on('change', 'select.city_auto, select[name="city"]', function() { const selectedCity = jQuery(this).val(); console.log('City changed to:', selectedCity); if (selectedCity) { localStorage.setItem('selectedCity', selectedCity); console.log('City saved to localStorage:', selectedCity); } // Update billboard locations when city changes updateBillboardLocations(); validateFormData(); }); // Add event listener for billboard location changes with immediate saving jQuery(document).on('change', 'select[name="Billboard-Location"]', function() { const billboardLocation = jQuery(this).val(); console.log('Billboard location changed to:', billboardLocation); if (billboardLocation) { localStorage.setItem('billboardLocation', billboardLocation); console.log('Billboard location saved to localStorage:', billboardLocation); } validateFormData(); }); // Professional form validation function function validateFormData() { // Ensure purpose is always set to 'custom' if (!localStorage.getItem('selectedPurpose')) { localStorage.setItem('selectedPurpose', 'custom'); } const requiredData = { purpose: localStorage.getItem('selectedPurpose') || 'custom', state: localStorage.getItem('selectedState'), city: localStorage.getItem('selectedCity'), location: localStorage.getItem('billboardLocation'), dates: localStorage.getItem('runDates') }; const missingFields = []; Object.keys(requiredData).forEach(field => { const value = requiredData[field]; // Purpose is always valid since it's set to 'custom' if (field === 'purpose') { return; // Skip validation for purpose } if (!value || value === 'Select a Purpose' || value === 'Not selected' || value.trim() === '') { missingFields.push(field); } }); console.log('Form validation check:', { data: requiredData, missingFields: missingFields, isValid: missingFields.length === 0 }); // Update UI to show validation status (optional) updateValidationStatus(missingFields); return missingFields.length === 0; } // Update validation status in UI - Only for latest-dropdowns form function updateValidationStatus(missingFields) { console.log('updateValidationStatus: Checking validation status with missing fields:', missingFields); // Remove existing validation messages only from the latest-dropdowns context jQuery('.latest-dropdowns-validation').remove(); // Find the specific form that contains our dropdown fields const targetForm = jQuery('#state_field, #city_field, #billboard_location_field').closest('.wpcf7-form'); if (targetForm.length === 0) { console.log('updateValidationStatus: No latest-dropdowns form found, skipping UI update'); return; } if (missingFields && missingFields.length > 0) { const message = `Please complete: ${missingFields.join(', ')}`; const statusDiv = jQuery(`${message}`); targetForm.prepend(statusDiv); } else { const statusDiv = jQuery(`✅ All required fields completed!`); targetForm.prepend(statusDiv); } // Update continue button if it exists const continueBtn = jQuery('.continue-btn, #continueBtn'); if (continueBtn.length > 0) { const isValid = !missingFields || missingFields.length === 0; continueBtn.prop('disabled', !isValid); continueBtn.css('opacity', isValid ? '1' : '0.5'); } console.log('updateValidationStatus: Validation status updated for latest-dropdowns context'); } // Purpose is now set to 'custom' by default - no event listener needed // Send initial purpose message to Ad Design and Content form try { const defaultPurpose = 'custom'; localStorage.setItem('selectedPurpose', defaultPurpose); // Send message to Ad Design and Content form if it's in another window/iframe const adDesignWindow = window.parent || window.opener || window; adDesignWindow.postMessage({ type: 'purposeSelected', purpose: defaultPurpose }, window.location.origin); // Also try to send to all frames if this is in a parent window if (window.frames && window.frames.length > 0) { for (let i = 0; i < window.frames.length; i++) { try { window.frames[i].postMessage({ type: 'purposeSelected', purpose: defaultPurpose }, window.location.origin); } catch (e) { // Ignore cross-origin errors } } } // Also trigger a custom event on the current document const purposeEvent = new CustomEvent('purposeSelected', { detail: { purpose: defaultPurpose } }); document.dispatchEvent(purposeEvent); console.log('Default purpose set and communicated:', defaultPurpose); } catch (e) { console.log('Could not send purpose message to other window:', e); } // Add form submission handler to store data for checkout jQuery(document).on('submit', '.wpcf7-form', function(e) { // Purpose is always 'custom' now const selectedPurpose = 'custom'; const selectedState = jQuery('select[name="state"]').val(); const selectedCity = jQuery('select[name="city"]').val(); const billboardLocation = jQuery('select[name="Billboard-Location"]').val(); const dateRange = jQuery('#date_range').val(); // Store all form data for checkout - purpose is always 'custom' localStorage.setItem('selectedPurpose', selectedPurpose); if (selectedState) { localStorage.setItem('selectedState', selectedState); } if (selectedCity) { localStorage.setItem('selectedCity', selectedCity); } if (billboardLocation) { localStorage.setItem('billboardLocation', billboardLocation); } if (dateRange) { localStorage.setItem('runDates', dateRange); } console.log('Form data stored for checkout:', { purpose: selectedPurpose, state: selectedState, city: selectedCity, location: billboardLocation, dates: dateRange }); }); });

      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;