{"product_id":"custom-garage-show","title":"Custom Garage Show","description":"\u003cdiv class=\"hfx-custom-product\" data-hfx-custom-product\u003e\n  \u003cdiv class=\"hfx-trailer-panel\" data-hfx-trailer\u003e\n    \u003cvideo class=\"hfx-trailer-panel__video\" controls autoplay muted loop playsinline preload=\"metadata\" poster=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0574\/3693\/0255\/files\/preview_images\/00ff05f058654dcba600aac35abc16fa.thumbnail.0000000000.jpg?v=1787501359\"\u003e\n      \u003csource src=\"https:\/\/cdn.shopify.com\/videos\/c\/vp\/00ff05f058654dcba600aac35abc16fa\/00ff05f058654dcba600aac35abc16fa.HD-1080p-7.2Mbps-92390059.mp4\" type=\"video\/mp4\"\u003e\n      \u003csource src=\"https:\/\/cdn.shopify.com\/videos\/c\/vp\/00ff05f058654dcba600aac35abc16fa\/00ff05f058654dcba600aac35abc16fa.HD-720p-4.5Mbps-92390059.mp4\" type=\"video\/mp4\"\u003e\u003c\/source\u003e\u003c\/video\u003e\n    \u003cp class=\"hfx-trailer-panel__caption\"\u003eWatch the Custom Garage Show teaser.\u003c\/p\u003e\n  \u003c\/div\u003e\n\n  \u003csection class=\"hfx-customization-section\" data-hfx-customization-section\u003e\n    \u003cdiv class=\"hfx-customization-section__intro\"\u003e\n      \u003cp class=\"hfx-customization-section__eyebrow\"\u003eCustomize your show\u003c\/p\u003e\n      \u003ch2\u003eCreate your garage door image\u003c\/h2\u003e\n      \u003cp\u003eCreate a custom 16:9 garage door image using your own text, or upload a finished 16:9 image. Approve the image you want, then checkout. HallowFX manually integrates the approved image into your final mini projection show.\u003c\/p\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv class=\"hfx-custom-steps\" aria-label=\"How custom orders work\"\u003e\n      \u003cdiv class=\"hfx-custom-step\"\u003e\n\u003cspan\u003e1\u003c\/span\u003e\u003cstrong\u003eWatch the teaser\u003c\/strong\u003e\u003cp\u003eUse the video above to see the style of the finished show.\u003c\/p\u003e\n\u003c\/div\u003e\n      \u003cdiv class=\"hfx-custom-step\"\u003e\n\u003cspan\u003e2\u003c\/span\u003e\u003cstrong\u003eCreate or upload\u003c\/strong\u003e\u003cp\u003eEnter the text for your door, or upload your own 16:9 artwork.\u003c\/p\u003e\n\u003c\/div\u003e\n      \u003cdiv class=\"hfx-custom-step\"\u003e\n\u003cspan\u003e3\u003c\/span\u003e\u003cstrong\u003eApprove the image\u003c\/strong\u003e\u003cp\u003eClick Yes - use this image so the approved artwork is saved to your order.\u003c\/p\u003e\n\u003c\/div\u003e\n      \u003cdiv class=\"hfx-custom-step\"\u003e\n\u003cspan\u003e4\u003c\/span\u003e\u003cstrong\u003eCheckout\u003c\/strong\u003e\u003cp\u003eAfter purchase, we build and manually deliver your finished video.\u003c\/p\u003e\n\u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv class=\"hfx-configurator\" data-hfx-configurator\u003e\n      \u003cdiv class=\"hfx-configurator__controls\"\u003e\n        \u003clabel for=\"hfx-door-text\"\u003eYour Text for your Door\u003c\/label\u003e\n        \u003cinput id=\"hfx-door-text\" type=\"text\" maxlength=\"40\" placeholder=\"Jones Family\" autocomplete=\"off\"\u003e\n        \u003cp class=\"hfx-configurator__hint\"\u003eUse letters, numbers, spaces, hyphens, or apostrophes. Max 40 characters.\u003c\/p\u003e\n        \u003cdiv class=\"hfx-human-check\"\u003e\n          \u003clabel for=\"hfx-human-check\"\u003eQuick human check\u003c\/label\u003e\n          \u003cinput id=\"hfx-human-check\" type=\"text\" maxlength=\"20\" placeholder=\"Type Halloween\" autocomplete=\"off\" spellcheck=\"false\" data-hfx-human-check\u003e\n          \u003cp class=\"hfx-configurator__hint\"\u003eBefore generating, type Halloween in the box above.\u003c\/p\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"hfx-configurator__error\" data-hfx-error hidden\u003e\u003c\/p\u003e\n        \u003cdiv class=\"hfx-configurator__actions\"\u003e\n          \u003cbutton type=\"button\" class=\"btn\" data-hfx-generate\u003eGenerate preview\u003c\/button\u003e\n          \u003cbutton type=\"button\" class=\"btn hfx-lock-button\" data-hfx-lock disabled\u003eYes - use this image\u003c\/button\u003e\n        \u003c\/div\u003e\n        \u003cdiv class=\"hfx-upload\"\u003e\n          \u003clabel for=\"hfx-upload-image\"\u003eOr upload your own 16:9 image\u003c\/label\u003e\n          \u003cinput id=\"hfx-upload-image\" type=\"file\" accept=\"image\/png,image\/jpeg,image\/webp\" data-hfx-upload\u003e\n          \u003cp class=\"hfx-configurator__hint\"\u003eAccepted: PNG, JPG, or WebP under 10 MB. Image must be 16:9. If upload fails, place the order and email the image with your order number to info@hallowfx.com.\u003c\/p\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003cdiv class=\"hfx-configurator__preview-panel\"\u003e\n        \u003cdiv class=\"hfx-configurator__preview\" data-hfx-preview aria-live=\"polite\"\u003e\n          \u003cimg data-hfx-preview-img src=\"https:\/\/cdn.shopify.com\/s\/files\/1\/0574\/3693\/0255\/files\/custom-garage-show-cover-preview_5863d379-7bd8-4551-9d1b-69c7b5df31e4.png?v=1787333036\" alt=\"Custom Garage Show 16:9 preview example\"\u003e\n          \u003cdiv class=\"hfx-progress\" data-hfx-progress hidden\u003e\n            \u003cdiv class=\"hfx-progress__content\"\u003e\n              \u003cp class=\"hfx-progress__eyebrow\"\u003eCreating preview\u003c\/p\u003e\n              \u003cdiv class=\"hfx-progress__track\"\u003e\u003cspan data-hfx-progress-bar\u003e\u003c\/span\u003e\u003c\/div\u003e\n              \u003cdiv class=\"hfx-progress__meta\"\u003e\n                \u003cspan data-hfx-progress-label\u003eWarming the projector...\u003c\/span\u003e\n                \u003cspan data-hfx-progress-time\u003eabout 30 sec\u003c\/span\u003e\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"hfx-configurator__status\" data-hfx-preview-status\u003eExample preview. Generate your own image or upload a finished 16:9 design.\u003c\/p\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003cp class=\"hfx-customization-section__delivery\"\u003e\u003cstrong\u003eManual delivery:\u003c\/strong\u003e After purchase, HallowFX will integrate your approved garage-door image into a custom mini projection show and deliver the finished video manually.\u003c\/p\u003e\n  \u003c\/section\u003e\n\u003c\/div\u003e\n\n\u003cstyle\u003e\n  .hfx-custom-product { color: #f5f1ed; }\n  .hfx-trailer-panel { width: 100%; margin: 0 0 22px; border: 1px solid rgba(255,255,255,.18); background: #050505; padding: clamp(10px, 1.8vw, 16px); }\n  .hfx-trailer-panel__video { display: block; width: 100%; aspect-ratio: 16 \/ 9; background: #070707; object-fit: contain; }\n  .hfx-trailer-panel__caption { margin: 10px 0 0; color: #ddd3c8; font-size: .95rem; }\n  .hfx-product-trailer-host .hfx-trailer-panel, .product-single__photos .hfx-trailer-panel, .product__media-wrapper .hfx-trailer-panel { margin-top: 0; margin-bottom: 20px; }\n  .hfx-customization-section { clear: both; width: min(1180px, calc(100vw - 32px)); margin: 42px auto 0; color: #f5f1ed; }\n  .hfx-customization-section:not([data-hfx-mounted=\"after-product\"]) { margin-left: 50%; transform: translateX(-50%); }\n  .hfx-customization-section__intro { max-width: 900px; margin-bottom: 22px; }\n  .hfx-customization-section__eyebrow { margin: 0 0 8px; color: #a7ff49; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; }\n  .hfx-customization-section h2 { margin: 0 0 12px; color: #fff; font-size: clamp(2rem, 4vw, 3.25rem); line-height: 1.05; letter-spacing: 0; text-transform: none; }\n  .hfx-customization-section__intro p, .hfx-customization-section__delivery { color: #ddd3c8; font-size: 1.08rem; line-height: 1.55; }\n  .hfx-custom-steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin: 24px 0 28px; }\n  .hfx-custom-step { border: 1px solid rgba(255,255,255,.16); background: rgba(255,255,255,.045); padding: 18px; }\n  .hfx-custom-step span { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; margin-bottom: 12px; background: #ff6a1a; color: #120701; font-weight: 900; }\n  .hfx-custom-step strong { display: block; color: #fff; margin-bottom: 8px; }\n  .hfx-custom-step p { margin: 0; color: #ddd3c8; font-size: .96rem; line-height: 1.45; }\n  .hfx-configurator { display: grid; grid-template-columns: minmax(280px, .68fr) minmax(520px, 1.55fr); gap: 24px; align-items: start; margin: 26px 0; }\n  .hfx-configurator__controls { border: 1px solid rgba(255,255,255,.18); background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.02)); padding: 22px; }\n  .hfx-configurator__preview-panel { width: 100%; }\n  .hfx-configurator label { display: block; margin-bottom: 8px; color: #fff; font-weight: 700; }\n  .hfx-configurator input[type=\"text\"], .hfx-configurator input[type=\"file\"] { width: 100%; min-height: 46px; padding: 10px 12px; border: 1px solid rgba(255,255,255,.25); background: #101010; color: #fff; }\n  .hfx-human-check { margin-top: 18px; padding-top: 16px; border-top: 1px solid rgba(255,255,255,.12); }\n  .hfx-upload { margin-top: 22px; padding-top: 18px; border-top: 1px solid rgba(255,255,255,.16); }\n  .hfx-configurator__hint, .hfx-configurator__error, .hfx-configurator__status, .hfx-progress__meta { font-size: .95rem; }\n  .hfx-configurator__error { color: #ff7a4d; }\n  .hfx-configurator__actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }\n  .hfx-lock-button[disabled], .hfx-configurator [data-hfx-generate][disabled] { opacity: .45; cursor: not-allowed; }\n  .hfx-configurator__preview { position: relative; overflow: hidden; aspect-ratio: 16 \/ 9; width: 100%; min-height: 320px; border: 1px solid rgba(255,255,255,.18); background: #070707; }\n  .hfx-configurator__preview img { display: block; width: 100%; height: 100%; object-fit: cover; }\n  .hfx-configurator__preview[data-busy=\"true\"] img { filter: brightness(.45) saturate(.8); }\n  .hfx-configurator__status { margin: 10px 0 0; color: #f5f1ed; }\n  .hfx-progress[hidden] { display: none; }\n  .hfx-progress { position: absolute; inset: 0; z-index: 3; display: flex; align-items: center; justify-content: center; padding: clamp(16px, 4vw, 36px); background: radial-gradient(circle at 50% 45%, rgba(55,0,0,.35), rgba(0,0,0,.78) 68%); }\n  .hfx-progress__content { width: min(680px, 92%); padding: clamp(16px, 3vw, 28px); border: 1px solid rgba(255,255,255,.24); background: rgba(0,0,0,.74); box-shadow: 0 18px 70px rgba(0,0,0,.58), 0 0 40px rgba(180,0,0,.22); }\n  .hfx-progress__eyebrow { margin: 0 0 12px; color: #fff; font-size: clamp(1.1rem, 2.4vw, 1.7rem); font-weight: 800; text-align: center; text-transform: uppercase; letter-spacing: .08em; }\n  .hfx-progress__track { position: relative; overflow: hidden; width: 100%; height: 18px; border: 1px solid rgba(255,255,255,.3); background: rgba(0,0,0,.62); }\n  .hfx-progress__track span { display: block; width: 0; height: 100%; background: linear-gradient(90deg, #6e0000, #d41414, #ff5a32); box-shadow: 0 0 22px rgba(255, 38, 18, .7); transition: width .24s linear; }\n  .hfx-progress__track::after { content: ''; position: absolute; inset: 0; background: linear-gradient(110deg, transparent 0 38%, rgba(255,255,255,.38) 50%, transparent 62%); transform: translateX(-100%); animation: hfx-progress-sheen 1.35s linear infinite; }\n  .hfx-progress__meta { display: flex; justify-content: space-between; gap: 12px; margin-top: 10px; color: #fff; font-weight: 700; }\n  .hfx-progress__meta span:last-child { color: #f7b267; white-space: nowrap; }\n  @keyframes hfx-progress-sheen { to { transform: translateX(100%); } }\n  @media only screen and (max-width: 980px) { .hfx-custom-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } .hfx-configurator { grid-template-columns: 1fr; } .hfx-configurator__preview { min-height: 0; } }\n  @media only screen and (max-width: 560px) { .hfx-customization-section { width: 100%; margin-left: 0; transform: none; } .hfx-custom-steps { grid-template-columns: 1fr; } .hfx-progress__meta { flex-direction: column; gap: 4px; text-align: center; } }\n\u003c\/style\u003e\n\n\u003cscript\u003e\n(function() {\n  var API_ENDPOINT = 'https:\/\/hallowfx-custom-preview.smdoyle.chatgpt.site\/api\/generate-garage-preview';\n  var UPLOAD_ENDPOINT = 'https:\/\/hallowfx-custom-preview.smdoyle.chatgpt.site\/api\/upload-garage-image';\n  var AVERAGE_GENERATION_MS = 30000;\n  var ASPECT_RATIO = 16 \/ 9;\n  var ASPECT_TOLERANCE = 0.01;\n  var HUMAN_CHECK_ANSWER = 'halloween';\n  var root = document.querySelector('[data-hfx-custom-product]');\n  var configurator = document.querySelector('[data-hfx-configurator]');\n  if (!root || !configurator) return;\n\n  function moveHfxSections() {\n    var trailer = root.querySelector('[data-hfx-trailer]');\n    var customSection = root.querySelector('[data-hfx-customization-section]');\n    var productForm = document.querySelector('form[action*=\"\/cart\/add\"]');\n    if (!productForm) return;\n    var productShell = productForm.closest('.product-single, .product, .product-template, section[id*=\"ProductSection\"], section[class*=\"product\"], .shopify-section') || productForm.parentElement;\n    var gallerySelectors = ['.product-single__photos', '.product__media-wrapper', '.product__media-list', '.product__media', '.product-media', '.product-gallery', '[class*=\"product\"][class*=\"media\"]', '[class*=\"product\"][class*=\"photo\"]'];\n    var gallery = null;\n    for (var i = 0; i \u003c gallerySelectors.length; i++) {\n      var matches = document.querySelectorAll(gallerySelectors[i]);\n      for (var j = 0; j \u003c matches.length; j++) {\n        if (!matches[j].contains(productForm) \u0026\u0026 !matches[j].contains(root)) { gallery = matches[j]; break; }\n      }\n      if (gallery) break;\n    }\n    if (!gallery \u0026\u0026 productShell) {\n      var children = productShell.children || [];\n      for (var c = 0; c \u003c children.length; c++) {\n        if (!children[c].contains(productForm) \u0026\u0026 !children[c].contains(root)) { gallery = children[c]; break; }\n      }\n    }\n    if (gallery \u0026\u0026 trailer \u0026\u0026 !gallery.contains(trailer)) { gallery.classList.add('hfx-product-trailer-host'); gallery.insertBefore(trailer, gallery.firstChild); }\n    if (productShell \u0026\u0026 customSection \u0026\u0026 productShell.parentNode \u0026\u0026 !customSection.hasAttribute('data-hfx-mounted')) { productShell.parentNode.insertBefore(customSection, productShell.nextSibling); customSection.setAttribute('data-hfx-mounted', 'after-product'); }\n  }\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', moveHfxSections); else moveHfxSections();\n  window.setTimeout(moveHfxSections, 800);\n\n  var input = document.getElementById('hfx-door-text');\n  var humanCheckInput = configurator.querySelector('[data-hfx-human-check]');\n  var uploadInput = configurator.querySelector('[data-hfx-upload]');\n  var error = configurator.querySelector('[data-hfx-error]');\n  var generateButton = configurator.querySelector('[data-hfx-generate]');\n  var lockButton = configurator.querySelector('[data-hfx-lock]');\n  var preview = configurator.querySelector('[data-hfx-preview]');\n  var previewImage = configurator.querySelector('[data-hfx-preview-img]');\n  var status = configurator.querySelector('[data-hfx-preview-status]');\n  var progress = configurator.querySelector('[data-hfx-progress]');\n  var progressBar = configurator.querySelector('[data-hfx-progress-bar]');\n  var progressLabel = configurator.querySelector('[data-hfx-progress-label]');\n  var progressTime = configurator.querySelector('[data-hfx-progress-time]');\n  var productForm = document.querySelector('form[action*=\"\/cart\/add\"]');\n  var addButtons = productForm ? productForm.querySelectorAll('[type=\"submit\"], button[name=\"add\"]') : [];\n  var generated = null;\n  var progressTimer = null;\n  var objectUrl = null;\n  var blocked = ['kill','murder','corpse','gore','hate'];\n  var progressStages = [\n    { at: 0, label: 'Warming the projector...' },\n    { at: 18, label: 'Composing the garage door...' },\n    { at: 38, label: 'Painting the blood lettering...' },\n    { at: 62, label: 'Adding handprints and splatter...' },\n    { at: 82, label: 'Finishing the preview...' }\n  ];\n\n  function showError(message) { error.textContent = message; error.hidden = !message; }\n  function cleanDoorText(value) { return value.replace(\/\\s+\/g, ' ').trim(); }\n  function cleanHumanCheck(value) { return value.replace(\/\\s+\/g, ' ').trim().toLowerCase(); }\n  function validateHumanCheck() { return humanCheckInput \u0026\u0026 cleanHumanCheck(humanCheckInput.value) === HUMAN_CHECK_ANSWER; }\n  function validateDoorText(value) {\n    var doorText = cleanDoorText(value);\n    if (!doorText) return 'Enter text for your door first.';\n    if (doorText.length \u003e 40) return 'Keep the door text under 40 characters.';\n    if (!\/^[A-Za-z0-9](?:[A-Za-z0-9 '\\-]*[A-Za-z0-9])?$\/.test(doorText)) return 'Use letters, numbers, spaces, hyphens, or apostrophes only.';\n    var lower = doorText.toLowerCase();\n    for (var i = 0; i \u003c blocked.length; i++) { if (lower.indexOf(blocked[i]) !== -1) return 'Please choose different door text.'; }\n    return '';\n  }\n  function setAddToCartEnabled(enabled) { for (var i = 0; i \u003c addButtons.length; i++) { addButtons[i].disabled = !enabled; addButtons[i].setAttribute('aria-disabled', String(!enabled)); } }\n  function setProperty(name, value) { if (!productForm) return; var inputName = 'properties[' + name + ']'; var field = productForm.querySelector('[name=\"' + inputName + '\"]'); if (!field) { field = document.createElement('input'); field.type = 'hidden'; field.name = inputName; productForm.appendChild(field); } field.value = value; }\n  function setProgressPercent(percent) { progressBar.style.width = Math.max(0, Math.min(100, percent)) + '%'; }\n  function stageLabelFor(percent) { var label = progressStages[0].label; for (var i = 0; i \u003c progressStages.length; i++) { if (percent \u003e= progressStages[i].at) label = progressStages[i].label; } return label; }\n  function stopProgress(success) { if (progressTimer) clearInterval(progressTimer); progressTimer = null; if (success) { setProgressPercent(100); progressLabel.textContent = 'Preview ready.'; progressTime.textContent = 'done'; window.setTimeout(function() { progress.hidden = true; preview.dataset.busy = 'false'; }, 900); } else { progress.hidden = true; preview.dataset.busy = 'false'; setProgressPercent(0); } }\n  function startProgress(label) { var startedAt = Date.now(); stopProgress(false); preview.dataset.busy = 'true'; progress.hidden = false; setProgressPercent(3); progressLabel.textContent = label || 'Warming the projector...'; progressTime.textContent = 'about 30 sec'; progressTimer = setInterval(function() { var elapsed = Date.now() - startedAt; var percent = Math.min(96, 4 + (elapsed \/ AVERAGE_GENERATION_MS) * 90); var secondsLeft = Math.max(1, Math.ceil((AVERAGE_GENERATION_MS - elapsed) \/ 1000)); setProgressPercent(percent); progressLabel.textContent = label || stageLabelFor(percent); progressTime.textContent = percent \u003e= 94 ? 'almost ready' : '~' + secondsLeft + ' sec'; }, 250); }\n  function clearLock() { lockButton.disabled = true; setAddToCartEnabled(false); }\n  function clearObjectUrl() { if (objectUrl) URL.revokeObjectURL(objectUrl); objectUrl = null; }\n\n  clearLock();\n  input.addEventListener('input', function() { if (generated \u0026\u0026 generated.source === 'upload') return; status.textContent = 'Generate a 16:9 preview to continue.'; showError(''); generated = null; clearLock(); stopProgress(false); });\n  if (humanCheckInput) humanCheckInput.addEventListener('input', function() { showError(''); });\n  if (uploadInput) {\n    uploadInput.addEventListener('change', function() {\n      var file = uploadInput.files \u0026\u0026 uploadInput.files[0];\n      showError(''); generated = null; clearLock(); stopProgress(false);\n      if (!file) return;\n      var image = new Image();\n      var localUrl = URL.createObjectURL(file);\n      image.onload = function() {\n        var ratio = image.naturalWidth \/ image.naturalHeight;\n        clearObjectUrl(); objectUrl = localUrl;\n        if (Math.abs(ratio - ASPECT_RATIO) \u003e ASPECT_TOLERANCE) { URL.revokeObjectURL(localUrl); objectUrl = null; uploadInput.value = ''; showError('Upload a 16:9 image. Example sizes: 1920x1080, 1280x720, or 3840x2160.'); status.textContent = 'Uploaded image was not 16:9.'; return; }\n        previewImage.src = localUrl;\n        status.textContent = 'Uploading and validating your 16:9 image...';\n        startProgress('Securing your uploaded image...');\n        var formData = new FormData(); formData.append('image', file);\n        fetch(UPLOAD_ENDPOINT, { method: 'POST', body: formData })\n          .then(function(response) { return response.json().then(function(body) { if (!response.ok) throw new Error(body \u0026\u0026 body.error ? body.error : 'Upload failed.'); return body; }); })\n          .then(function(result) { generated = { source: 'upload', doorText: cleanDoorText(input.value) || 'Customer uploaded image', previewImageUrl: result.uploadedImageUrl, generationId: result.uploadId, promptVersion: 'customer-upload-16x9', uploadWidth: result.width, uploadHeight: result.height }; status.textContent = 'Uploaded image saved. Click Yes - use this image before adding to cart.'; lockButton.disabled = false; stopProgress(true); })\n          .catch(function(err) { showError((err.message || 'Upload failed.') + ' You can still order and email your 16:9 image with the order number to info@hallowfx.com.'); status.textContent = 'Upload could not be saved yet.'; clearLock(); stopProgress(false); });\n      };\n      image.onerror = function() { URL.revokeObjectURL(localUrl); uploadInput.value = ''; showError('That file could not be read as an image. Try a PNG, JPG, or WebP.'); };\n      image.src = localUrl;\n    });\n  }\n  generateButton.addEventListener('click', function() {\n    var doorText = cleanDoorText(input.value);\n    var validation = validateDoorText(doorText);\n    if (validation) { showError(validation); clearLock(); stopProgress(false); return; }\n    if (!validateHumanCheck()) { showError('Please type Halloween in the quick human check before generating.'); clearLock(); stopProgress(false); return; }\n    showError(''); generated = null; clearLock(); if (uploadInput) uploadInput.value = '';\n    generateButton.disabled = true; status.textContent = 'Generating your 16:9 garage door preview...'; startProgress();\n    fetch(API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application\/json' }, body: JSON.stringify({ doorText: doorText, humanCheck: humanCheckInput ? humanCheckInput.value : '' }) })\n      .then(function(response) { return response.json().then(function(body) { if (!response.ok) throw new Error(body \u0026\u0026 body.error ? body.error : 'Preview generation failed.'); return body; }); })\n      .then(function(result) { var resultText = result.doorText || result.familyName || doorText; generated = { source: 'generated', doorText: resultText, previewImageUrl: result.previewImageUrl, generationId: result.generationId, promptVersion: result.promptVersion }; if (result.previewImageUrl) previewImage.src = result.previewImageUrl; status.textContent = 'Preview generated. Click Yes - use this image before adding to cart.'; lockButton.disabled = false; stopProgress(true); })\n      .catch(function(err) { showError(err.message || 'Preview generation failed. Please try again.'); status.textContent = 'Preview could not be generated yet.'; clearLock(); stopProgress(false); })\n      .finally(function() { generateButton.disabled = false; });\n  });\n  lockButton.addEventListener('click', function() {\n    if (!generated) return;\n    setProperty('Door text', generated.doorText);\n    setProperty('Image source', generated.source === 'upload' ? 'Customer upload' : 'Generated preview');\n    setProperty('Preview image URL', generated.previewImageUrl);\n    setProperty('Generation ID', generated.generationId);\n    setProperty('Prompt version', generated.promptVersion);\n    setProperty('Approved image', 'yes');\n    setProperty('Delivery method', 'manual custom video');\n    if (generated.source === 'upload') { setProperty('Uploaded image URL', generated.previewImageUrl); setProperty('Upload ID', generated.generationId); setProperty('Uploaded image size', generated.uploadWidth + 'x' + generated.uploadHeight); }\n    status.textContent = 'Image approved. You can now add this custom show to cart.';\n    setAddToCartEnabled(true);\n  });\n})();\n\u003c\/script\u003e","brand":"HALLOWFX","offers":[{"title":"Custom Garage Show","offer_id":57098932617542,"sku":"HFX-CUSTOM-GARAGE-SHOW","price":59.99,"currency_code":"USD","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0574\/3693\/0255\/files\/custom-garage-show-cover-preview_5863d379-7bd8-4551-9d1b-69c7b5df31e4.png?v=1787333036","url":"https:\/\/hallowfx.com\/products\/custom-garage-show","provider":"HALLOWFX","version":"1.0","type":"link"}