1(function () { 2 function onReady(callback) { 3 if (document.readyState === 'loading') { 4 document.addEventListener('DOMContentLoaded', callback); 5 return; 6 } 7 callback(); 8 } 9 10 function escapeHtml(value) { 11 return String(value || '').replace(/[&<>"']/g, function (char) { 12 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]; 13 }); 14 } 15 16 function restUrl() { 17 if (window.OCFRuntime && window.OCFRuntime.restUrl) { 18 return window.OCFRuntime.restUrl; 19 } 20 if (window.wpApiSettings && window.wpApiSettings.root) { 21 return window.wpApiSettings.root + 'ocf/v1/lead'; 22 } 23 return '/wp-json/ocf/v1/lead'; 24 } 25 26 function normalizedSectionOrderPath() { 27 var path = String(window.location.pathname || '/').replace(/\/+$/, ''); 28 return path || '/'; 29 } 30 31 function sectionOrderForCurrentPage() { 32 var runtime = window.OCFRuntime || {}; 33 var order = runtime.sectionOrder || {}; 34 if (!order || typeof order !== 'object' || Array.isArray(order)) return []; 35 var current = order[normalizedSectionOrderPath()]; 36 return Array.isArray(current) ? current.filter(Boolean).map(String) : []; 37 } 38 39 function isPageSectionElement(section) { 40 if (!section || !section.getAttribute) return false; 41 var sectionId = String(section.getAttribute('data-ocf-section') || '').toLowerCase(); 42 if (!sectionId || sectionId === 'header' || sectionId === 'footer') return false; 43 var instance = String(section.getAttribute('data-ocf-section-instance') || ''); 44 return instance.indexOf('global:') !== 0; 45 } 46 47 function sectionInstance(section) { 48 return String(section && section.getAttribute ? section.getAttribute('data-ocf-section-instance') || '' : ''); 49 } 50 51 function sectionHasImageOverlay(section) { 52 if (!section || !section.querySelector) return false; 53 if (section.getAttribute('data-ocf-bg-image-active') === '1') return true; 54 if (section.querySelector('[class*="__shade"], [class*="-shade"], [data-ocf-overlay]')) return true; 55 var computed = window.getComputedStyle(section); 56 return computed && String(computed.backgroundImage || '').indexOf('url
56(') !== -1; 57 } 58 59 function validOverlayValue(value, fallback) { 60 var parsed = parseFloat(value); 61 if (!Number.isFinite(parsed)) parsed = fallback; 62 parsed = Math.max(0, Math.min(1, parsed)); 63 return String(Math.round(parsed * 100) / 100); 64 } 65 66 function applySectionOrder(order) { 67 if (!Array.isArray(order) || order.length < 2) return; 68 var sections = Array.prototype.slice.call(document.querySelectorAll('[data-ocf-section]')).filter(isPageSectionElement); 69 if (sections.length < 2) return; 70 71 var byInstance = {}; 72 sections.forEach(function (section) { 73 var instance = sectionInstance(section); 74 if (instance) byInstance[instance] = section; 75 }); 76 77 var ordered = order.map(function (instance) { 78 return byInstance[String(instance || '')] || null; 79 }).filter(Boolean); 80 if (ordered.length < 2) return; 81 82 var parent = ordered[0].parentNode; 83 if (!parent) return; 84 var siblings = sections.filter(function (section) { 85 return section.parentNode === parent; 86 }); 87 if (siblings.length < 2) return; 88 89 var orderedLookup = {}; 90 ordered.forEach(function (section) { 91 orderedLookup[sectionInstance(section)] = true; 92 }); 93 var finalOrder = ordered.concat(siblings.filter(function (section) { 94 return !orderedLookup[sectionInstance(section)]; 95 })); 96 97 var cursor = siblings[0]; 98 finalOrder.forEach(function (section) { 99 if (section.parentNode !== parent) return; 100 if (section !== cursor) { 101 parent.insertBefore(section, cursor); 102 } 103 cursor = section.nextSibling; 104 }); 105 } 106 107 function applyStoredSectionOrder() { 108 applySectionOrder(sectionOrderForCurrentPage()); 109 } 110 111 function submitLead(payload) { 112 return fetch(restUrl(), { 113 method: 'POST', 114 headers: { 'Content-Type': 'application/json' }, 115 body: JSON.stringify(payload) 116 }).then(function (response) { 117 return response.json().catch(function () { 118 return { ok: response.ok }; 119 }); 120 }); 121 } 122 123 var optimisticSuccessDelay = 500; 124 125 function getPhoneOptions(combo) { 126 try { 127 return JSON.parse(combo ? combo.getAttribute('data-phone-options') || '[]' : '[]'); 128 } catch (error) { 129 return []; 130 } 131 } 132 133 function sortedPhoneOptions(combo) { 134 return getPhoneOptions(combo).slice().sort(function (a, b) { 135 return String(b.dial || '').replace(/\D/g, '').length - String(a.dial || '').replace(/\D/g, '').length; 136 }); 137 } 138 139 function getInternationalPhone(input) { 140 if (!input) return ''; 141 var local = input.value.trim(); 142 if (!local) return ''; 143 144 var combo = input.closest('[data-phone-combo]'); 145 var digits = local.replace(/\D/g, ''); 146 if (local.charAt(0) === '+') { 147 var match = sortedPhoneOptions(combo).find(function (country) { 148 var countryDigits = String(country.dial || '').replace(/\D/g, ''); 149 return countryDigits && digits.indexOf(countryDigits) === 0; 150 }); 151 if (match) { 152 var dialDigits = String(match.dial || '').replace(/\D/g, ''); 153 return digits.length > dialDigits.length ? '+' + digits : ''; 154 } 155 return digits.length > 1 ? '+' + digits : ''; 156 } 157 158 var country = combo ? combo.querySelector('[data-phone-country]') : null; 159 var dial = country ? country.value.trim() : ''; 160 return dial && digits ? dial + digits : digits; 161 } 162 163 window.ocfGetInternationalPhone = getInternationalPhone; 164 window.getInternationalPhone = getInternationalPhone; 165 166 function groupPhoneDigits(value) { 167 if (value.length <= 3) return value; 168 if (value.length <= 6) return value.slice(0, 3) + '-' + value.slice(3); 169 if (value.length <= 10) return value.slice(0, 3) + '-' + value.slice(3, 6) + '-' + value.slice(6); 170 171 var groups = []; 172 for (var index = 0; index < value.length; index += 4) { 173 groups.push(value.slice(index, index + 4)); 174 } 175 return groups.join('-'); 176 } 177 178 function initPhoneCombos() { 179 document.querySelectorAll('[data-phone-combo]').forEach(function (combo) { 180 if (combo.getAttribute('data-ocf-phone-bound') === '1') return; 181 combo.setAttribute('data-ocf-phone-bound', '1'); 182 183 var input = combo.querySelector('[data-phone-local]'); 184 var flag = combo.querySelector('[data-phone-flag]'); 185 if (!input || !flag) return; 186 187 var defaultDial = combo.getAttribute('data-phone-default') || '+1'; 188 var defaultFlag = flag.textContent || 'US'; 189 var options = sortedPhoneOptions(combo); 190 191 function formatPhoneValue(raw) { 192 var match = options.find(function (country) { 193 var dial = String(country.dial || ''); 194 return dial && raw.indexOf(dial) === 0; 195 }); 196 var dial = match ? String(match.dial || '') : defaultDial; 197 if (raw.indexOf(dial) !== 0) return raw; 198 199 var local = raw.slice(dial.length); 200 return local ? dial + '-' + groupPhoneDigits(local) : dial; 201 } 202 203 function normalizeValue() { 204 var value = input.value || ''; 205 var digits = value.replace(/\D/g, '').slice(0, 15); 206 if (!value) { 207 input.value = defaultDial; 208 return; 209 } 210 if (!digits) { 211 input.value = '+'; 212 return; 213 } 214 if (value.charAt(0) !== '+') { 215 var defaultDigits = defaultDial.replace(/\D/g, ''); 216 if (digits.length <= 10 && defaultDigits) { 217 digits = defaultDigits + digits; 218 } 219 } 220 input.value = formatPhoneValue('+' + digits); 221 } 222 223 function updateFlag() { 224 var value = input.value || ''; 225 var dialDigits = value.replace(/^\+/, '').replace(/\D/g, ''); 226 var match = options.find(function (country) { 227 var countryDigits = String(country.dial || '').replace(/\D/g, ''); 228 return countryDigits && dialDigits.indexOf(countryDigits) === 0; 229 }); 230 flag.textContent = match ? match.flag : defaultFlag; 231 } 232 233 function placeCaretEnd() { 234 var end = input.value.length; 235 try { 236 input.setSelectionRange(end, end); 237 } catch (error) {} 238 } 239 240 if (!input.value) { 241 input.value = defaultDial; 242 } 243 normalizeValue(); 244 updateFlag(); 245 246 input.addEventListener('focus', function () { 247 if (!input.value || input.value.charAt(0) !== '+') { 248 input.value = defaultDial; 249 } 250 window.setTimeout(placeCaretEnd, 0); 251 }); 252 253 input.addEventListener('keydown', function (event) { 254 var start = input.selectionStart || 0; 255 var end = input.selectionEnd || start; 256 if ((event.key === 'Backspace' && start <= 1 && end <= 1) || (event.key === 'Delete' && start === 0 && end <= 1)
256) { 257 event.preventDefault(); 258 if (input.value.charAt(0) !== '+') input.value = '+'; 259 try { 260 input.setSelectionRange(1, 1); 261 } catch (error) {} 262 } 263 }); 264 265 input.addEventListener('input', function () { 266 var before = input.value; 267 var caret = input.selectionStart || before.length; 268 normalizeValue(); 269 updateFlag(); 270 if (before !== input.value && document.activeElement === input) { 271 var next = Math.max(1, caret + (input.value.length - before.length)); 272 try { 273 input.setSelectionRange(next, next); 274 } catch (error) {} 275 } 276 }); 277 }); 278 } 279 280 function initLeadForms() { 281 document.querySelectorAll('[data-ocf-lead-form], .ocf-simple-form').forEach(function (form) { 282 if (form.getAttribute('data-ocf-lead-bound') === '1') return; 283 form.setAttribute('data-ocf-lead-bound', '1'); 284 285 form.addEventListener('submit', function (event) { 286 event.preventDefault(); 287 var btn = form.querySelector('.form-submit-btn, [type="submit"]'); 288 var status = form.querySelector('.ocf-form-status'); 289 var fields = form.querySelector('.hero-form-fields'); 290 var nameInput = form.querySelector('[name="name"]'); 291 var emailInput = form.querySelector('[name="email"]'); 292 var phoneInput = form.querySelector('[name="phone"]'); 293 var descInput = form.querySelector('[name="description"]'); 294 var honeypotInput = form.querySelector('[name="website_url"]'); 295 var companyInput = form.querySelector('[name="company"]'); 296 var name = nameInput ? nameInput.value.trim() : ''; 297 var email = emailInput ? emailInput.value.trim() : ''; 298 var phone = getInternationalPhone(phoneInput); 299 var defaultText = btn ? btn.textContent : ''; 300 var labels = { 301 sending: form.dataset.labelSending || '...', 302 successTitle: form.dataset.labelSuccessTitle || '', 303 successMessage: form.dataset.labelSuccessMessage || '', 304 genericError: form.dataset.labelGenericError || 'Something went wrong. Please try again.', 305 networkError: form.dataset.labelNetworkError || 'Network error. Please try again.' 306 }; 307 308 if (!name) { 309 if (nameInput) nameInput.focus(); 310 return; 311 } 312 if (!email && !phone) { 313 if (emailInput) emailInput.focus(); 314 return; 315 } 316 if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 317 if (emailInput) emailInput.focus(); 318 return; 319 } 320 321 if (btn) { 322 btn.disabled = true; 323 btn.textContent = labels.sending; 324 } 325 if (status) { 326 status.classList.remove('is-visible'); 327 status.innerHTML = ''; 328 } 329 330 var successShown = false; 331 var showSuccess = function () { 332 if (successShown) return; 333 successShown = true; 334 if (fields) fields.style.display = 'none'; 335 (form.closest('.hero-form-card') || form).querySelectorAll('[data-form-card-heading]').forEach(function (node) { 336 node.style.display = 'none'; 337 }); 338 if (status) { 339 var successMessageHtml = labels.successMessage ? '<p>' + escapeHtml(labels.successMessage) + '</p>' : ''; 340 status.classList.add('is-visible'); 341 status.innerHTML = '<div class="ocf-form-success"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg><h3>' + escapeHtml(labels.successTitle) + '</h3>' + successMessageHtml + '</div>'; 342 } 343 }; 344 var successTimer = setTimeout(showSuccess, optimisticSuccessDelay); 345 346 submitLead({ 347 name: name, 348 email: email, 349 phone: phone, 350 description: descInput ? descInput.value.trim() : '', 351 website_url: honeypotInput ? honeypotInput.value : '', 352 company: companyInput ? companyInput.value : '', 353 page_url: window.location.href 354 }).then(function (response) { 355 if (response.ok) { 356 showSuccess(); 357 return; 358 } 359 if (successShown) return; 360 clearTimeout(successTimer); 361 if (btn) { 362 btn.disabled = false;
363 btn.textContent = defaultText; 364 } 365 if (status) { 366 status.classList.add('is-visible'); 367 status.innerHTML = '<p class="ocf-form-error">' + escapeHtml(response.error || labels.genericError) + '</p>'; 368 setTimeout(function () { status.classList.remove('is-visible'); }, 5000); 369 } 370 }).catch(function () { 371 if (successShown) return; 372 clearTimeout(successTimer); 373 if (btn) { 374 btn.disabled = false; 375 btn.textContent = defaultText; 376 } 377 if (status) { 378 status.classList.add('is-visible'); 379 status.innerHTML = '<p class="ocf-form-error">' + escapeHtml(labels.networkError) + '</p>'; 380 setTimeout(function () { status.classList.remove('is-visible'); }, 5000); 381 } 382 }); 383 }); 384 }); 385 } 386 387 function initBookingWizard() { 388 var wizard = document.getElementById('booking-wizard'); 389 if (!wizard || wizard.getAttribute('data-ocf-booking-bound') === '1') return; 390 wizard.setAttribute('data-ocf-booking-bound', '1'); 391 392 var totalSteps = 3; 393 var currentStep = 1; 394 var state = { 395 services: [], 396 service_other: '', 397 description: '', 398 notes: '', 399 first_name: '', 400 last_name: '', 401 phone: '', 402 email: '', 403 address: '' 404 }; 405 406 var progressBar = document.getElementById('bw-progress-bar'); 407 var nextBtn = document.getElementById('bw-next'); 408 var backBtn = document.getElementById('bw-back'); 409 var nav = document.getElementById('bw-nav'); 410 var overlay = document.getElementById('booking-wizard-overlay'); 411 var closeBtn = document.getElementById('bw-close'); 412 var backdrop = document.getElementById('bw-backdrop'); 413 var serviceOtherWrap = document.getElementById('bw-service-other-wrap'); 414 var serviceOtherInput = document.getElementById('bw-service-other'); 415 var labels = { 416 services: wizard.dataset.labelServices || '', 417 contact: wizard.dataset.labelContact || '', 418 name: wizard.dataset.labelName || 'Name', 419 phone: wizard.dataset.labelPhone || 'Phone', 420 email: wizard.dataset.labelEmail || 'Email', 421 address: wizard.dataset.labelAddress || '', 422 details: wizard.dataset.labelDetails || '', 423 complete: wizard.dataset.labelComplete || '', 424 continueText: wizard.dataset.labelContinue || '', 425 sending: wizard.dataset.labelSending || '...', 426 emailError: wizard.dataset.labelEmailError || '' 427 }; 428 429 if (!nextBtn || !backBtn || !nav) return; 430 431 function shake(el) { 432 if (!el) return; 433 el.classList.remove('bw-shake'); 434 void el.offsetHeight; 435 el.classList.add('bw-shake'); 436 setTimeout(function () { el.classList.remove('bw-shake'); }, 500); 437 } 438 439 function syncServiceOtherInput() { 440 if (!serviceOtherWrap) return; 441 var show = state.services.indexOf('Other') > -1; 442 serviceOtherWrap.style.display = show ? 'block' : 'none'; 443 if (show && serviceOtherInput && serviceOtherInput.value !== state.service_other) { 444 serviceOtherInput.value = state.service_other || ''; 445 } 446 } 447 448 function updateProgress() { 449 if (!progressBar) return; 450 progressBar.style.width = Math.min((currentStep / totalSteps) * 100, 100) + '%'; 451 } 452 453 function updateNav() { 454 backBtn.style.visibility = currentStep > 1 ? 'visible' : 'hidden'; 455 if (currentStep === totalSteps) { 456 nextBtn.innerHTML = escapeHtml(labels.complete) + ' <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>'; 457 } else { 458 nextBtn.innerHTML = escapeHtml(labels.continueText) + ' <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><polyline points="12 5 19 12 12 19"/></svg>'; 459 } 460 } 461 462 function goTo(step) { 463 if (step < 1 || step > totalSteps + 1) return; 464 465 var currentEl = wizard.querySelector('.bw-step.is-active'); 466 if (currentEl) { 467 currentEl.classList.add('is-leaving'); 468 currentEl.classList.remove('is-active'); 469 } 470 471 setTimeout(function () { 472 if (currentEl) { 473 currentEl.classList.remove('is-leaving'); 474 currentEl.style.display = 'none'; 475 } 476 477 var target = step === totalSteps + 1 478 ? wizard.querySelector('[data-step="success"]') 479 : wizard.querySelector('[data-step="' + step + '"]'); 480 481 if (step === totalSteps + 1) { 482 nav.style.display = 'none'; 483 } 484 if (target) { 485 if (step === 1) syncServiceOtherInput(); 486 target.style.display = ''; 487 void target.offsetHeight; 488 target.classList.add('is-active'); 489 } 490 491 currentStep = step; 492 updateProgress(); 493 updateNav(); 494 window.scrollTo({ top: 0, behavior: 'smooth' }); 495 }, 250); 496 } 497 498 wizard.querySelectorAll('.bw-service-tile').forEach(function (tile) { 499 tile.addEventListener('click', function () { 500 tile.classList.toggle('is-selected'); 501 var name = tile.getAttribute('data-service-name'); 502 var index = state.services.indexOf(name); 503 if (index > -1) { 504 state.services.splice(index, 1); 505 } else { 506 state.services.push(name); 507 } 508 509 if (tile.getAttribute('data-service-id') === 'other' && serviceOtherWrap) { 510 var show = tile.classList.contains('is-selected'); 511 serviceOtherWrap.style.display = show ? 'block' : 'none'; 512 if (show && serviceOtherInput) serviceOtherInput.focus(); 513 if (!show) { 514 state.service_other = ''; 515 if (serviceOtherInput) serviceOtherInput.value = ''; 516 } 517 } 518 }); 519 }); 520 521 if (serviceOtherInput) { 522 serviceOtherInput.addEventListener('input', function () { 523 state.service_other = serviceOtherInput.value.trim(); 524 }); 525 } 526 527 function validateContactForm() {
528 var valid = true; 529 var fields = [ 530 { id: 'bw-fname', key: 'first_name', required: true }, 531 { id: 'bw-lname', key: 'last_name', required: true }, 532 { id: 'bw-phone', key: 'phone', required: true }, 533 { id: 'bw-email', key: 'email', required: true }, 534 { id: 'bw-address', key: 'address' } 535 ]; 536 537 wizard.querySelectorAll('.bw-invalid').forEach(function (el) { 538 el.classList.remove('bw-invalid'); 539 }); 540 wizard.querySelectorAll('.bw-error-msg').forEach(function (el) { 541 el.remove(); 542 }); 543 544 fields.forEach(function (field) { 545 var input = document.getElementById(field.id); 546 if (!input) return; 547 var localVal = input.value.trim(); 548 var value = field.key === 'phone' ? getInternationalPhone(input) : localVal; 549 state[field.key] = value; 550 551 if ((!localVal || (field.key === 'phone' && !value)) && field.required) { 552 input.classList.add('bw-invalid'); 553 valid = false; 554 } 555 556 if (field.key === 'email' && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { 557 input.classList.add('bw-invalid'); 558 var msg = document.createElement('div'); 559 msg.className = 'bw-error-msg'; 560 msg.textContent = labels.emailError; 561 input.parentNode.appendChild(msg); 562 valid = false; 563 } 564 }); 565 566 if (!valid) { 567 var firstInvalid = wizard.querySelector('.bw-invalid'); 568 if (firstInvalid) { 569 shake(firstInvalid); 570 firstInvalid.focus(); 571 } 572 } 573 574 state.description = (document.getElementById('bw-description') || {}).value || ''; 575 return valid; 576 } 577 578 function validate(step) { 579 if (step === 1) { 580 if (state.services.length === 0) { 581 shake(wizard.querySelector('.bw-service-grid')); 582 return false; 583 } 584 if (state.services.indexOf('Other') > -1 && !state.service_other) { 585 shake(serviceOtherWrap); 586 if (serviceOtherInput) serviceOtherInput.focus(); 587 return false; 588 } 589 return true; 590 } 591 if (step === 2) { 592 return validateContactForm(); 593 } 594 return true; 595 } 596 597 function buildSummary() { 598 var summary = document.getElementById('bw-summary'); 599 if (!summary) return; 600 var servicesList = state.services.map(function (service) { 601 return service === 'Other' && state.service_other ? state.service_other : service; 602 }).filter(Boolean); 603 604 function summaryChip(value) { 605 return '<span class="bw-summary-chip">' + escapeHtml(value) + '</span>'; 606 } 607 608 function contactItem(label, value) { 609 if (!value) return ''; 610 return '<div class="bw-summary-contact-item"><span class="bw-summary-contact-label">' + 611 escapeHtml(label) + '</span><span class="bw-summary-contact-value">' + escapeHtml(value) + '</span></div>'; 612 } 613 614 var contactHtml = '<div class="bw-summary-contact-grid">' + 615 contactItem(labels.name, (state.first_name + ' ' + state.last_name).trim()) + 616 contactItem(labels.phone, state.phone) + 617 contactItem(labels.email, state.email) + 618 '</div>'; 619 620 var rows = [ 621 { 622 type: 'services', 623 icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>', 624 label: labels.services, 625 value: '<div class="bw-summary-chip-list">' + servicesList.map(summaryChip).join('') + '</div>' 626 }, 627 { 628 type: 'contact', 629 icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>', 630 label: labels.contact, 631 value: contactHtml 632 } 633 ]; 634 var insertAt = 1; 635 636 if (state.description || state.notes) { 637 var detailParts = []; 638 if (state.description) detailParts.push(escapeHtml(state.description)); 639 if (state.notes) detailParts.push(escapeHtml(state.notes)); 640 rows.splice(insertAt, 0, { 641 type: 'details', 642 icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>', 643 label: labels.details, 644 value: detailParts.join('<br>') 645 }); 646 insertAt++; 647 } 648 649 if (state.address) { 650 rows.push({ 651 type: 'address', 652 icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>', 653 label: labels.address, 654 value: escapeHtml(state.address) 655 }); 656 } 657 658 summary.innerHTML = rows.map(function (row) { 659 return '<div class="bw-summary-row bw-summary-row--' + escapeHtml(row.type || 'item') + '"><div class="bw-summary-icon">' + 660 row.icon + '</div><div class="bw-summary-body"><div class="bw-summary-label">' + 661 row.label + '</div><div class="bw-summary-value">' + row.value + '</div></div></div>'; 662 }).join(''); 663 } 664 665 function submit() { 666 state.notes = (document.getElementById('bw-notes') || {}).value || ''; 667 nextBtn.classList.add('is-loading'); 668 nextBtn.innerHTML = escapeHtml(labels.sending) + ' <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>'; 669 670 var successShown = false;
671 var showSuccess = function () { 672 if (successShown) return; 673 successShown = true; 674 goTo(totalSteps + 1); 675 }; 676 var successTimer = setTimeout(showSuccess, optimisticSuccessDelay); 677 678 submitLead(Object.assign({}, state, { 679 page_url: window.location.href, 680 website_url: (document.getElementById('bw-website-url') || {}).value || '', 681 company: (document.getElementById('bw-company') || {}).value || '' 682 })).then(function (response) { 683 if (response.ok) { 684 showSuccess(); 685 return; 686 } 687 if (successShown) return; 688 clearTimeout(successTimer); 689 nextBtn.classList.remove('is-loading'); 690 nextBtn.textContent = labels.complete || labels.continueText || 'Continue'; 691 window.alert(response.error || 'Something went wrong. Please try again.'); 692 }).catch(function () { 693 if (successShown) return; 694 clearTimeout(successTimer); 695 nextBtn.classList.remove('is-loading'); 696 nextBtn.textContent = labels.complete || labels.continueText || 'Continue'; 697 window.alert('Network error. Please try again.'); 698 }); 699 } 700 701 nextBtn.addEventListener('click', function () { 702 if (!validate(currentStep)) return; 703 if (currentStep === totalSteps) { 704 submit(); 705 return; 706 } 707 if (currentStep === totalSteps - 1) { 708 validateContactForm(); 709 buildSummary(); 710 } 711 goTo(currentStep + 1); 712 }); 713 714 backBtn.addEventListener('click', function () { 715 goTo(currentStep - 1); 716 }); 717 718 wizard.addEventListener('keydown', function (event) { 719 if (event.key !== 'Enter' || currentStep > totalSteps) return; 720 var tag = (event.target.tagName || '').toLowerCase(); 721 if (tag === 'textarea') return; 722 event.preventDefault(); 723 nextBtn.click(); 724 }); 725 726 function openWizard() { 727 if (!overlay) return; 728 overlay.classList.add('is-open'); 729 overlay.setAttribute('aria-hidden', 'false'); 730 document.body.style.overflow = 'hidden'; 731 } 732 733 function closeWizard() { 734 if (!overlay) return; 735 overlay.classList.remove('is-open'); 736 overlay.setAttribute('aria-hidden', 'true'); 737 document.body.style.overflow = ''; 738 } 739 740 document.addEventListener('click', function (event) { 741 var trigger = event.target.closest('[data-open-booking], [href="#book"]'); 742 if (!trigger) return; 743 event.preventDefault(); 744 openWizard(); 745 }); 746 747 if (closeBtn) closeBtn.addEventListener('click', closeWizard); 748 if (backdrop) backdrop.addEventListener('click', closeWizard); 749 if (window.location.hash === '#book') openWizard(); 750 751 document.addEventListener('keydown', function (event) { 752 if (event.key === 'Escape' && overlay && overlay.classList.contains('is-open')) { 753 closeWizard(); 754 } 755 }); 756 } 757 758 function initEditorBridge() { 759 var params = new URLSearchParams(window.location.search); 760 if (params.get('ocf_editor') !== '1') return; 761 if (!params.get('ocf_editor_token')) return; 762 if (window.OCFRuntime && window.OCFRuntime.editor && window.OCFRuntime.editor.active === false) return; 763 if (document.documentElement.getAttribute('data-ocf-editor-bound') === '1') return; 764 document.documentElement.setAttribute('data-ocf-editor-bound', '1'); 765 766 var selectedSection = null; 767 var selectedField = null; 768 var selectedPostItem = null; 769 var restoreSelectionKey = 'ocfEditor.restoreSelection'; 770 var originalSectionState = new WeakMap(); 771 var style = document.createElement('style'); 772 style.textContent = [ 773 '[data-ocf-section]{position:relative;}', 774 '[data-ocf-section].ocf-editor-selected{outline:2px solid #1f3e90;outline-offset:-2px;}', 775 '[data-ocf-section].ocf-editor-hover{outline:2px dashed rgba(31,62,144,.55);outline-offset:-2px;}', 776 '[data-ocf-field].ocf-editor-field-selected{outline:2px solid #2f7668;outline-offset:3px;border-radius:4px;}', 777 '[data-ocf-post-type][data-ocf-post-id].ocf-editor-post-selected{outline:2px solid #2f7668;outline-offset:4px;border-radius:6px;}', 778 '[data-ocf-preview-loop-hidden="1"]{display:none!important;}', 779 '[data-ocf-field][contenteditable="true"]{cursor:text;caret-color:#2f7668;user-select:text;-webkit-user-select:text;}', 780 '[data-ocf-field][contenteditable="true"]:focus{outline:2px solid #2f7668;outline-offset:3px;border-radius:4px;}', 781 '[data-ocf-bg-image-active="1"] > [class*="__media"],[data-ocf-bg-image-active="1"] > img[class*="__bg-image"],[data-ocf-bg-image-active="1"] > img[class*="__image"]{display:none!important;}' 782 ].join(''); 783 document.head.appendChild(style); 784 785 function findSection(target) { 786 return target && target.closest ? target.closest('[data-ocf-section]') : null; 787 } 788 789 function findField(target, section) { 790 if (!target || !target.closest || !section) return null; 791 var field = target.closest('[data-ocf-field]'); 792 if (field && isHeaderNavField(field, section)) return null; 793 return field && section.contains(field) ? field : null; 794 } 795 796 function findAction(target, section) { 797 if (!target || !target.closest || !section) return null; 798 var action = target.closest('[data-ocf-action]'); 799 return action && section.contains(action) ? action : null; 800 } 801 802 function findFormSurface(target, section) { 803 if (!target || !target.closest || !section) return null; 804 var form = target.closest('[data-ocf-lead-form], .ocf-simple-form, .hero-form-card, .ocf-form-shell'); 805 return form && section.contains(form) ? form : null; 806 } 807 808 function findPostItem(target, section) { 809 if (!target || !target.closest || !section) return null; 810 if (isHeaderSection(section)) return null; 811 var item = target.closest('[data-ocf-post-type][data-ocf-post-id]'); 812 return item && section.contains(item) ? item : null; 813 } 814 815 function findInstance(target, section) { 816 if (!target || !target.closest || !section) return null; 817 var instance = target.closest('[data-ocf-instance]'); 818 return instance && section.contains(instance) ? instance : null; 819 } 820 821 function findRepeaterItem(field, section) { 822 if (!field || !field.closest || !section) return null; 823 var item = field.closest('[data-ocf-repeater][data-ocf-index]'); 824 return item && section.contains(item) ? item : null; 825 } 826 827 function setSelectedSection(section) { 828 if (selectedSection && selectedSection !== section) { 829 selectedSection.classList.remove('ocf-editor-selected'); 830 } 831 selectedSection = section; 832 if (selectedSection) { 833 selectedSection.classList.add('ocf-editor-selected'); 834 } 835 } 836 837 function isInlineEditableField(field) { 838 if (!field || !field.tagName) return false;
839 var tag = field.tagName.toLowerCase(); 840 return ['img', 'picture', 'video', 'iframe', 'input', 'textarea', 'select', 'form'].indexOf(tag) === -1; 841 } 842 843 function isHeaderSection(section) { 844 return String(section && section.getAttribute ? section.getAttribute('data-ocf-section') || '' : '') === 'header'; 845 } 846 847 function isHeaderNavField(field, section) { 848 return isHeaderSection(section) && /^nav_/.test(String(field && field.getAttribute ? field.getAttribute('data-ocf-field') || '' : '')); 849 } 850 851 function setSelectedField(field) { 852 if (selectedField && selectedField !== field && selectedField.getAttribute('contenteditable') === 'true') { 853 selectedField.removeAttribute('contenteditable'); 854 } 855 if (selectedField && selectedField !== field) { 856 selectedField.classList.remove('ocf-editor-field-selected'); 857 } 858 selectedField = field; 859 if (!selectedField) return; 860 selectedField.classList.add('ocf-editor-field-selected'); 861 if (isInlineEditableField(selectedField)) { 862 selectedField.setAttribute('contenteditable', 'true'); 863 selectedField.setAttribute('spellcheck', 'true'); 864 } 865 } 866 867 function setSelectedPostItem(item) { 868 if (selectedPostItem && selectedPostItem !== item) { 869 selectedPostItem.classList.remove('ocf-editor-post-selected'); 870 } 871 selectedPostItem = item; 872 if (selectedPostItem) { 873 selectedPostItem.classList.add('ocf-editor-post-selected'); 874 } 875 } 876 877 function fieldValue(field) { 878 if (!field) return ''; 879 var fieldKey = field.getAttribute('data-ocf-field'); 880 if (fieldKey === 'hero_rating_stars') { 881 return String(starCountFromNode(field)); 882 } 883 var type = fieldType(fieldKey); 884 if (type === 'richtext') { 885 return String(field.innerHTML || '').trim(); 886 } 887 return (field.innerText || field.textContent || '').replace(/\u00a0/g, ' ').trim(); 888 } 889 890 function fieldType(fieldKey) { 891 var editor = window.ocfEditor || {}; 892 var schema = editor.schema || {}; 893 var fields = schema.fields || {}; 894 var def = fields[fieldKey] || {}; 895 return String(def.type || ''); 896 } 897 898 function setFieldRenderedValue(node, fieldKey, value) { 899 var tag = node && node.tagName ? node.tagName.toLowerCase() : ''; 900 if (fieldKey === 'hero_rating_stars') { 901 updateStarNodes(node, value); 902 return; 903 } 904 if (fieldType(fieldKey) === 'richtext') { 905 node.innerHTML = value || ''; 906 return; 907 } 908 if (tag === 'input' || tag === 'textarea') { 909 node.value = value || ''; 910 return; 911 } 912 node.textContent = value || ''; 913 } 914 915 function postFields(post) { 916 return post && post.ocf_state && typeof post.ocf_state === 'object' ? post.ocf_state : {}; 917 } 918 919 function applyPostToNode(node, post) { 920 var fields = postFields(post); 921 Object.keys(fields).forEach(function (field) { 922 node.querySelectorAll('[data-ocf-field="' + CSS.escape(field) + '"]').forEach(function (fieldNode) { 923 setFieldRenderedValue(fieldNode, field, fields[field] || ''); 924 }); 925 if (node.getAttribute('data-ocf-field') === field) { 926 setFieldRenderedValue(node, field, fields[field] || ''); 927 } 928 }); 929 } 930 931 function resetPreviewPostNode(node, postType, postId) { 932 node.setAttribute('data-ocf-post-type', postType); 933 node.setAttribute('data-ocf-post-id', String(postId)); 934 node.setAttribute('data-ocf-preview-loop-post', '1'); 935 node.removeAttribute('data-ocf-preview-loop-hidden'); 936 node.classList.remove('ocf-editor-post-selected'); 937 node.style.removeProperty('display'); 938 if (node.matches && node.matches('a[href]')) node.setAttribute('href', '#'); 939 node.querySelectorAll('a[href]').forEach(function (anchor) { 940 anchor.setAttribute('href', '#'); 941 }); 942 } 943 944 function loopTemplateGroups(postType) { 945 var groups = []; 946 var seen = []; 947 document.querySelectorAll('[data-ocf-post-type="' + CSS.escape(postType) + '"][data-ocf-post-id]:not([data-ocf-preview-loop-post])').forEach(function (node) { 948 var parent = node.parentNode; 949 if (!parent || seen.indexOf(parent) !== -1) return; 950 seen.push(parent); 951 groups.push({ parent: parent, template: node }); 952 }); 953 return groups; 954 } 955 956 function postLoopNodeInParent(parent, postType, postId) { 957 var nodes = Array.prototype.slice.call(parent.children || []); 958 for (var i = 0; i < nodes.length; i++) { 959 if ( 960 nodes[i] && 961 nodes[i].getAttribute && 962 nodes[i].getAttribute('data-ocf-post-type') === postType && 963 String(nodes[i].getAttribute('data-ocf-post-id') || '')
963=== String(postId) 964 ) { 965 return nodes[i]; 966 } 967 } 968 return null; 969 } 970 971 function reorderPostLoopPreviewNodes(postType, posts) { 972 loopTemplateGroups(postType).forEach(function (group) { 973 posts.forEach(function (post) { 974 var node = postLoopNodeInParent(group.parent, postType, post.id); 975 if (!node && Number(post.id) <= 0) { 976 node = group.template.cloneNode(true); 977 resetPreviewPostNode(node, postType, post.id); 978 } 979 if (!node) return; 980 applyPostToNode(node, post); 981 group.parent.appendChild(node); 982 }); 983 }); 984 } 985 986 function applyPostLoopPreview(postType, posts) { 987 postType = String(postType || ''); 988 if (!postType || !Array.isArray(posts)) return; 989 posts = posts.filter(function (post) { return post && !post._deleted && String(post.type || '') === postType; }); 990 var activeIds = posts.map(function (post) { return String(post.id); }); 991 document.querySelectorAll('[data-ocf-preview-loop-post][data-ocf-post-type="' + CSS.escape(postType) + '"]').forEach(function (node) { 992 node.remove(); 993 }); 994 document.querySelectorAll('[data-ocf-post-type="' + CSS.escape(postType) + '"][data-ocf-post-id]:not([data-ocf-preview-loop-post])').forEach(function (node) { 995 var id = String(node.getAttribute('data-ocf-post-id') || ''); 996 if (activeIds.indexOf(id) === -1) { 997 node.setAttribute('data-ocf-preview-loop-hidden', '1'); 998 return; 999 } 1000 node.removeAttribute('data-ocf-preview-loop-hidden'); 1001 node.style.removeProperty('display'); 1002 var match = posts.find(function (post) { return String(post.id) === id; }); 1003 if (match) applyPostToNode(node, match); 1004 }); 1005 reorderPostLoopPreviewNodes(postType, posts); 1006 } 1007 1008 function applyPostListPreview(posts) { 1009 if (!Array.isArray(posts)) return; 1010 var types = []; 1011 posts.forEach(function (post) { 1012 var type = String(post && post.type || ''); 1013 if (type && types.indexOf(type) === -1) types.push(type); 1014 }); 1015 document.querySelectorAll('[data-ocf-post-type][data-ocf-post-id]').forEach(function (node) { 1016 var type = String(node.getAttribute('data-ocf-post-type') || ''); 1017 if (type && types.indexOf(type) === -1) types.push(type); 1018 }); 1019 types.forEach(function (type) { 1020 applyPostLoopPreview(type, posts); 1021 }); 1022 } 1023 1024 function navFieldForItem(item) { 1025 var slug = normalizedNavSlug(item); 1026 var map = { 1027 home: 'nav_home', 1028 services: 'nav_services', 1029 company: 'nav_company', 1030 about: 'nav_about', 1031 blog: 'nav_blog', 1032 contact: 'nav_contact', 1033 industries: 'nav_industries', 1034 locations: 'nav_locations', 1035 service_areas: 'nav_service_areas' 1036 }; 1037 return map[slug] || ''; 1038 } 1039 1040 function normalizedNavSlug(item) { 1041 var type = String(item && item.type ? item.type : ''); 1042 var postType = String(item && item.post_type ? item.post_type : ''); 1043 var slug = String(item && item.slug ? item.slug : '').replace(/^archive_/, '').toLowerCase(); 1044 if (type === 'archive' && postType) slug = postType.toLowerCase(); 1045 var map = { 1046 service: 'services', 1047 service_area: 'service_areas', 1048 location: 'locations', 1049 industry: 'industries', 1050 post: 'blog' 1051 }; 1052 return map[slug] || slug; 1053 } 1054 1055 function navTemplateForField(section, field, mode) { 1056 if (!section || !field) return null; 1057 var selector = '[data-ocf-field="' + CSS.escape(field) + '"]'; 1058 var nodes = Array.prototype.slice.call(section.querySelectorAll(selector)); 1059 for (var index = 0;
1059 index < nodes.length; index += 1) { 1060 var node = nodes[index]; 1061 if (mode === 'desktop') { 1062 var desktop = node.closest('.nav-dropdown') || node.closest('a.nav-link'); 1063 if (desktop) return desktop.cloneNode(true); 1064 } else { 1065 var mobile = node.closest('.mobile-accordion') || node.closest('a.mobile-link'); 1066 if (mobile) return mobile.cloneNode(true); 1067 } 1068 } 1069 return null; 1070 } 1071 1072 function genericNavLink(item, field, mode) { 1073 var link = document.createElement('a'); 1074 link.href = item.url || '#'; 1075 link.className = mode === 'desktop' ? 'nav-link' : 'mobile-link'; 1076 if (field) link.setAttribute('data-ocf-field', field); 1077 link.textContent = item.label || 'Page'; 1078 return link; 1079 } 1080 1081 function navChildPostType(item) { 1082 if (!item || !item.show_items) return ''; 1083 var slug = normalizedNavSlug(item); 1084 var map = { 1085 services: 'service', 1086 service_areas: 'service_area', 1087 locations: 'location', 1088 industries: 'industry' 1089 }; 1090 return map[slug] || ''; 1091 } 1092 1093 function navChildLinks(postType, maxItems) { 1094 if (!postType) return []; 1095 var max = parseInt(maxItems, 10); 1096 var seen = {}; 1097 var links = []; 1098 document.querySelectorAll('[data-ocf-post-type="' + CSS.escape(postType) + '"][data-ocf-post-id]').forEach(function (node) { 1099 var anchor = node.matches && node.matches('a[href]') ? node : node.closest('a[href]'); 1100 if (!anchor) return; 1101 var id = node.getAttribute('data-ocf-post-id') || anchor.getAttribute('data-ocf-post-id') || anchor.href; 1102 if (seen[id]) return; 1103 var labelNode = anchor.querySelector('h3') || anchor.querySelector('.dropdown-item-title') || anchor; 1104 var label = String(labelNode.textContent || '').replace(/\s+/g, ' ').trim(); 1105 if (!label) return; 1106 seen[id] = true; 1107 links.push({ id: id, label: label, url: anchor.href }); 1108 }); 1109 if (Number.isFinite(max) && max > 0) return links.slice(0, max); 1110 return links; 1111 } 1112 1113 function genericNavDropdown(item, field, mode, children) { 1114 var label = String(item.label || 'Page'); 1115 var url = String(item.url || '#'); 1116 if (mode === 'mobile') { 1117 var accordion = document.createElement('div'); 1118 accordion.className = 'mobile-accordion'; 1119 var trigger = document.createElement('button'); 1120 trigger.className = 'mobile-accordion-trigger'; 1121 trigger.type = 'button'; 1122 trigger.setAttribute('data-accordion', ''); 1123 var triggerLabel = document.createElement('span'); 1124 if (field) triggerLabel.setAttribute('data-ocf-field', field); 1125 triggerLabel.textContent = label; 1126 trigger.appendChild(triggerLabel); 1127 trigger.appendChild(dropdownChevron('mobile-accordion-chevron', 16)); 1128 var panel = document.createElement('div'); 1129 panel.className = 'mobile-accordion-panel'; 1130 panel.appendChild(genericNavLink(item, field, 'mobile')); 1131 children.forEach(function (child) { 1132 var link = document.createElement('a'); 1133 link.className = 'mobile-link'; 1134 link.href = child.url || '#'; 1135 link.textContent = child.label || 'Item'; 1136 panel.appendChild(link); 1137 }); 1138 accordion.appendChild(trigger); 1139 accordion.appendChild(panel); 1140 return accordion; 1141 } 1142 1143 var dropdown = document.createElement('div'); 1144 dropdown.className = 'nav-dropdown'; 1145 dropdown.setAttribute('data-dropdown', ''); 1146 var triggerLink = document.createElement('a'); 1147 triggerLink.href = url; 1148 triggerLink.className = 'nav-link nav-dropdown-trigger'; 1149 if (field) triggerLink.setAttribute('data-ocf-field', field); 1150 var span = document.createElement('span'); 1151 span.textContent = label; 1152 triggerLink.appendChild(span); 1153 triggerLink.appendChild(dropdownChevron('nav-dropdown-chevron', 12)); 1154 var panelWrap = document.createElement('div'); 1155 panelWrap.className = 'nav-dropdown-panel'; 1156 var menu = document.createElement('div'); 1157 menu.className = 'dropdown-menu dropdown-menu--wide';
1158 children.forEach(function (child) { 1159 var link = document.createElement('a'); 1160 link.className = 'dropdown-item dropdown-item--area'; 1161 link.href = child.url || '#'; 1162 link.textContent = child.label || 'Item'; 1163 menu.appendChild(link); 1164 }); 1165 panelWrap.appendChild(menu); 1166 dropdown.appendChild(triggerLink); 1167 dropdown.appendChild(panelWrap); 1168 return dropdown; 1169 } 1170 1171 function dropdownChevron(className, size) { 1172 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 1173 svg.setAttribute('class', className); 1174 svg.setAttribute('width', String(size)); 1175 svg.setAttribute('height', String(size)); 1176 svg.setAttribute('fill', 'none'); 1177 svg.setAttribute('stroke', 'currentColor'); 1178 svg.setAttribute('viewBox', '0 0 24 24'); 1179 svg.setAttribute('stroke-width', '2'); 1180 var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 1181 path.setAttribute('d', 'M19 9l-7 7-7-7'); 1182 svg.appendChild(path); 1183 return svg; 1184 } 1185 1186 function updateNavNode(node, item, field) { 1187 var label = String(item.label || 'Page'); 1188 var url = String(item.url || '#'); 1189 if (node.matches && node.matches('a')) { 1190 node.href = url; 1191 } 1192 node.querySelectorAll('a.nav-link, a.mobile-link').forEach(function (link) { 1193 link.href = url; 1194 }); 1195 if (field) { 1196 var selector = '[data-ocf-field="' + CSS.escape(field) + '"]'; 1197 var fieldNodes = []; 1198 if (node.matches && node.matches(selector)) { 1199 fieldNodes.push(node); 1200 } 1201 fieldNodes = fieldNodes.concat(Array.prototype.slice.call(node.querySelectorAll(selector))); 1202 fieldNodes.forEach(function (fieldNode) { 1203 if (fieldNode.matches && fieldNode.matches('a')) { 1204 fieldNode.href = url; 1205 } 1206 var labelNode = fieldNode.matches && fieldNode.matches('a') ? fieldNode.querySelector('span') : null; 1207 if (labelNode) { 1208 labelNode.textContent = label; 1209 } else { 1210 fieldNode.textContent = label; 1211 } 1212 }); 1213 } 1214 if (!field) { 1215 node.textContent = label; 1216 } 1217 } 1218 1219 function updateHeaderNavPreview(items) { 1220 if (!Array.isArray(items)) return; 1221 var section = document.querySelector('[data-ocf-section="header"]') || document.querySelector('header'); 1222 if (!section) return; 1223 updateHeaderNavContainer(section, items, 'desktop'); 1224 updateHeaderNavContainer(section, items, 'mobile'); 1225 } 1226 1227 function updateHeaderNavContainer(section, items, mode) { 1228 var container = mode === 'desktop' ? section.querySelector('.nav-links') : section.querySelector('.mobile-menu-inner'); 1229 if (!container) return; 1230 var trailing = mode === 'mobile' ? Array.prototype.slice.call(container.children).filter(function (child) { 1231 return child.classList && child.classList.contains('mobile-actions'); 1232 }) : []; 1233 var nextNodes = []; 1234 items.forEach(function (item) { 1235 if (!item || !item.label) return; 1236 var field = navFieldForItem(item); 1237 var children = navChildLinks(navChildPostType(item), item.max_items); 1238 var node = children.length 1239 ? genericNavDropdown(item, field, mode, children) 1240 : genericNavLink(item, field, mode); 1241 updateNavNode(node, item, field); 1242 nextNodes.push(node); 1243 }); 1244 container.replaceChildren.apply(container, nextNodes.concat(trailing)); 1245 } 1246 1247 function starCountFromNode(node) { 1248 if (!node) return 5; 1249 var rating = parseFloat(node.getAttribute('data-rating') || ''); 1250 if (Number.isFinite(rating)) return Math.round(Math.max(0, Math.min(5, rating)) * 10) / 10; 1251 var customStars = node.querySelectorAll('.ocf-hero__star'); 1252 if (customStars.length) { 1253 var total = 0;
1254 customStars.forEach(function (star) { 1255 var fill = parseFloat((star.style.getPropertyValue('--star-fill') || '0').replace('%', '')); 1256 total += Number.isFinite(fill) ? Math.max(0, Math.min(100, fill)) / 100 : 0; 1257 }); 1258 return Math.round(total * 10) / 10; 1259 } 1260 var filled = node.querySelectorAll('svg.is-filled').length; 1261 if (filled || node.querySelectorAll('svg').length) return filled; 1262 return 5; 1263 } 1264 1265 function updateStarNodes(node, value) { 1266 var count = Math.max(0, Math.min(5, parseFloat(value) || 0)); 1267 count = Math.round(count * 10) / 10; 1268 node.setAttribute('data-rating', String(count)); 1269 var customStars = node.querySelectorAll('.ocf-hero__star'); 1270 if (customStars.length) { 1271 customStars.forEach(function (star, index) { 1272 var fill = Math.max(0, Math.min(1, count - index)); 1273 star.style.setProperty('--star-fill', String(Math.round(fill * 1000) / 10) + '%'); 1274 star.classList.toggle('is-filled', fill >= 1); 1275 star.classList.toggle('is-empty', fill <= 0); 1276 star.classList.toggle('is-partial', fill > 0 && fill < 1); 1277 }); 1278 return; 1279 } 1280 node.querySelectorAll('svg').forEach(function (svg, index) { 1281 var fill = Math.max(0, Math.min(1, count - index)); 1282 var filled = fill >= 1; 1283 svg.classList.toggle('is-filled', filled); 1284 svg.classList.toggle('is-empty', fill <= 0); 1285 if (filled) { 1286 svg.style.opacity = ''; 1287 } else { 1288 svg.style.opacity = String(0.24 + (fill * 0.76)); 1289 } 1290 }); 1291 } 1292 1293 function sectionFields(section) { 1294 var fields = []; 1295 section.querySelectorAll('[data-ocf-field]').forEach(function (node) { 1296 var field = node.getAttribute('data-ocf-field'); 1297 if (field && fields.indexOf(field) === -1) { 1298 fields.push(field); 1299 } 1300 }); 1301 return fields; 1302 } 1303 1304 function mediaUrl(node) { 1305 if (!node) return ''; 1306 var tag = node.tagName ? node.tagName.toLowerCase() : ''; 1307 if (tag === 'img') return node.getAttribute('src') || ''; 1308 if (tag === 'source') return node.getAttribute('srcset') || node.getAttribute('src') || ''; 1309 var background = window.getComputedStyle(node).backgroundImage || ''; 1310 var match = background.match(/url\(["']?([^"')]+)["']?\)/); 1311 return match ? match[1] : ''; 1312 } 1313 1314 function sectionFieldPreviews(section) { 1315 var previews = {}; 1316 section.querySelectorAll('[data-ocf-field]').forEach(function (node) { 1317 var field = node.getAttribute('data-ocf-field'); 1318 if (!field || previews[field]) return; 1319 var url = mediaUrl(node); 1320 var action = findAction(node, section); 1321 var instance = findInstance(node, section); 1322 previews[field] = { 1323 url: url, 1324 tag: node.tagName ? node.tagName.toLowerCase() : '', 1325 className: typeof node.className === 'string' ? node.className : '', 1326 role: fieldRole(node, field, section), 1327 action: action ? action.getAttribute('data-ocf-action') : '', 1328 actionSlot: instance ? instance.getAttribute('data-ocf-instance') : '', 1329 }; 1330 }); 1331 return previews; 1332 } 1333 1334 function sectionPostTypes(section) { 1335 var types = []; 1336 section.querySelectorAll('[data-ocf-post-type][data-ocf-post-id]').forEach(function (node) { 1337 var type = String(node.getAttribute('data-ocf-post-type') || ''); 1338 if (type && types.indexOf(type) === -1) { 1339 types.push(type); 1340 } 1341 }); 1342 return types; 1343 } 1344 1345 function fieldRole(node, fieldKey, section) { 1346 var key = String(fieldKey || '').toLowerCase(); 1347 var tag = node && node.tagName ? node.tagName.toLowerCase() : '';
1348 var className = node && typeof node.className === 'string' ? node.className.toLowerCase() : ''; 1349 var action = findAction(node, section); 1350 var actionName = action ? String(action.getAttribute('data-ocf-action') || '').toLowerCase() : ''; 1351 1352 if (actionName === 'primary_cta' || key.indexOf('cta_text') !== -1 || key.indexOf('button_text') !== -1) return 'Button Label'; 1353 if (actionName === 'phone' || key.indexOf('phone') !== -1) return 'Phone'; 1354 if (key.indexOf('email') !== -1) return 'Email'; 1355 if (key.indexOf('address') !== -1) return 'Address'; 1356 if (key.indexOf('rating_stars') !== -1 || key.indexOf('stars') !== -1) return 'Star Count'; 1357 if (key.indexOf('logo') !== -1) return 'Logo'; 1358 if (tag === 'img' || tag === 'source' || key.indexOf('image') !== -1) return 'Image'; 1359 if (className.indexOf('eyebrow') !== -1 || key.indexOf('eyebrow') !== -1) return 'Eyebrow'; 1360 if (/^h[1-6]$/.test(tag) || className.indexOf('headline') !== -1 || className.indexOf('heading') !== -1 || key.indexOf('headline') !== -1 || key.indexOf('heading') !== -1 || key.indexOf('_h1') !== -1 || key.indexOf('_h2') !== -1) return 'Heading'; 1361 if (className.indexOf('nav-') !== -1 || key.indexOf('nav_') === 0) return 'Navigation Label'; 1362 if (className.indexOf('footer-heading') !== -1) return 'Footer Heading'; 1363 if (className.indexOf('body') !== -1 || className.indexOf('description') !== -1 || className.indexOf('intro') !== -1 || key.indexOf('description') !== -1 || key.indexOf('subheadline') !== -1 || key.indexOf('intro') !== -1 || key.indexOf('bio') !== -1 || key.indexOf('content') !== -1) return 'Body Text'; 1364 if (tag === 'p') return 'Body Text'; 1365 return 'Text'; 1366 } 1367 1368 function fieldPayload(section, field, type) { 1369 var repeaterItem = findRepeaterItem(field, section); 1370 var action = findAction(field, section); 1371 var formSurface = findFormSurface(field, section); 1372 var postItem = findPostItem(field, section); 1373 var instance = findInstance(field, section); 1374 return { 1375 type: type, 1376 sectionId: section.getAttribute('data-ocf-section'), 1377 sectionInstance: section.getAttribute('data-ocf-section-instance') || '', 1378 field: field ? field.getAttribute('data-ocf-field') : '', 1379 value: field ? fieldValue(field) : '', 1380 fieldRole: field ? fieldRole(field, field.getAttribute('data-ocf-field'), section) : '', 1381 repeater: repeaterItem ? repeaterItem.getAttribute('data-ocf-repeater') : '', 1382 index: repeaterItem ? Number(repeaterItem.getAttribute('data-ocf-index')) : undefined, 1383 postType: postItem ? postItem.getAttribute('data-ocf-post-type') : '', 1384 postId: postItem ? Number(postItem.getAttribute('data-ocf-post-id')) : undefined, 1385 action: action ? action.getAttribute('data-ocf-action') : '', 1386 actionSlot: instance ? instance.getAttribute('data-ocf-instance') : '', 1387 gradient: section.getAttribute('data-ocf-gradient') || '', 1388 formSurface: Boolean(formSurface), 1389 fields: sectionFields(section), 1390 loopPostTypes: sectionPostTypes(section), 1391 previews: sectionFieldPreviews(section) 1392 }; 1393 } 1394 1395 function sectionPayload(section, type) { 1396 return { 1397 type: type, 1398 sectionId: section.getAttribute('data-ocf-section'), 1399 sectionInstance: section.getAttribute('data-ocf-section-instance') || '', 1400 field: '', 1401 postType: '', 1402 postId: undefined, 1403 action: '', 1404 actionSlot: '', 1405 gradient: section.getAttribute('data-ocf-gradient') || '', 1406 fields: sectionFields(section), 1407 loopPostTypes: sectionPostTypes(section), 1408 previews: sectionFieldPreviews(section) 1409 }; 1410 } 1411 1412 function postItemPayload(section, item, type) { 1413 return { 1414 type: type, 1415 sectionId: section.getAttribute('data-ocf-section'), 1416 sectionInstance: section.getAttribute('data-ocf-section-instance') || '', 1417 field: '', 1418 postType: item ? item.getAttribute('data-ocf-post-type')
1418: '', 1419 postId: item ? Number(item.getAttribute('data-ocf-post-id')) : undefined, 1420 action: '', 1421 actionSlot: '', 1422 gradient: section.getAttribute('data-ocf-gradient') || '', 1423 fields: sectionFields(section), 1424 loopPostTypes: sectionPostTypes(section), 1425 previews: sectionFieldPreviews(section) 1426 }; 1427 } 1428 1429 function restoredInitialSection() { 1430 if (!window.sessionStorage) return null; 1431 try { 1432 var raw = window.sessionStorage.getItem(restoreSelectionKey); 1433 if (!raw) return null; 1434 window.sessionStorage.removeItem(restoreSelectionKey); 1435 var restore = JSON.parse(raw); 1436 if (!restore || !restore.sectionId) return null; 1437 if (restore.sectionInstance) { 1438 var byInstance = document.querySelector('[data-ocf-section-instance="' + CSS.escape(String(restore.sectionInstance)) + '"]'); 1439 if (byInstance) return byInstance; 1440 } 1441 return document.querySelector('[data-ocf-section="' + CSS.escape(String(restore.sectionId)) + '"]'); 1442 } catch (error) { 1443 return null; 1444 } 1445 } 1446 1447 function findInitialSection() { 1448 var restored = restoredInitialSection(); 1449 if (restored) return restored; 1450 return Array.prototype.slice.call(document.querySelectorAll('[data-ocf-section]')).filter(isPageSectionElement)[0] || null; 1451 } 1452 1453 function selectInitialSection() { 1454 if (selectedSection) return; 1455 var section = findInitialSection(); 1456 if (!section) return; 1457 setSelectedSection(section); 1458 setSelectedField(null); 1459 window.parent.postMessage(sectionPayload(section, 'ocf:section-selected'), '*'); 1460 } 1461 1462 function cssString(value) { 1463 return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n|\r|\f/g, ' '); 1464 } 1465 1466 function fieldStyleCss(overrides) { 1467 if (!Array.isArray(overrides)) return ''; 1468 return overrides.map(function (row) { 1469 if (!row || row.scope !== 'home' || !row.section || !row.field) return ''; 1470 var declarations = []; 1471 var childDeclarations = []; 1472 if (typeof row.color_token === 'string' && /^[a-zA-Z0-9_]+$/.test(row.color_token)) { 1473 declarations.push('--field-color:var(' + cssNameForToken(row.color_token) + ');'); 1474 declarations.push('color:var(--field-color);'); 1475 } else if (typeof row.color === 'string' && row.color) { 1476 declarations.push('--field-color:' + row.color + ';'); 1477 declarations.push('color:var(--field-color);'); 1478 } 1479 if (typeof row.font_family === 'string' && row.font_family) { 1480 declarations.push('--field-font-family:"' + cssString(row.font_family) + '";'); 1481 declarations.push('font-family:var(--field-font-family);'); 1482 } 1483 if (typeof row.font_size === 'string' && row.font_size) { 1484 declarations.push('--field-font-size:' + row.font_size + ';'); 1485 declarations.push('font-size:var(--field-font-size)!important;'); 1486 } 1487 if (typeof row.font_weight === 'string' && row.font_weight) { 1488 declarations.push('--field-font-weight:' + row.font_weight + ';'); 1489 declarations.push('font-weight:var(--field-font-weight)!important;'); 1490 } 1491 if (typeof row.font_style === 'string' && row.font_style) { 1492 declarations.push('--field-font-style:' + row.font_style + ';'); 1493 declarations.push('font-style:var(--field-font-style)!important;'); 1494 childDeclarations.push('font-style:var(--field-font-style)!important;'); 1495 } 1496 if (typeof row.text_decoration === 'string' && row.text_decoration) { 1497 declarations.push('--field-text-decoration:' + row.text_decoration + ';'); 1498 declarations.push('text-decoration:var(--field-text-decoration)!important;'); 1499 childDeclarations.push('text-decoration:var(--field-text-decoration)!important;'); 1500 } 1501 if (!declarations.length) return ''; 1502 var selector = '[data-ocf-section="' + CSS.escape(row.section) + '"] [data-ocf-field="' + CSS.escape(row.field) + '"]'; 1503 return selector + '{' + declarations.join('') + '}' + (childDeclarations.length ? selector + ' *{' + childDeclarations.join('') + '}' : ''); 1504 }
1504).filter(Boolean).join('\n'); 1505 } 1506 1507 function updateFieldStyleOverrides(overrides) { 1508 var styleNode = document.getElementById('ocf-field-style-overrides'); 1509 if (!styleNode) { 1510 styleNode = document.createElement('style'); 1511 styleNode.id = 'ocf-field-style-overrides'; 1512 document.head.appendChild(styleNode); 1513 } 1514 styleNode.textContent = fieldStyleCss(overrides); 1515 } 1516 1517 function hasTextSelectionInside(node) { 1518 if (!node || !window.getSelection) return false; 1519 var selection = window.getSelection(); 1520 if (!selection || selection.isCollapsed || String(selection).trim() === '') return false; 1521 return node.contains(selection.anchorNode) || node.contains(selection.focusNode); 1522 } 1523 1524 function findNavigationTarget(target) { 1525 if (!target || !target.closest) return null; 1526 return target.closest('a[href], area[href], input[type="submit"], input[type="image"], button[type="submit"]'); 1527 } 1528 1529 function blockEditorNavigation(event) { 1530 if (event.target && event.target.closest && event.target.closest('.ocf-editor-wrap--frontend')) return; 1531 if (!findNavigationTarget(event.target)) return; 1532 event.preventDefault(); 1533 } 1534 1535 document.addEventListener('click', blockEditorNavigation, true); 1536 document.addEventListener('auxclick', blockEditorNavigation, true); 1537 document.addEventListener('submit', function (event) { 1538 event.preventDefault(); 1539 event.stopPropagation(); 1540 }, true); 1541 1542 document.addEventListener('mouseover', function (event) { 1543 var section = findSection(event.target); 1544 if (!section || section === selectedSection) return; 1545 section.classList.add('ocf-editor-hover'); 1546 }); 1547 1548 document.addEventListener('mouseout', function (event) { 1549 var section = findSection(event.target); 1550 if (!section || section === selectedSection) return; 1551 section.classList.remove('ocf-editor-hover'); 1552 }); 1553 1554 document.addEventListener('mousedown', function (event) { 1555 if (event.button !== 0) return; 1556 var section = findSection(event.target); 1557 if (!section) return; 1558 var field = findField(event.target, section); 1559 if (!field || !isInlineEditableField(field)) return; 1560 section.classList.remove('ocf-editor-hover'); 1561 setSelectedSection(section); 1562 setSelectedField(field); 1563 window.parent.postMessage(fieldPayload(section, field, 'ocf:section-selected'), '*'); 1564 }, true); 1565 1566 document.addEventListener('click', function (event) { 1567 var section = findSection(event.target); 1568 if (!section) return; 1569 var field = findField(event.target, section); 1570 var action = findAction(event.target, section); 1571 var postItem = findPostItem(event.target, section); 1572 var instance = findInstance(event.target, section); 1573 var formSurface = findFormSurface(event.target, section); 1574 var preserveTextSelection = field && isInlineEditableField(field) && hasTextSelectionInside(field); 1575 1576 if (postItem) { 1577 event.preventDefault(); 1578 event.stopPropagation(); 1579 section.classList.remove('ocf-editor-hover'); 1580 setSelectedSection(section); 1581 setSelectedField(null); 1582 setSelectedPostItem(postItem); 1583 window.parent.postMessage(postItemPayload(section, postItem, 'ocf:section-selected'), '*'); 1584 return; 1585 } 1586 1587 if (preserveTextSelection) { 1588 event.stopPropagation(); 1589 return; 1590 } 1591 event.preventDefault(); 1592 event.stopPropagation(); 1593 section.classList.remove('ocf-editor-hover'); 1594 setSelectedSection(section); 1595 setSelectedField(field); 1596 setSelectedPostItem(null); 1597 1598 window.parent.postMessage(field ? fieldPayload(section, field, 'ocf:section-selected') : { 1599 type: 'ocf:section-selected', 1600 sectionId: section.getAttribute('data-ocf-section'), 1601 sectionInstance: section.getAttribute('data-ocf-section-instance') || '', 1602 field: '', 1603 postType: postItem ? postItem.getAttribute('data-ocf-post-type')
1603: '', 1604 postId: postItem ? Number(postItem.getAttribute('data-ocf-post-id')) : undefined, 1605 action: action ? action.getAttribute('data-ocf-action') : '', 1606 actionSlot: instance ? instance.getAttribute('data-ocf-instance') : '', 1607 gradient: section.getAttribute('data-ocf-gradient') || '', 1608 formSurface: Boolean(formSurface), 1609 fields: sectionFields(section), 1610 loopPostTypes: sectionPostTypes(section), 1611 previews: sectionFieldPreviews(section) 1612 }, '*'); 1613 1614 if (field && isInlineEditableField(field)) { 1615 field.focus({ preventScroll: true }); 1616 } 1617 }, true); 1618 1619 document.addEventListener('input', function (event) { 1620 var section = findSection(event.target); 1621 if (!section) return; 1622 var field = findField(event.target, section); 1623 if (!field || !isInlineEditableField(field)) return; 1624 window.parent.postMessage(fieldPayload(section, field, 'ocf:field-preview-updated'), '*'); 1625 }, true); 1626 1627 document.addEventListener('paste', function (event) { 1628 var section = findSection(event.target); 1629 if (!section) return; 1630 var field = findField(event.target, section); 1631 if (!field || !isInlineEditableField(field)) return; 1632 event.preventDefault(); 1633 var text = (event.clipboardData || window.clipboardData).getData('text/plain'); 1634 document.execCommand('insertText', false, text); 1635 }, true); 1636 1637 var gradientOptions = { 1638 media_dark: { token: '--gradient-media-dark', tone: 'dark', overlay: 'max(0.88, var(--media-overlay-opacity))' }, 1639 media_dark_reverse: { token: '--gradient-media-dark-reverse', tone: 'dark', overlay: 'max(0.88, var(--media-overlay-opacity))' }, 1640 media_dark_even: { token: '--gradient-media-dark-even', tone: 'dark', overlay: 'max(0.88, var(--media-overlay-opacity))' }, 1641 media_light: { token: '--gradient-media-light', tone: 'light', overlay: 'var(--media-light-overlay-opacity)' }, 1642 media_light_reverse: { token: '--gradient-media-light-reverse', tone: 'light', overlay: 'var(--media-light-overlay-opacity)' }, 1643 media_light_even: { token: '--gradient-media-light-even', tone: 'light', overlay: 'var(--media-light-overlay-opacity)' }, 1644 media_light_transparent: { token: '--gradient-media-light-transparent', tone: 'light', overlay: '1' }, 1645 hero_dark: { token: '--gradient-hero-dark', tone: 'dark', overlay: '1' }, 1646 primary_fade: { token: '--gradient-primary-fade', tone: 'dark', overlay: '1' }, 1647 primary: { token: '--gradient-primary', tone: 'dark', overlay: '0.9' }, 1648 primary_reverse: { token: '--gradient-primary-reverse', tone: 'dark', overlay: '0.9' }, 1649 primary_soft: { token: '--gradient-primary-soft', tone: 'light', overlay: '0.92' }, 1650 primary_soft_reverse: { token: '--gradient-primary-soft-reverse', tone: 'light', overlay: '0.92' }, 1651 primary_secondary: { token: '--gradient-primary-secondary', tone: 'dark', overlay: '0.9' }, 1652 secondary_primary: { token: '--gradient-secondary-primary', tone: 'dark', overlay: '0.9' }, 1653 dark_primary: { token: '--gradient-dark-primary', tone: 'dark', overlay: '0.92' }, 1654 primary_dark: { token: '--gradient-primary-dark', tone: 'dark', overlay: '0.92' }, 1655 surface: { token: '--gradient-surface', tone: 'light', overlay: '0.92' }, 1656 surface_reverse: { token: '--gradient-surface-reverse', tone: 'light', overlay: '0.92' } 1657 }; 1658 1659 function tokenCssVar(token) { 1660 token = String(token || '').replace(/[^A-Za-z0-9_]/g, ''); 1661 if (!token) return ''; 1662 if (token.indexOf('color_') === 0) return '--ocf-' + token.slice(6).replace(/_/g, '-'); 1663 return '--ocf-' + token.replace(/_/g, '-'); 1664 } 1665 1666 function applySectionSettings(section, settings) {
1667 if (!section || !settings || typeof settings !== 'object') return; 1668 if (!originalSectionState.has(section)) { 1669 originalSectionState.set(section, { 1670 style: section.getAttribute('style'), 1671 gradient: section.getAttribute('data-ocf-gradient'), 1672 tone: section.getAttribute('data-ocf-tone') 1673 }); 1674 } 1675 var background = settings.background || {}; 1676 if (!background.mode && !background.color_token && !background.color && !background.gradient && !background.image_url) { 1677 var original = originalSectionState.get(section) || {}; 1678 if (original.style == null) section.removeAttribute('style'); 1679 else section.setAttribute('style', original.style); 1680 if (original.gradient == null) section.removeAttribute('data-ocf-gradient'); 1681 else section.setAttribute('data-ocf-gradient', original.gradient); 1682 if (original.tone == null) section.removeAttribute('data-ocf-tone'); 1683 else section.setAttribute('data-ocf-tone', original.tone); 1684 section.removeAttribute('data-ocf-bg-image-active'); 1685 return; 1686 } 1687 var mode = String(background.mode || ''); 1688 if (!mode) { 1689 if (background.color_token || background.color) mode = 'color'; 1690 else if (background.gradient) mode = 'gradient'; 1691 } 1692 if (!mode) mode = 'color'; 1693 var imageUrl = /^https?:\/\//.test(String(background.image_url || '')) ? String(background.image_url || '') : ''; 1694 var hasCustomImage = imageUrl !== ''; 1695 var attachment = String(background.attachment || '') === 'fixed' ? 'fixed' : 'scroll'; 1696 section.style.removeProperty('--section-bg'); 1697 section.style.removeProperty('--section-gradient'); 1698 section.style.removeProperty('--section-image'); 1699 section.style.removeProperty('--section-overlay-mix'); 1700 section.style.removeProperty('background'); 1701 section.style.removeProperty('background-color'); 1702 section.style.removeProperty('background-image'); 1703 section.style.removeProperty('background-size'); 1704 section.style.removeProperty('background-position'); 1705 section.style.removeProperty('background-repeat'); 1706 section.style.removeProperty('background-attachment'); 1707 section.removeAttribute('data-ocf-gradient'); 1708 section.removeAttribute('data-ocf-bg-image-active'); 1709 if (hasCustomImage) { 1710 section.setAttribute('data-ocf-bg-image-active', '1'); 1711 section.style.setProperty('--section-image', 'url("' + cssString(imageUrl) + '")'); 1712 section.style.backgroundSize = 'cover'; 1713 section.style.backgroundPosition = 'center'; 1714 section.style.backgroundRepeat = 'no-repeat'; 1715 section.style.backgroundAttachment = attachment; 1716 } 1717 if (mode === 'color') { 1718 var colorVar = tokenCssVar(background.color_token); 1719 var customColor = /^#[0-9a-fA-F]{6}$/.test(String(background.color || '')) ? String(background.color) : ''; 1720 var overlayColor = colorVar ? 'var(' + colorVar + ')' : customColor; 1721 var imageOverlay = hasCustomImage || sectionHasImageOverlay(section); 1722 var overlayMix = String(Math.round(Number(validOverlayValue(background.overlay, 0.55)) * 100)) + '%'; 1723 if (colorVar) { 1724 section.style.setProperty('--section-bg', 'var(' + colorVar + ')'); 1725 section.style.setProperty('--section-gradient', imageOverlay ? overlayColor : 'none'); 1726 section.style.setProperty('--section-overlay-opacity', imageOverlay ? validOverlayValue(background.overlay, 0.55) : '0'); 1727 section.style.backgroundColor = 'var(--section-bg)'; 1728 if (hasCustomImage) { 1729 section.style.setProperty('--section-overlay-mix', overlayMix); 1730 section.style.backgroundImage = 'linear-gradient(color-mix(in srgb, var(--section-bg) var(--section-overlay-mix), transparent), color-mix(in srgb, var(--section-bg) var(--section-overlay-mix), transparent)), var(--section-image)'; 1731 } else { 1732 section.style.background = 'var(--section-bg)'; 1733 } 1734 } else if (customColor) { 1735 section.style.setProperty('--section-bg', customColor); 1736 section.style.setProperty('--section-gradient', imageOverlay ? overlayColor : 'none'); 1737 section.style.setProperty('--section-overlay-opacity', imageOverlay ? validOverlayValue(background.overlay, 0.55) : '0'); 1738 section.style.backgroundColor = 'var(--section-bg)'; 1739 if (hasCustomImage) { 1740 section.style.setProperty('--section-overlay-mix', overlayMix); 1741 section.style.backgroundImage = 'linear-gradient(color-mix(in srgb, var(--section-bg) var(--section-overlay-mix), transparent), color-mix(in srgb, var(--section-bg) var(--section-overlay-mix), transparent)), var(--section-image)'; 1742 } else { 1743 section.style.background = 'var(--section-bg)'; 1744 } 1745 } else if (hasCustomImage) { 1746 section.style.backgroundImage = 'var(--section-image)'; 1747 } 1748 } else if (mode === 'gradient') { 1749 if (background.gradient) { 1750 applySectionGradient(section, background.gradient); 1751 if (hasCustomImage) { 1752 section.style.backgroundImage = 'var(--section-gradient), var(--section-image)'; 1753 } else { 1754 section.style.background = 'var(--section-gradient)'; 1755 } 1756 } else { 1757 section.style.setProperty('--section-gradient', 'none'); 1758 section.style.setProperty('--section-overlay-opacity', '0'); 1759 if (hasCustomImage) { 1760 section.style.backgroundImage = 'var(--section-image)'; 1761 } else { 1762 section.style.background = 'none'; 1763 } 1764 } 1765 } else if (hasCustomImage) { 1766 section.style.backgroundImage = 'var(--section-image)'; 1767 } 1768 if (/^(?:0(?:\.\d+)?|1(?:\.0+)?)$/.test(String(background.overlay || ''))) { 1769 section.style.setProperty('--section-overlay-opacity', String(background.overlay)); 1770 } else if (mode !== 'gradient' && !(mode === 'color' && sectionHasImageOverlay(section))) { 1771 section.style.removeProperty('--section-overlay-opacity'); 1772 } 1773 section.style.removeProperty('--section-pad-top'); 1774 section.style.removeProperty('--section-pad-bottom'); 1775 section.style.removeProperty('--section-container'); 1776 section.style.removeProperty('padding-top'); 1777 section.style.removeProperty('padding-bottom'); 1778 } 1779 1780 function applySectionGradient(section, value) { 1781 if (!section) return; 1782 var key = String(value || ''); 1783 var gradient = gradientOptions[key] || gradientOptions.primary_fade; 1784 section.setAttribute('data-ocf-gradient', gradientOptions[key] ? key : 'primary_fade'); 1785 section.setAttribute('data-ocf-tone', gradient.tone); 1786 section.style.setProperty('--section-gradient', 'var(' + gradient.token + ')'); 1787 section.style.setProperty('--section-overlay-opacity', gradient.overlay); 1788 } 1789 1790 function sectionFromMessage(message) { 1791 if (message.instance || message.sectionInstance) { 1792 var instance = String(message.instance || message.sectionInstance || ''); 1793 var byInstance = document.querySelector('[data-ocf-section-instance="' + CSS.escape(instance) + '"]'); 1794 if (byInstance) return byInstance; 1795 } 1796 return document.querySelector('[data-ocf-section="' + CSS.escape(message.sectionId || '') + '"]'); 1797 } 1798 1799 function sectionSelectorFromMessage(message) { 1800 if (message.instance || message.sectionInstance) { 1801 return '[data-ocf-section-instance="' + CSS.escape(String(message.instance || message.sectionInstance || '')) + '"]'; 1802 } 1803 return '[data-ocf-section="' + CSS.escape(message.sectionId || '') + '"]'; 1804 } 1805 1806 window.addEventListener('message', function (event) { 1807 var message = event.data || {}; 1808 if (message.type === 'ocf:select-section') { 1809 var section = sectionFromMessage(message); 1810 if (!section) return; 1811 setSelectedSection(section); 1812 setSelectedField(null); 1813 section.scrollIntoView({ behavior: 'smooth', block: 'center' }); 1814 } 1815 1816 if (message.type === 'ocf:section-order-updated') { 1817 applySectionOrder(message.order || []); 1818 } 1819 1820 if (message.type === 'ocf:preview-section-settings-updated') { 1821 applySectionSettings(sectionFromMessage(message), message.settings || {}); 1822 } 1823 1824 if (message.type === 'ocf:preview-field-updated') { 1825 if (/_gradient$/.test(String(message.field || ''))) { 1826 applySectionGradient(sectionFromMessage(message), message.value || ''); 1827 } 1828 var selector = sectionSelectorFromMessage(message) + ' [data-ocf-field="' + CSS.escape(message.field) + '"]'; 1829 document.querySelectorAll(selector).forEach(function (node) { 1830 setFieldRenderedValue(node, message.field, message.value || ''); 1831 }); 1832 } 1833 1834 if (message.type === 'ocf:preview-global-field-updated') { 1835 document.querySelectorAll('[data-ocf-field="' + CSS.escape(message.field) + '"]').forEach(function (node) { 1836 setFieldRenderedValue(node, message.field, message.value || ''); 1837 }); 1838 } 1839 1840 if (message.type === 'ocf:preview-repeater-field-updated') { 1841 var repeaterSelector = sectionSelectorFromMessage(message) + ' [data-ocf-repeater="' + CSS.escape(message.repeater) + '"][data-ocf-index="' + String(message.index) + '"] [data-ocf-field="' + CSS.escape(message.field) + '"]'; 1842 document.querySelectorAll(repeaterSelector).forEach(function (node) { 1843 setFieldRenderedValue(node, message.field, message.value || ''); 1844 }); 1845 } 1846 1847 if (message.type === 'ocf:preview-repeater-updated' && Array.isArray(message.items)) {
1848 message.items.forEach(function (item, index) { 1849 Object.keys(item || {}).forEach(function (field) { 1850 var itemSelector = '[data-ocf-section="' + CSS.escape(message.sectionId) + '"] [data-ocf-repeater="' + CSS.escape(message.repeater) + '"][data-ocf-index="' + String(index) + '"] [data-ocf-field="' + CSS.escape(field) + '"]'; 1851 document.querySelectorAll(itemSelector).forEach(function (node) { 1852 setFieldRenderedValue(node, field, item[field] || ''); 1853 }); 1854 }); 1855 }); 1856 } 1857 1858 if (message.type === 'ocf:preview-post-field-updated') { 1859 var postSelector = '[data-ocf-post-type="' + CSS.escape(message.postType || '') + '"][data-ocf-post-id="' + String(message.postId || '') + '"] [data-ocf-field="' + CSS.escape(message.field || '') + '"]'; 1860 document.querySelectorAll(postSelector).forEach(function (node) { 1861 var type = fieldType(message.field || ''); 1862 if (message.url && (type === 'image' || type === 'image_id') && node.tagName && node.tagName.toLowerCase() === 'img') { 1863 node.setAttribute('src', message.url); 1864 return; 1865 } 1866 setFieldRenderedValue(node, message.field, message.value || ''); 1867 }); 1868 } 1869 1870 if (message.type === 'ocf:preview-post-loop-updated') { 1871 applyPostLoopPreview(message.postType || '', message.posts || []); 1872 } 1873 1874 if (message.type === 'ocf:preview-nav-menu-updated') { 1875 updateHeaderNavPreview(message.items || []); 1876 } 1877 1878 if (message.type === 'ocf:preview-image-updated') { 1879 var imageSelector = '[data-ocf-section="' + CSS.escape(message.sectionId) + '"] [data-ocf-field="' + CSS.escape(message.field) + '"]'; 1880 document.querySelectorAll(imageSelector).forEach(function (node) { 1881 if (node.tagName && node.tagName.toLowerCase() === 'img') { 1882 node.setAttribute('src', message.url || ''); 1883 } else { 1884 node.style.backgroundImage = message.url ? 'url("' + message.url + '")' : ''; 1885 } 1886 }); 1887 if (message.field === 'page_featured_image') { 1888 var imageSection = document.querySelector('[data-ocf-section="' + CSS.escape(message.sectionId) + '"]'); 1889 if (imageSection) { 1890 imageSection.style.setProperty('--page-header-image', message.url ? 'url("' + message.url + '")' : ''); 1891 } 1892 } 1893 } 1894 1895 if (message.type === 'ocf:preview-global-image-updated') { 1896 document.querySelectorAll('[data-ocf-field="' + CSS.escape(message.field) + '"]').forEach(function (node) { 1897 if (node.tagName && node.tagName.toLowerCase() === 'img') { 1898 node.setAttribute('src', message.url || ''); 1899 } else { 1900 node.style.backgroundImage = message.url ? 'url("' + message.url + '")' : ''; 1901 } 1902 }); 1903 } 1904 1905 if (message.type === 'ocf:preview-theme-updated' && message.tokens && typeof message.tokens === 'object') { 1906 updateThemeTokens(message.tokens); 1907 } 1908 1909 if (message.type === 'ocf:preview-theme-mode-updated') { 1910 updateThemeMode(message.mode); 1911 } 1912 1913 if (message.type === 'ocf:preview-field-style-overrides-updated') { 1914 updateFieldStyleOverrides(message.overrides); 1915 } 1916 1917 if (message.type === 'ocf:preview-editor-state-restored') { 1918 if (message.design && typeof message.design === 'object') { 1919 updateThemeTokens(message.design); 1920 } 1921 if (message.site && typeof message.site === 'object') { 1922 Object.keys(message.site).forEach(function (fieldKey) { 1923 var value = message.site[fieldKey]; 1924 if (typeof value === 'object') return; 1925 document.querySelectorAll('[data-ocf-field="' + CSS.escape(fieldKey) + '"]').forEach(function (node) { 1926 setFieldRenderedValue(node, fieldKey, value == null ? '' : String(value)); 1927 }); 1928 }); 1929 if (typeof message.site.theme_mode === 'string') { 1930 updateThemeMode(message.site.theme_mode); 1931 } 1932 } 1933 applyPostListPreview(message.posts || []); 1934 updateFieldStyleOverrides(message.overrides); 1935 } 1936 }); 1937 1938 window.setTimeout(function () { 1939 var editor = window.ocfEditor || {}; 1940 var pkg = editor.package || {}; 1941 applyPostListPreview(pkg.posts || []); 1942 selectInitialSection(); 1943 }, 0); 1944 1945 function updateThemeTokens(tokens) { 1946 Object.keys(tokens || {}
1946).forEach(function (key) { 1947 var value = tokens[key]; 1948 if (typeof value !== 'string') return; 1949 document.documentElement.style.setProperty(cssNameForToken(key), value); 1950 }); 1951 } 1952 1953 function updateThemeMode(mode) { 1954 mode = String(mode || 'light').toLowerCase(); 1955 if (['light', 'dark'].indexOf(mode) === -1) mode = 'light'; 1956 document.documentElement.setAttribute('data-theme-mode', mode); 1957 document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light'); 1958 } 1959 1960 function cssNameForToken(key) { 1961 return '--ocf-' + String(key || '') 1962 .replace(/^color_/, '') 1963 .replace(/^font_/, 'font_') 1964 .replace(/_/g, '-'); 1965 } 1966 } 1967 1968 onReady(function () { 1969 applyStoredSectionOrder(); 1970 initPhoneCombos(); 1971 initLeadForms(); 1972 initBookingWizard(); 1973 initEditorBridge(); 1974 }); 1975})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.