{"product_id":"personalised-embroidered-baseball-cap-custom-name-text-organic-cotton-hat","title":"Personalised Embroidered Baseball Cap Custom Name Text Organic Cotton Hat","description":"\u003c!-- Product Description HTML: USA STAR FASHION mini design studio with Save Message --\u003e\n\n\u003cdiv class=\"usf-desc-studio\" id=\"usf-desc-studio\"\u003e\n  \u003cstyle\u003e\n    #usf-desc-studio{font-family:Arial,sans-serif;margin:20px 0;border:2px solid #111;border-radius:16px;overflow:hidden;background:#fff;color:#111}\n    #usf-desc-studio *{box-sizing:border-box}\n    #usf-desc-studio button,#usf-desc-studio input,#usf-desc-studio select,#usf-desc-studio textarea{font:inherit}\n    #usf-desc-studio .usf-top{background:#000;color:#fff;padding:14px;border-bottom:4px solid #ff4bb8}\n    #usf-desc-studio .usf-title{font-size:22px;font-weight:900;margin-bottom:4px}\n    #usf-desc-studio .usf-sub{font-size:13px;color:#cbd5e1}\n    #usf-desc-studio .usf-methods{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px}\n    #usf-desc-studio .usf-method{min-height:46px;border:2px solid #475569;border-radius:10px;background:#111827;color:#fff;font-weight:900;cursor:pointer}\n    #usf-desc-studio .usf-method.active[data-method=\"dtf\"]{background:#2563eb;border-color:#93c5fd}\n    #usf-desc-studio .usf-method.active[data-method=\"embroidery\"]{background:#7c3aed;border-color:#c4b5fd}\n    #usf-desc-studio .usf-wrap{padding:12px;display:grid;grid-template-columns:260px 1fr;gap:12px}\n    #usf-desc-studio .usf-panel{border:1px solid #ddd;border-radius:12px;padding:10px;background:#fafafa}\n    #usf-desc-studio label{display:block;font-size:12px;font-weight:900;color:#555;margin:8px 0 4px}\n    #usf-desc-studio input,#usf-desc-studio select,#usf-desc-studio textarea,#usf-desc-studio .usf-panel button,#usf-desc-studio .usf-actions button{width:100%;min-height:38px;border:1px solid #bbb;border-radius:8px;padding:7px;background:#fff;color:#111}\n    #usf-desc-studio input[type=color]{padding:3px}\n    #usf-desc-studio textarea{min-height:76px;resize:vertical}\n    #usf-desc-studio button{font-weight:900;cursor:pointer}\n    #usf-desc-studio .blue{background:#2563eb!important;color:#fff!important;border-color:#2563eb!important}\n    #usf-desc-studio .purple{background:#7c3aed!important;color:#fff!important;border-color:#7c3aed!important}\n    #usf-desc-studio .black{background:#000!important;color:#fff!important;border-color:#000!important}\n    #usf-desc-studio .green{background:#22c55e!important;color:#052e16!important;border-color:#22c55e!important}\n    #usf-desc-studio .orange{background:#f97316!important;color:#fff!important;border-color:#f97316!important}\n    #usf-desc-studio .row{display:grid;grid-template-columns:1fr 1fr;gap:7px}\n    #usf-desc-studio .stage{position:relative;min-height:420px;display:flex;align-items:center;justify-content:center;background:#fff;border:1px solid #eee;border-radius:12px;overflow:hidden}\n    #usf-desc-studio canvas{width:100%;max-width:680px;aspect-ratio:1\/1.15;display:block;touch-action:none;cursor:grab}\n    #usf-desc-studio canvas.drag{cursor:grabbing}\n    #usf-desc-studio .usf-actions{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin-top:10px}\n    #usf-desc-studio .status{font-size:13px;font-weight:800;color:#555;margin-top:8px;min-height:20px}\n    #usf-desc-studio .finish{margin-top:10px;border-top:1px solid #ddd;padding-top:10px}\n    #usf-desc-studio .hidden{display:none!important}\n    #usf-desc-studio .toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:999999;background:#000;color:#fff;padding:14px 18px;border-radius:999px;font-weight:900;box-shadow:0 14px 40px #0005;display:none;text-align:center;max-width:92vw}\n    #usf-desc-studio .toast.show{display:block}\n    @media(max-width:800px){#usf-desc-studio .usf-wrap{grid-template-columns:1fr}#usf-desc-studio .usf-actions{grid-template-columns:1fr 1fr}}\n  \u003c\/style\u003e\n\n  \u003cdiv class=\"usf-top\"\u003e\n    \u003cdiv class=\"usf-title\"\u003eCustomize This Product\u003c\/div\u003e\n    \u003cdiv class=\"usf-sub\"\u003eChoose DTF or Embroidery, create your mockup, then save your message.\u003c\/div\u003e\n\n    \u003cdiv class=\"usf-methods\"\u003e\n      \u003cbutton type=\"button\" class=\"usf-method active\" data-method=\"dtf\"\u003eDTF Print\u003c\/button\u003e\n      \u003cbutton type=\"button\" class=\"usf-method\" data-method=\"embroidery\"\u003eEmbroidery\u003c\/button\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\n  \u003cdiv class=\"usf-wrap\"\u003e\n    \u003cdiv class=\"usf-panel\"\u003e\n      \u003clabel\u003eAdd text\u003c\/label\u003e\n      \u003cinput value=\"Your Brand\" data-text\u003e\n\n      \u003clabel\u003eFont\u003c\/label\u003e\n      \u003cselect data-font\u003e\n        \u003coption value=\"Impact\"\u003eImpact\u003c\/option\u003e\n        \u003coption value=\"Arial Black\"\u003eArial Black\u003c\/option\u003e\n        \u003coption value=\"Arial\"\u003eArial\u003c\/option\u003e\n        \u003coption value=\"Georgia\"\u003eGeorgia\u003c\/option\u003e\n        \u003coption value=\"Times New Roman\"\u003eTimes\u003c\/option\u003e\n        \u003coption value=\"Trebuchet MS\"\u003eTrebuchet\u003c\/option\u003e\n        \u003coption value=\"Brush Script MT\"\u003eBrush Script\u003c\/option\u003e\n      \u003c\/select\u003e\n\n      \u003clabel\u003eStyle\u003c\/label\u003e\n      \u003cselect data-style\u003e\n        \u003coption value=\"normal\"\u003eNormal\u003c\/option\u003e\n        \u003coption value=\"sticker\"\u003eDTF Sticker\u003c\/option\u003e\n        \u003coption value=\"shadow\"\u003eDTF Shadow\u003c\/option\u003e\n        \u003coption value=\"retro\"\u003eDTF Retro\u003c\/option\u003e\n        \u003coption value=\"stitch\"\u003eEmbroidery Stitch\u003c\/option\u003e\n      \u003c\/select\u003e\n\n      \u003cdiv class=\"row\"\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eFill \/ Thread\u003c\/label\u003e\n          \u003cinput type=\"color\" value=\"#ffffff\" data-fill\u003e\n        \u003c\/div\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eOutline\u003c\/label\u003e\n          \u003cinput type=\"color\" value=\"#111111\" data-stroke\u003e\n        \u003c\/div\u003e\n      \u003c\/div\u003e\n\n      \u003clabel\u003eSize\u003c\/label\u003e\n      \u003cinput type=\"range\" min=\"20\" max=\"190\" value=\"68\" data-size\u003e\n\n      \u003cbutton type=\"button\" class=\"blue\" data-add-text\u003eAdd Text\u003c\/button\u003e\n\n      \u003clabel\u003eUpload artwork\u003c\/label\u003e\n      \u003cbutton type=\"button\" class=\"black\" data-upload-btn\u003eUpload Image\u003c\/button\u003e\n      \u003cinput class=\"hidden\" type=\"file\" accept=\"image\/png,image\/jpeg,image\/webp\" data-upload\u003e\n\n      \u003cbutton type=\"button\" data-remove-bg\u003eRemove White Background\u003c\/button\u003e\n\n      \u003cdiv class=\"finish\"\u003e\n        \u003clabel\u003eYour email \/ contact\u003c\/label\u003e\n        \u003cinput type=\"text\" placeholder=\"customer@email.com or phone\" data-contact\u003e\n\n        \u003clabel\u003eDesign note\u003c\/label\u003e\n        \u003ctextarea placeholder=\"Example: Center chest DTF print, white logo, remove background.\" data-note\u003e\u003c\/textarea\u003e\n\n        \u003clabel\u003eQuantity\u003c\/label\u003e\n        \u003cinput type=\"number\" min=\"1\" value=\"1\" data-qty\u003e\n\n        \u003cbutton type=\"button\" class=\"green\" data-add-cart\u003eAdd Product To Cart\u003c\/button\u003e\n        \u003cbutton type=\"button\" class=\"orange\" data-save-message\u003eSave Message\u003c\/button\u003e\n      \u003c\/div\u003e\n\n      \u003cdiv class=\"status\" data-status\u003eProduct detection loading...\u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv\u003e\n      \u003cdiv class=\"stage\"\u003e\n        \u003ccanvas width=\"1000\" height=\"1150\" data-canvas\u003e\u003c\/canvas\u003e\n      \u003c\/div\u003e\n\n      \u003cdiv class=\"usf-actions\"\u003e\n        \u003cbutton type=\"button\" data-center\u003eCenter\u003c\/button\u003e\n        \u003cbutton type=\"button\" data-fit\u003eFit\u003c\/button\u003e\n        \u003cbutton type=\"button\" data-delete\u003eDelete\u003c\/button\u003e\n        \u003cbutton type=\"button\" data-download\u003eDownload PNG\u003c\/button\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n  \u003c\/div\u003e\n\n  \u003cdiv class=\"toast\" data-toast\u003eProduct saved. Any questions? Ask us.\u003c\/div\u003e\n\n  \u003cscript\u003e\n    (() =\u003e {\n      const root = document.getElementById('usf-desc-studio');\n      if (!root || root.dataset.ready === 'true') return;\n      root.dataset.ready = 'true';\n\n      const $ = (s) =\u003e root.querySelector(s);\n      const canvas = $('[data-canvas]');\n      const ctx = canvas.getContext('2d');\n\n      const els = {\n        methodButtons: [...root.querySelectorAll('[data-method]')],\n        text: $('[data-text]'),\n        font: $('[data-font]'),\n        style: $('[data-style]'),\n        fill: $('[data-fill]'),\n        stroke: $('[data-stroke]'),\n        size: $('[data-size]'),\n        addText: $('[data-add-text]'),\n        uploadBtn: $('[data-upload-btn]'),\n        upload: $('[data-upload]'),\n        removeBg: $('[data-remove-bg]'),\n        center: $('[data-center]'),\n        fit: $('[data-fit]'),\n        delete: $('[data-delete]'),\n        download: $('[data-download]'),\n        contact: $('[data-contact]'),\n        note: $('[data-note]'),\n        qty: $('[data-qty]'),\n        addCart: $('[data-add-cart]'),\n        saveMessage: $('[data-save-message]'),\n        status: $('[data-status]'),\n        toast: $('[data-toast]')\n      };\n\n      const state = {\n        method: 'dtf',\n        productTitle: findProductTitle(),\n        variantId: findVariantId(),\n        productImageUrl: findProductImage(),\n        productImage: null,\n        selected: null,\n        layers: [],\n        drag: null\n      };\n\n      function setStatus(text) {\n        els.status.textContent = text;\n      }\n\n      function findProductTitle() {\n        const selectors = ['h1.product__title', '.product__title h1', '.product__title', 'main h1', 'h1'];\n\n        for (const selector of selectors) {\n          const node = document.querySelector(selector);\n          const text = node \u0026\u0026 node.textContent \u0026\u0026 node.textContent.trim();\n\n          if (text) return text;\n        }\n\n        return document.title.replace(\/\\s*[–|-]\\s*.*$\/, '').trim() || 'Custom product';\n      }\n\n      function findVariantId() {\n        const urlVariant = new URLSearchParams(window.location.search).get('variant');\n\n        if (urlVariant \u0026\u0026 Number(urlVariant)) return urlVariant;\n\n        const selectors = [\n          'form[action*=\"\/cart\/add\"] [name=\"id\"]',\n          'product-form [name=\"id\"]',\n          '[data-product-form] [name=\"id\"]',\n          'select[name=\"id\"]',\n          'input[name=\"id\"]',\n          'variant-selects select',\n          'variant-radios input:checked'\n        ];\n\n        for (const selector of selectors) {\n          const node = document.querySelector(selector);\n          const value = node \u0026\u0026 node.value;\n\n          if (value \u0026\u0026 Number(value)) return value;\n        }\n\n        return '';\n      }\n\n      function findProductImage() {\n        const selectors = [\n          '.product__media img',\n          '.product-media img',\n          '.product-gallery img',\n          '.product__media-list img',\n          '.product img',\n          'main img'\n        ];\n\n        for (const selector of selectors) {\n          const image = document.querySelector(selector);\n          const src = image \u0026\u0026 (image.currentSrc || image.src);\n\n          if (src \u0026\u0026 !src.startsWith('data:image')) return src;\n        }\n\n        return '';\n      }\n\n      function loadImage(src) {\n        return new Promise((resolve, reject) =\u003e {\n          const img = new Image();\n\n          img.onload = () =\u003e resolve(img);\n          img.onerror = reject;\n\n          if (!src.startsWith('data:')) img.crossOrigin = 'anonymous';\n\n          img.src = src;\n        });\n      }\n\n      async function loadProductImage() {\n        state.productTitle = findProductTitle();\n        state.variantId = findVariantId();\n        state.productImageUrl = findProductImage();\n\n        if (!state.productImageUrl) {\n          setStatus('Product image not detected. Using blank mockup.');\n          render();\n          return;\n        }\n\n        try {\n          state.productImage = await loadImage(state.productImageUrl);\n          setStatus(state.variantId ? 'Product detected: ' + state.productTitle : 'Product image detected, but variant ID not found.');\n          render();\n        } catch (e) {\n          setStatus('Product image could not load.');\n          render();\n        }\n      }\n\n      function printArea() {\n        return state.method === 'embroidery'\n          ? { x: 330, y: 430, w: 340, h: 170 }\n          : { x: 300, y: 360, w: 400, h: 460 };\n      }\n\n      function drawProduct() {\n        ctx.fillStyle = '#fff';\n        ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n        if (state.productImage) {\n          const img = state.productImage;\n          const scale = Math.min(830 \/ img.width, 960 \/ img.height);\n          const w = img.width * scale;\n          const h = img.height * scale;\n\n          ctx.drawImage(img, (1000 - w) \/ 2, (1150 - h) \/ 2, w, h);\n        } else {\n          ctx.fillStyle = '#111';\n          ctx.fillRect(250, 220, 500, 700);\n        }\n      }\n\n      function drawGuide() {\n        const area = printArea();\n\n        ctx.save();\n        ctx.strokeStyle = state.method === 'embroidery' ? '#7c3aed' : '#2563eb';\n        ctx.fillStyle = state.method === 'embroidery' ? 'rgba(124,58,237,.08)' : 'rgba(37,99,235,.08)';\n        ctx.setLineDash([8, 6]);\n        ctx.lineWidth = 3;\n        ctx.fillRect(area.x, area.y, area.w, area.h);\n        ctx.strokeRect(area.x, area.y, area.w, area.h);\n        ctx.restore();\n      }\n\n      function drawTextLayer(layer) {\n        ctx.save();\n        ctx.translate(layer.x, layer.y);\n        ctx.rotate(layer.rotate * Math.PI \/ 180);\n        ctx.globalAlpha = layer.opacity;\n\n        ctx.font = `900 ${layer.size}px ${layer.font}`;\n        ctx.textAlign = 'center';\n        ctx.textBaseline = 'middle';\n        ctx.lineJoin = 'round';\n\n        if (layer.style === 'shadow') {\n          ctx.shadowColor = '#ff4bb8';\n          ctx.shadowBlur = 8;\n          ctx.shadowOffsetX = 8;\n          ctx.shadowOffsetY = 8;\n        }\n\n        if (layer.style === 'retro') {\n          ctx.fillStyle = '#ff4bb8';\n\n          for (let i = 10; i \u003e= 2; i -= 2) {\n            ctx.save();\n            ctx.translate(i, i);\n            ctx.fillText(layer.text, 0, 0);\n            ctx.restore();\n          }\n        }\n\n        if (layer.style === 'sticker') {\n          ctx.strokeStyle = '#fff';\n          ctx.lineWidth = Math.max(layer.strokeWidth, 14);\n          ctx.strokeText(layer.text, 0, 0);\n        }\n\n        if (layer.style === 'stitch' || layer.mode === 'embroidery') {\n          ctx.strokeStyle = layer.fill;\n          ctx.lineWidth = 2;\n\n          for (let x = -layer.w \/ 2; x \u003c layer.w \/ 2; x += 9) {\n            ctx.beginPath();\n            ctx.moveTo(x, -layer.h \/ 2);\n            ctx.lineTo(x + layer.h, layer.h \/ 2);\n            ctx.stroke();\n          }\n        }\n\n        ctx.strokeStyle = layer.stroke;\n        ctx.lineWidth = layer.strokeWidth;\n        ctx.fillStyle = layer.fill;\n\n        if (layer.strokeWidth \u003e 0) ctx.strokeText(layer.text, 0, 0);\n        ctx.fillText(layer.text, 0, 0);\n\n        ctx.restore();\n      }\n\n      function drawImageLayer(layer) {\n        ctx.save();\n        ctx.translate(layer.x, layer.y);\n        ctx.rotate(layer.rotate * Math.PI \/ 180);\n        ctx.globalAlpha = layer.opacity;\n        ctx.drawImage(layer.image, -layer.w \/ 2, -layer.h \/ 2, layer.w, layer.h);\n        ctx.restore();\n      }\n\n      function drawSelection() {\n        const layer = state.selected;\n        if (!layer) return;\n\n        ctx.save();\n        ctx.translate(layer.x, layer.y);\n        ctx.rotate(layer.rotate * Math.PI \/ 180);\n        ctx.strokeStyle = layer.mode === 'embroidery' ? '#7c3aed' : '#2563eb';\n        ctx.lineWidth = 3;\n        ctx.setLineDash([7, 5]);\n        ctx.strokeRect(-layer.w \/ 2, -layer.h \/ 2, layer.w, layer.h);\n        ctx.setLineDash([]);\n        ctx.fillStyle = '#fff';\n        ctx.strokeStyle = '#111';\n        ctx.fillRect(layer.w \/ 2 - 10, layer.h \/ 2 - 10, 20, 20);\n        ctx.strokeRect(layer.w \/ 2 - 10, layer.h \/ 2 - 10, 20, 20);\n        ctx.restore();\n      }\n\n      function render(exporting = false) {\n        drawProduct();\n\n        const area = printArea();\n\n        ctx.save();\n        ctx.beginPath();\n        ctx.rect(area.x, area.y, area.w, area.h);\n        ctx.clip();\n\n        state.layers.forEach((layer) =\u003e {\n          if (layer.kind === 'image') drawImageLayer(layer);\n          else drawTextLayer(layer);\n        });\n\n        ctx.restore();\n\n        if (!exporting) {\n          drawGuide();\n          drawSelection();\n        }\n      }\n\n      function addText() {\n        const area = printArea();\n        const text = els.text.value || 'Your Brand';\n        const size = Number(els.size.value || 68);\n\n        const layer = {\n          id: Date.now(),\n          kind: 'text',\n          mode: state.method,\n          text,\n          font: els.font.value,\n          style: state.method === 'embroidery' ? 'stitch' : els.style.value,\n          fill: els.fill.value,\n          stroke: els.stroke.value,\n          strokeWidth: state.method === 'embroidery' ? 1 : 6,\n          size,\n          w: Math.max(140, text.length * size * 0.62),\n          h: size * 1.4,\n          x: area.x + area.w \/ 2,\n          y: area.y + area.h \/ 2,\n          rotate: 0,\n          opacity: 1\n        };\n\n        state.layers.push(layer);\n        state.selected = layer;\n        render();\n      }\n\n      async function addImage(src) {\n        const image = await loadImage(src);\n        const area = printArea();\n        const scale = Math.min(area.w * 0.9 \/ image.width, area.h * 0.9 \/ image.height, 1);\n\n        const layer = {\n          id: Date.now(),\n          kind: 'image',\n          mode: state.method,\n          image,\n          src,\n          originalSrc: src,\n          w: image.width * scale,\n          h: image.height * scale,\n          x: area.x + area.w \/ 2,\n          y: area.y + area.h \/ 2,\n          rotate: 0,\n          opacity: 1\n        };\n\n        state.layers.push(layer);\n        state.selected = layer;\n        render();\n      }\n\n      async function removeWhiteBackground() {\n        const layer = state.selected;\n\n        if (!layer || layer.kind !== 'image') {\n          setStatus('Select uploaded image first.');\n          return;\n        }\n\n        const image = await loadImage(layer.src);\n        const temp = document.createElement('canvas');\n        const tempCtx = temp.getContext('2d');\n\n        temp.width = image.width;\n        temp.height = image.height;\n        tempCtx.drawImage(image, 0, 0);\n\n        const imageData = tempCtx.getImageData(0, 0, temp.width, temp.height);\n        const data = imageData.data;\n\n        for (let i = 0; i \u003c data.length; i += 4) {\n          const distance = Math.hypot(data[i] - 255, data[i + 1] - 255, data[i + 2] - 255);\n          if (distance \u003c 70) data[i + 3] = 0;\n        }\n\n        tempCtx.putImageData(imageData, 0, 0);\n        layer.src = temp.toDataURL('image\/png');\n        layer.image = await loadImage(layer.src);\n        render();\n      }\n\n      function canvasPoint(event) {\n        const rect = canvas.getBoundingClientRect();\n\n        return {\n          x: (event.clientX - rect.left) * canvas.width \/ rect.width,\n          y: (event.clientY - rect.top) * canvas.height \/ rect.height\n        };\n      }\n\n      function hit(point) {\n        for (let i = state.layers.length - 1; i \u003e= 0; i -= 1) {\n          const layer = state.layers[i];\n\n          if (Math.abs(point.x - layer.x) \u003c= layer.w \/ 2 \u0026\u0026 Math.abs(point.y - layer.y) \u003c= layer.h \/ 2) {\n            return layer;\n          }\n        }\n\n        return null;\n      }\n\n      function exportImageDataUrl() {\n        render(true);\n        const url = canvas.toDataURL('image\/png');\n        render();\n        return url;\n      }\n\n      function downloadPng() {\n        const url = exportImageDataUrl();\n        const link = document.createElement('a');\n\n        link.href = url;\n        link.download = `custom-${state.method}-mockup.png`;\n        link.click();\n\n        return url;\n      }\n\n      function productionNote() {\n        return [\n          'Custom design order',\n          'Product: ' + state.productTitle,\n          'Variant ID: ' + state.variantId,\n          'Method: ' + (state.method === 'dtf' ? 'DTF Print' : 'Embroidery'),\n          'Quantity: ' + (els.qty.value || 1),\n          'Contact: ' + (els.contact.value || ''),\n          'Design note: ' + (els.note.value || ''),\n          'Layers: ' + state.layers.length\n        ].join('\\n');\n      }\n\n      function savedMessageData() {\n        return {\n          savedAt: new Date().toISOString(),\n          productTitle: state.productTitle,\n          variantId: state.variantId,\n          method: state.method === 'dtf' ? 'DTF Print' : 'Embroidery',\n          quantity: els.qty.value || '1',\n          contact: els.contact.value || '',\n          designNote: els.note.value || '',\n          layerCount: state.layers.length,\n          message: 'Product saved. Any questions? Ask us.'\n        };\n      }\n\n      function showToast(message) {\n        els.toast.textContent = message;\n        els.toast.classList.add('show');\n\n        window.setTimeout(() =\u003e {\n          els.toast.classList.remove('show');\n        }, 3500);\n      }\n\n      function showDeviceNotification(message) {\n        setStatus(message);\n        showToast(message);\n\n        if (!('Notification' in window)) return;\n\n        if (Notification.permission === 'granted') {\n          new Notification('USA STAR FASHION', { body: message });\n          return;\n        }\n\n        if (Notification.permission !== 'denied') {\n          Notification.requestPermission().then((permission) =\u003e {\n            if (permission === 'granted') {\n              new Notification('USA STAR FASHION', { body: message });\n            }\n          });\n        }\n      }\n\n      function saveMessage() {\n        state.variantId = findVariantId() || state.variantId;\n\n        const data = savedMessageData();\n        const latestKey = 'usa-star-fashion-saved-design';\n        const historyKey = 'usa-star-fashion-saved-design-' + Date.now();\n\n        try {\n          localStorage.setItem(latestKey, JSON.stringify(data));\n          localStorage.setItem(historyKey, JSON.stringify(data));\n          showDeviceNotification('Product saved. Any questions? Ask us.');\n        } catch (error) {\n          setStatus('Could not save message. Please try again.');\n          showToast('Could not save message. Please try again.');\n        }\n      }\n\n      async function addToCart() {\n        state.variantId = findVariantId() || state.variantId;\n\n        if (!state.variantId) {\n          setStatus('Variant ID not found. This must be on a product page with Add to Cart form.');\n          return;\n        }\n\n        try {\n          const rootPath = window.Shopify \u0026\u0026 Shopify.routes ? Shopify.routes.root : '\/';\n          const response = await fetch(rootPath + 'cart\/add.js', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application\/json', Accept: 'application\/json' },\n            body: JSON.stringify({\n              items: [{\n                id: Number(state.variantId),\n                quantity: Math.max(1, Number(els.qty.value || 1)),\n                properties: {\n                  'Custom design': 'Yes',\n                  'Print method': state.method === 'dtf' ? 'DTF Print' : 'Embroidery',\n                  'Contact': els.contact.value || '',\n                  'Design note': els.note.value || '',\n                  '_Saved design note': productionNote()\n                }\n              }]\n            })\n          });\n\n          const result = await response.json();\n\n          if (!response.ok) {\n            throw new Error(result.description || result.message || 'Could not add to cart.');\n          }\n\n          saveMessage();\n          setStatus('Custom product added to cart. Product saved. Any questions? Ask us.');\n          window.location.href = rootPath + 'cart';\n        } catch (error) {\n          setStatus(error.message || 'Could not add to cart.');\n        }\n      }\n\n      els.methodButtons.forEach((button) =\u003e {\n        button.addEventListener('click', () =\u003e {\n          state.method = button.dataset.method;\n          els.methodButtons.forEach((btn) =\u003e btn.classList.toggle('active', btn === button));\n          render();\n        });\n      });\n\n      els.addText.addEventListener('click', addText);\n      els.uploadBtn.addEventListener('click', () =\u003e els.upload.click());\n\n      els.upload.addEventListener('change', () =\u003e {\n        const file = els.upload.files[0];\n\n        if (!file) return;\n\n        const reader = new FileReader();\n\n        reader.onload = () =\u003e addImage(String(reader.result));\n        reader.readAsDataURL(file);\n\n        els.upload.value = '';\n      });\n\n      els.removeBg.addEventListener('click', removeWhiteBackground);\n\n      els.center.addEventListener('click', () =\u003e {\n        if (!state.selected) return;\n\n        const area = printArea();\n\n        state.selected.x = area.x + area.w \/ 2;\n        state.selected.y = area.y + area.h \/ 2;\n\n        render();\n      });\n\n      els.fit.addEventListener('click', () =\u003e {\n        if (!state.selected) return;\n\n        const area = printArea();\n        const scale = Math.min(area.w * 0.9 \/ state.selected.w, area.h * 0.9 \/ state.selected.h);\n\n        state.selected.w *= scale;\n        state.selected.h *= scale;\n\n        if (state.selected.size) state.selected.size *= scale;\n\n        state.selected.x = area.x + area.w \/ 2;\n        state.selected.y = area.y + area.h \/ 2;\n\n        render();\n      });\n\n      els.delete.addEventListener('click', () =\u003e {\n        if (!state.selected) return;\n\n        state.layers = state.layers.filter((layer) =\u003e layer !== state.selected);\n        state.selected = null;\n\n        render();\n      });\n\n      els.download.addEventListener('click', downloadPng);\n      els.addCart.addEventListener('click', addToCart);\n      els.saveMessage.addEventListener('click', saveMessage);\n\n      canvas.addEventListener('pointerdown', (event) =\u003e {\n        const point = canvasPoint(event);\n        const layer = hit(point);\n\n        if (!layer) {\n          state.selected = null;\n          render();\n          return;\n        }\n\n        state.selected = layer;\n        state.drag = {\n          x: point.x,\n          y: point.y,\n          layerX: layer.x,\n          layerY: layer.y\n        };\n\n        canvas.classList.add('drag');\n        canvas.setPointerCapture(event.pointerId);\n        render();\n      });\n\n      canvas.addEventListener('pointermove', (event) =\u003e {\n        if (!state.drag || !state.selected) return;\n\n        const point = canvasPoint(event);\n\n        state.selected.x = state.drag.layerX + point.x - state.drag.x;\n        state.selected.y = state.drag.layerY + point.y - state.drag.y;\n\n        render();\n      });\n\n      canvas.addEventListener('pointerup', (event) =\u003e {\n        state.drag = null;\n        canvas.classList.remove('drag');\n\n        try {\n          canvas.releasePointerCapture(event.pointerId);\n        } catch (e) {}\n      });\n\n      document.addEventListener('change', (event) =\u003e {\n        if (event.target.matches('[name=\"id\"], variant-selects select, variant-radios input, select[name=\"id\"]')) {\n          setTimeout(() =\u003e {\n            state.variantId = findVariantId();\n            state.productImageUrl = findProductImage();\n            loadProductImage();\n          }, 200);\n        }\n      });\n\n      loadProductImage();\n      render();\n    })();\n  \u003c\/script\u003e\n\u003c\/div\u003e\n","brand":"USASTARFASHION","offers":[{"title":"Black \/ Custom DTF Printing","offer_id":58430117577035,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Black \/ Custom Embroidery","offer_id":58459501691211,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Green \/ Custom DTF Printing","offer_id":58430117609803,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Green \/ Custom Embroidery","offer_id":58459501723979,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Gray \/ Custom DTF Printing","offer_id":58430117642571,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Gray \/ Custom Embroidery","offer_id":58459501756747,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Navy \/ Custom DTF Printing","offer_id":58430117675339,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Navy \/ Custom Embroidery","offer_id":58459501789515,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"White \/ Custom DTF Printing","offer_id":58430117708107,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"White \/ Custom Embroidery","offer_id":58459501822283,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Blue \/ Custom DTF Printing","offer_id":58430117740875,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Blue \/ Custom Embroidery","offer_id":58459501855051,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0818\/3575\/3803\/files\/ChatGPTImageSep9_2026_01_53_28PM.png?v=1788973278","url":"https:\/\/www.usastarfashion.com\/products\/personalised-embroidered-baseball-cap-custom-name-text-organic-cotton-hat","provider":"USASTARFASHION","version":"1.0","type":"link"}