{"product_id":"personalised-embroidered-baseball-cap-custom-name-text-organic-cotton-hat-copy-copy","title":"Personalised Embroidered Baseball Cap Custom Name Text Organic Cotton Hat (Copy) (Copy)","description":"\u003c!-- File: Shopify Custom Liquid section OR Product Description HTML\/code view --\u003e\n\n\u003cdiv id=\"usaf-pro-studio\"\u003e\n  \u003cstyle\u003e\n    @import url(\"https:\/\/fonts.googleapis.com\/css2?family=Anton\u0026family=Bebas+Neue\u0026family=Black+Ops+One\u0026family=Bungee\u0026family=Lobster\u0026family=Oswald:wght@700\u0026family=Pacifico\u0026family=Permanent+Marker\u0026display=swap\");\n\n    #usaf-pro-studio{\n      --yellow:#ffd400;\n      --line:#2a3542;\n      --text:#f7f8fb;\n      --muted:#919eab;\n      --green:#22d99a;\n      --red:#ff707c;\n      max-width:1600px;\n      margin:20px auto;\n      overflow:hidden;\n      border:1px solid #202a35;\n      border-radius:27px;\n      background:radial-gradient(circle at 10% 0%,rgba(255,212,0,.07),transparent 26%),linear-gradient(180deg,#05070b,#090d13);\n      color:var(--text);\n      font-family:Arial,Helvetica,sans-serif;\n      box-shadow:0 30px 80px rgba(0,0,0,.32);\n    }\n\n    #usaf-pro-studio *{box-sizing:border-box}\n    #usaf-pro-studio button,\n    #usaf-pro-studio input,\n    #usaf-pro-studio select,\n    #usaf-pro-studio textarea{font:inherit}\n\n    .usaf-hidden{display:none!important}\n\n    .usaf-appbar{\n      min-height:80px;\n      padding:14px 18px;\n      display:flex;\n      justify-content:space-between;\n      align-items:center;\n      gap:12px;\n      flex-wrap:wrap;\n      border-bottom:1px solid var(--line);\n      background:linear-gradient(180deg,#111821,#090d13);\n    }\n\n    .usaf-brand{display:flex;align-items:center;gap:12px}\n\n    .usaf-logo{\n      width:53px;\n      height:53px;\n      display:grid;\n      place-items:center;\n      border-radius:16px;\n      background:linear-gradient(145deg,#ffe95f,#ffd400);\n      color:#111;\n      font-size:25px;\n      font-weight:1000;\n    }\n\n    .usaf-brand strong{display:block;font-size:20px}\n\n    .usaf-brand span{\n      display:block;\n      margin-top:3px;\n      color:var(--muted);\n      font-size:9px;\n      font-weight:900;\n      letter-spacing:.14em;\n      text-transform:uppercase;\n    }\n\n    .usaf-mode{\n      padding:7px 11px;\n      border:1px solid rgba(255,212,0,.22);\n      border-radius:999px;\n      background:rgba(255,212,0,.06);\n      color:var(--yellow);\n      font-size:9px;\n      font-weight:900;\n    }\n\n    .usaf-nav{\n      display:flex;\n      gap:7px;\n      flex-wrap:wrap;\n      padding:10px 16px;\n      border-bottom:1px solid var(--line);\n      background:#080c12;\n    }\n\n    .usaf-tab,\n    .usaf-btn{\n      min-height:43px;\n      padding:9px 13px;\n      border:1px solid var(--line);\n      border-radius:12px;\n      background:linear-gradient(180deg,#18212c,#111821);\n      color:#fff;\n      font-size:11px;\n      font-weight:900;\n      cursor:pointer;\n      text-decoration:none;\n    }\n\n    .usaf-tab{\n      border-color:transparent;\n      background:transparent;\n      color:#aeb9c4;\n    }\n\n    .usaf-tab.active,\n    .usaf-btn.primary{\n      border-color:#ac9000;\n      background:linear-gradient(180deg,#ffe452,#ffd400);\n      color:#111;\n    }\n\n    .usaf-btn.green{border-color:#23644e;background:#15382e;color:#96f0cf}\n    .usaf-btn.danger{border-color:#632a33;background:#29171c;color:#ffb6bd}\n    .usaf-btn:disabled{opacity:.4;cursor:not-allowed}\n\n    .usaf-content{padding:15px}\n    .usaf-section{display:none}\n    .usaf-section.active{display:block}\n\n    .usaf-panel{\n      border:1px solid var(--line);\n      border-radius:19px;\n      background:linear-gradient(180deg,#111923,#0b1118);\n      overflow:hidden;\n    }\n\n    .usaf-panel-head{padding:14px 15px 11px;border-bottom:1px solid var(--line)}\n    .usaf-panel-body{padding:14px}\n    .usaf-title{font-size:17px;font-weight:950}\n    .usaf-sub{margin-top:4px;color:var(--muted);font-size:10px;line-height:1.55}\n\n    .usaf-label{\n      display:block;\n      margin:10px 0 6px;\n      color:#d7dde3;\n      font-size:9px;\n      font-weight:900;\n      letter-spacing:.06em;\n      text-transform:uppercase;\n    }\n\n    .usaf-input,\n    .usaf-select,\n    .usaf-textarea{\n      width:100%;\n      min-height:43px;\n      padding:9px 10px;\n      border:1px solid var(--line);\n      border-radius:11px;\n      background:#080d13;\n      color:#fff;\n      outline:none;\n    }\n\n    .usaf-textarea{min-height:82px;resize:vertical}\n    .usaf-range{width:100%;accent-color:var(--yellow)}\n    .usaf-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n    .usaf-two{display:grid;grid-template-columns:1fr 1fr;gap:8px}\n    .usaf-three{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}\n\n    .usaf-note{\n      padding:10px;\n      border:1px solid rgba(255,212,0,.23);\n      border-radius:11px;\n      background:rgba(255,212,0,.055);\n      color:#ffe778;\n      font-size:10px;\n      line-height:1.5;\n    }\n\n    .usaf-note.good{border-color:rgba(34,217,154,.28);background:rgba(34,217,154,.06);color:#90efcd}\n    .usaf-note.error{border-color:rgba(255,112,124,.3);background:rgba(255,112,124,.07);color:#ffb5bd}\n\n    .usaf-chipbar{display:flex;flex-wrap:wrap;gap:5px}\n\n    .usaf-chip{\n      padding:7px 9px;\n      border:1px solid var(--line);\n      border-radius:999px;\n      background:#121a24;\n      color:#d9dfe5;\n      font-size:9px;\n      font-weight:900;\n      cursor:pointer;\n    }\n\n    .usaf-chip.active{\n      border-color:#927900;\n      background:rgba(255,212,0,.07);\n      color:var(--yellow);\n    }\n\n    .usaf-library-layout{display:grid;grid-template-columns:300px 1fr;gap:14px}\n    .usaf-gallery{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:11px}\n\n    .usaf-card{\n      position:relative;\n      overflow:hidden;\n      border:1px solid var(--line);\n      border-radius:15px;\n      background:#090e14;\n    }\n\n    .usaf-card.selected{border:2px solid var(--yellow)}\n\n    .usaf-card.selected:after{\n      content:\"SELECTED\";\n      position:absolute;\n      top:7px;\n      right:7px;\n      z-index:4;\n      padding:5px 7px;\n      border-radius:999px;\n      background:var(--yellow);\n      color:#111;\n      font-size:8px;\n      font-weight:900;\n    }\n\n    .usaf-thumb{\n      position:relative;\n      aspect-ratio:1\/1;\n      overflow:hidden;\n      background:#05080b;\n    }\n\n    .usaf-thumb img{\n      width:100%;\n      height:100%;\n      display:block;\n      object-fit:cover;\n      user-select:none;\n      -webkit-user-drag:none;\n    }\n\n    .usaf-cat{\n      position:absolute;\n      left:7px;\n      top:7px;\n      padding:5px 7px;\n      border-radius:999px;\n      background:rgba(0,0,0,.72);\n      color:#fff;\n      font-size:8px;\n      font-weight:900;\n    }\n\n    .usaf-card-body{padding:9px}\n\n    .usaf-card-name{\n      display:block;\n      overflow:hidden;\n      white-space:nowrap;\n      text-overflow:ellipsis;\n      font-size:10px;\n      font-weight:900;\n    }\n\n    .usaf-card-meta{\n      min-height:22px;\n      margin-top:4px;\n      color:var(--muted);\n      font-size:8px;\n    }\n\n    .usaf-card-actions{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:5px;\n      margin-top:7px;\n    }\n\n    .usaf-card-actions .usaf-btn{\n      min-height:34px;\n      padding:4px;\n      font-size:8px;\n    }\n\n    .usaf-pages{\n      display:flex;\n      justify-content:center;\n      gap:5px;\n      flex-wrap:wrap;\n      margin-top:13px;\n    }\n\n    .usaf-page{\n      width:36px;\n      height:36px;\n      border:1px solid var(--line);\n      border-radius:9px;\n      background:#121a24;\n      color:#fff;\n      cursor:pointer;\n    }\n\n    .usaf-page.active{background:var(--yellow);color:#111;font-weight:900}\n\n    .usaf-studio-layout{display:grid;grid-template-columns:minmax(0,1fr) 360px;gap:14px}\n    .usaf-stack{display:grid;gap:11px;align-content:start}\n\n    .usaf-stage,\n    .usaf-half-preview{\n      min-height:680px;\n      display:grid;\n      place-items:center;\n      overflow:auto;\n      padding:13px;\n      border-radius:16px;\n      background:\n        linear-gradient(45deg,#ddd 25%,transparent 25%),\n        linear-gradient(-45deg,#ddd 25%,transparent 25%),\n        linear-gradient(45deg,transparent 75%,#ddd 75%),\n        linear-gradient(-45deg,transparent 75%,#ddd 75%),\n        #f5f6f7;\n      background-size:24px 24px;\n      background-position:0 0,0 12px,12px -12px,-12px 0;\n    }\n\n    #usafMockCanvas,\n    #usafHalfCanvas{\n      max-width:100%;\n      height:auto;\n      display:block;\n      touch-action:none;\n    }\n\n    .usaf-product{\n      overflow:hidden;\n      border:1px solid var(--line);\n      border-radius:13px;\n      background:#080d13;\n    }\n\n    .usaf-product-top{\n      display:grid;\n      grid-template-columns:70px 1fr;\n      gap:9px;\n      padding:9px;\n      align-items:center;\n    }\n\n    .usaf-product-img{\n      width:70px;\n      height:70px;\n      object-fit:contain;\n      border-radius:9px;\n      background:#fff;\n    }\n\n    .usaf-price{\n      margin-top:4px;\n      color:var(--yellow);\n      font-size:18px;\n      font-weight:950;\n    }\n\n    .usaf-layer,\n    .usaf-gang-piece{\n      padding:8px;\n      margin-bottom:6px;\n      border:1px solid var(--line);\n      border-radius:10px;\n      background:#080d13;\n      font-size:9px;\n      cursor:pointer;\n    }\n\n    .usaf-layer.active{border-color:#927900;color:var(--yellow)}\n    .usaf-half-layout{display:grid;grid-template-columns:340px 1fr;gap:14px}\n    .usaf-gang-layout{display:grid;grid-template-columns:300px minmax(0,1fr) 340px;gap:13px}\n\n    .usaf-gang-workspace{\n      min-height:740px;\n      width:100%;\n      max-width:100%;\n      overflow:auto;\n      padding:13px;\n      border-radius:16px;\n      background:#05080c;\n    }\n\n    .usaf-gang-inner{width:max-content;margin:0 auto}\n\n    .usaf-width-badge{\n      width:max-content;\n      margin:0 auto 7px;\n      padding:5px 9px;\n      border-radius:999px;\n      background:var(--yellow);\n      color:#111;\n      font-size:9px;\n      font-weight:950;\n    }\n\n    #usafGangCanvas{\n      display:block;\n      max-width:none;\n      height:auto;\n      border:1px solid #46525e;\n      box-shadow:0 22px 55px rgba(0,0,0,.45);\n      touch-action:none;\n    }\n\n    .usaf-price-card{\n      padding:13px;\n      border:1px solid rgba(255,212,0,.23);\n      border-radius:13px;\n      background:rgba(255,212,0,.045);\n    }\n\n    .usaf-price-grid{\n      display:grid;\n      grid-template-columns:1fr auto;\n      gap:7px;\n      font-size:10px;\n    }\n\n    .usaf-price-grid span{color:var(--muted)}\n    .usaf-quality{margin-top:8px;padding:8px;border-radius:9px;font-size:9px;font-weight:900}\n    .usaf-quality.good{background:rgba(34,217,154,.08);color:#91efcd}\n    .usaf-quality.warn{background:rgba(255,212,0,.08);color:#ffe676}\n    .usaf-quality.bad{background:rgba(255,112,124,.08);color:#ffb3bc}\n\n    @media(max-width:1200px){\n      .usaf-library-layout,\n      .usaf-studio-layout,\n      .usaf-half-layout{grid-template-columns:1fr}\n      .usaf-gang-layout{grid-template-columns:280px 1fr}\n      .usaf-gang-right{grid-column:1\/-1;display:grid;grid-template-columns:1fr 1fr;gap:11px}\n      .usaf-gallery{grid-template-columns:repeat(3,minmax(0,1fr))}\n    }\n\n    @media(max-width:720px){\n      #usaf-pro-studio{margin:0;border-radius:0;border-left:0;border-right:0}\n      .usaf-appbar{min-height:auto;padding:11px}\n      .usaf-logo{width:44px;height:44px;border-radius:13px;font-size:21px}\n      .usaf-brand strong{font-size:15px}\n      .usaf-brand span{font-size:7px}\n      .usaf-content{padding:8px}\n      .usaf-nav{padding:8px;gap:5px}\n\n      .usaf-tab{\n        flex:1 1 46%;\n        min-height:39px;\n        padding:7px;\n        font-size:9px;\n      }\n\n      .usaf-library-layout,\n      .usaf-studio-layout,\n      .usaf-half-layout,\n      .usaf-gang-layout{\n        display:block!important;\n        width:100%!important;\n      }\n\n      .usaf-stack,\n      .usaf-gang-right{\n        width:100%!important;\n        display:block!important;\n      }\n\n      .usaf-stack .usaf-panel,\n      .usaf-gang-right .usaf-panel{margin-bottom:9px}\n\n      .usaf-gallery{\n        grid-template-columns:repeat(2,minmax(0,1fr))!important;\n        gap:6px!important;\n      }\n\n      .usaf-stage,\n      .usaf-half-preview{\n        min-height:410px;\n        padding:7px;\n      }\n\n      .usaf-gang-workspace{\n        width:100%!important;\n        max-width:100%!important;\n        min-height:430px!important;\n        padding:4px!important;\n      }\n\n      .usaf-gang-inner{\n        width:100%!important;\n        max-width:100%!important;\n        min-width:0!important;\n        margin:0 auto!important;\n      }\n\n      #usafGangCanvas{\n        display:block!important;\n        width:100%!important;\n        max-width:100%!important;\n        height:auto!important;\n        transform:none!important;\n        margin:0 auto!important;\n      }\n\n      .usaf-two,\n      .usaf-three{grid-template-columns:1fr}\n    }\n  \u003c\/style\u003e\n\n  \u003cscript type=\"application\/json\" id=\"usafCustomerJson\"\u003e\n    {\n      \"loggedIn\": {% if customer %}true{% else %}false{% endif %},\n      \"id\": {% if customer %}{{ customer.id | json }}{% else %}\"\"{% endif %},\n      \"name\": {% if customer %}{{ customer.name | json }}{% else %}\"\"{% endif %},\n      \"email\": {% if customer %}{{ customer.email | json }}{% else %}\"\"{% endif %},\n      \"phone\": {% if customer and customer.phone %}{{ customer.phone | json }}{% else %}\"\"{% endif %}\n    }\n  \u003c\/script\u003e\n\n  \u003cheader class=\"usaf-appbar\"\u003e\n    \u003cdiv class=\"usaf-brand\"\u003e\n      \u003cdiv class=\"usaf-logo\"\u003eU\u003c\/div\u003e\n      \u003cdiv\u003e\n        \u003cstrong\u003eUSASTARFASHION PRO Studio\u003c\/strong\u003e\n        \u003cspan\u003eLibrary · Personalise · Halftone · Gang Sheet\u003c\/span\u003e\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n\n    \u003cdiv class=\"usaf-row\"\u003e\n      \u003cspan id=\"usafMode\" class=\"usaf-mode\"\u003eReady\u003c\/span\u003e\n      \u003cbutton id=\"usafRefreshTop\" class=\"usaf-btn\" type=\"button\"\u003e↻ Refresh Library\u003c\/button\u003e\n    \u003c\/div\u003e\n  \u003c\/header\u003e\n\n  \u003cnav class=\"usaf-nav\"\u003e\n    \u003cbutton class=\"usaf-tab active\" data-tab=\"library\" type=\"button\"\u003e▦ Library\u003c\/button\u003e\n    \u003cbutton id=\"usafPersonaliseTab\" class=\"usaf-tab\" data-tab=\"studio\" type=\"button\"\u003e✦ Personalise\u003c\/button\u003e\n    \u003cbutton class=\"usaf-tab\" data-tab=\"halftone\" type=\"button\"\u003e◉ Halftone\u003c\/button\u003e\n    \u003cbutton class=\"usaf-tab\" data-tab=\"gang\" type=\"button\"\u003e▤ Gang Sheet\u003c\/button\u003e\n  \u003c\/nav\u003e\n\n  \u003cdiv class=\"usaf-content\"\u003e\n    \u003csection id=\"usafSection_library\" class=\"usaf-section active\"\u003e\n      \u003cdiv class=\"usaf-library-layout\"\u003e\n        \u003caside class=\"usaf-panel\"\u003e\n          \u003cdiv class=\"usaf-panel-head\"\u003e\n            \u003cdiv class=\"usaf-title\"\u003eArtwork Library\u003c\/div\u003e\n            \u003cdiv class=\"usaf-sub\"\u003eGoogle Drive artwork or local upload.\u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel-body\"\u003e\n            \u003cinput id=\"usafSearch\" class=\"usaf-input\" placeholder=\"Search artwork...\"\u003e\n\n            \u003cdiv class=\"usaf-two\" style=\"margin-top:8px\"\u003e\n              \u003cbutton id=\"usafRefreshLibrary\" class=\"usaf-btn primary\" type=\"button\"\u003eRefresh\u003c\/button\u003e\n              \u003cbutton id=\"usafLibraryUploadBtn\" class=\"usaf-btn\" type=\"button\"\u003eUpload\u003c\/button\u003e\n            \u003c\/div\u003e\n\n            \u003cinput id=\"usafLibraryUpload\" class=\"usaf-hidden\" type=\"file\" multiple accept=\"image\/png,image\/jpeg,image\/webp\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eCategories\u003c\/label\u003e\n            \u003cdiv id=\"usafFilters\" class=\"usaf-chipbar\"\u003e\u003c\/div\u003e\n\n            \u003cdiv id=\"usafLibraryStatus\" class=\"usaf-note\" style=\"margin-top:9px\"\u003eLoading Google Drive…\u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/aside\u003e\n\n        \u003cmain class=\"usaf-panel\"\u003e\n          \u003cdiv class=\"usaf-panel-head\"\u003e\n            \u003cdiv class=\"usaf-title\"\u003eDesign Gallery\u003c\/div\u003e\n            \u003cdiv id=\"usafGalleryInfo\" class=\"usaf-sub\"\u003e\u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel-body\"\u003e\n            \u003cdiv id=\"usafGallery\" class=\"usaf-gallery\"\u003e\u003c\/div\u003e\n            \u003cdiv id=\"usafPages\" class=\"usaf-pages\"\u003e\u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/main\u003e\n      \u003c\/div\u003e\n    \u003c\/section\u003e\n\n    \u003csection id=\"usafSection_studio\" class=\"usaf-section\"\u003e\n      \u003cdiv class=\"usaf-studio-layout\"\u003e\n        \u003cmain class=\"usaf-panel\"\u003e\n          \u003cdiv class=\"usaf-panel-head\"\u003e\n            \u003cdiv class=\"usaf-title\"\u003ePersonalisation Studio\u003c\/div\u003e\n            \u003cdiv class=\"usaf-sub\"\u003eUses Shopify product image as mockup.\u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel-body\"\u003e\n            \u003cdiv id=\"usafAreas\" class=\"usaf-chipbar\" style=\"margin-bottom:9px\"\u003e\u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-stage\"\u003e\n              \u003ccanvas id=\"usafMockCanvas\" width=\"860\" height=\"860\"\u003e\u003c\/canvas\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/main\u003e\n\n        \u003caside class=\"usaf-stack\"\u003e\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eShopify Product\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv id=\"usafProductStatus\" class=\"usaf-note\"\u003eDetecting product…\u003c\/div\u003e\n\n              \u003cdiv id=\"usafProductBox\" class=\"usaf-product usaf-hidden\" style=\"margin-top:8px\"\u003e\n                \u003cdiv class=\"usaf-product-top\"\u003e\n                  \u003cimg id=\"usafProductImage\" class=\"usaf-product-img\" alt=\"\"\u003e\n                  \u003cdiv\u003e\n                    \u003cstrong id=\"usafProductTitle\"\u003e\u003c\/strong\u003e\n                    \u003cdiv id=\"usafProductPrice\" class=\"usaf-price\"\u003e\u003c\/div\u003e\n                    \u003cdiv id=\"usafProductStock\" style=\"color:var(--muted);font-size:9px\"\u003e\u003c\/div\u003e\n                  \u003c\/div\u003e\n                \u003c\/div\u003e\n\n                \u003cdiv id=\"usafVariantOptions\" style=\"padding:0 9px 9px\"\u003e\u003c\/div\u003e\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eProduct \/ Method\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv class=\"usaf-two\"\u003e\n                \u003cbutton class=\"usaf-btn primary\" data-product=\"tshirt\" type=\"button\"\u003eT-Shirt\u003c\/button\u003e\n                \u003cbutton class=\"usaf-btn\" data-product=\"cap\" type=\"button\"\u003eCap\u003c\/button\u003e\n              \u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003ePrint Method\u003c\/label\u003e\n              \u003cdiv class=\"usaf-two\"\u003e\n                \u003cbutton class=\"usaf-btn primary\" data-method=\"dtf\" type=\"button\"\u003eDTF\u003c\/button\u003e\n                \u003cbutton class=\"usaf-btn\" data-method=\"embroidery\" type=\"button\"\u003eEmbroidery\u003c\/button\u003e\n              \u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eMockup Colour\u003c\/label\u003e\n              \u003cinput id=\"usafMockColour\" class=\"usaf-input\" type=\"color\" value=\"#f2f2f2\" style=\"padding:4px\"\u003e\n\n              \u003clabel style=\"display:flex;gap:8px;margin-top:9px;font-size:10px\"\u003e\n                \u003cinput id=\"usafUseVariant\" type=\"checkbox\" checked\u003e\n                Use Shopify variant image\n              \u003c\/label\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eArtwork\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv id=\"usafSelectedDesign\" class=\"usaf-note\"\u003eNo Library design selected.\u003c\/div\u003e\n\n              \u003cdiv class=\"usaf-two\" style=\"margin-top:7px\"\u003e\n                \u003cbutton id=\"usafAddLibraryMock\" class=\"usaf-btn primary\" type=\"button\"\u003eAdd Selected\u003c\/button\u003e\n                \u003cbutton id=\"usafLibraryHalf\" class=\"usaf-btn\" type=\"button\"\u003eHalftone\u003c\/button\u003e\n              \u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eCustomer Artwork\u003c\/label\u003e\n              \u003cinput id=\"usafCustomerUpload\" class=\"usaf-input\" type=\"file\" accept=\"image\/png,image\/jpeg,image\/webp\"\u003e\n\n              \u003cdiv class=\"usaf-two\" style=\"margin-top:7px\"\u003e\n                \u003cbutton id=\"usafAddCustomer\" class=\"usaf-btn\" type=\"button\"\u003eAdd\u003c\/button\u003e\n                \u003cbutton id=\"usafCustomerHalf\" class=\"usaf-btn\" type=\"button\"\u003eHalftone\u003c\/button\u003e\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eText\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cinput id=\"usafText\" class=\"usaf-input\" placeholder=\"Your text\"\u003e\n\n              \u003cdiv class=\"usaf-two\" style=\"margin-top:7px\"\u003e\n                \u003cselect id=\"usafFont\" class=\"usaf-select\"\u003e\n                  \u003coption value=\"Arial\"\u003eArial\u003c\/option\u003e\n                  \u003coption value=\"Impact\"\u003eImpact\u003c\/option\u003e\n                  \u003coption value=\"'Anton', Impact\"\u003eAnton\u003c\/option\u003e\n                  \u003coption value=\"'Bebas Neue', Impact\"\u003eBebas Neue\u003c\/option\u003e\n                  \u003coption value=\"'Black Ops One', Impact\"\u003eBlack Ops One\u003c\/option\u003e\n                  \u003coption value=\"'Bungee', Impact\"\u003eBungee\u003c\/option\u003e\n                  \u003coption value=\"'Oswald', Arial\"\u003eOswald\u003c\/option\u003e\n                  \u003coption value=\"'Permanent Marker', cursive\"\u003ePermanent Marker\u003c\/option\u003e\n                  \u003coption value=\"'Pacifico', cursive\"\u003ePacifico\u003c\/option\u003e\n                  \u003coption value=\"'Lobster', cursive\"\u003eLobster\u003c\/option\u003e\n                  \u003coption value=\"Georgia\"\u003eGeorgia\u003c\/option\u003e\n                  \u003coption value=\"Verdana\"\u003eVerdana\u003c\/option\u003e\n                  \u003coption value=\"'Courier New'\"\u003eCourier New\u003c\/option\u003e\n                \u003c\/select\u003e\n\n                \u003cinput id=\"usafTextSize\" class=\"usaf-input\" type=\"number\" value=\"56\" min=\"8\" max=\"300\"\u003e\n              \u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eText Style\u003c\/label\u003e\n              \u003cselect id=\"usafTextStyle\" class=\"usaf-select\"\u003e\n                \u003coption value=\"normal\"\u003eNormal\u003c\/option\u003e\n                \u003coption value=\"sticker\"\u003eDTF Sticker\u003c\/option\u003e\n                \u003coption value=\"shadow\"\u003eShadow\u003c\/option\u003e\n                \u003coption value=\"glow\"\u003eGlow\u003c\/option\u003e\n                \u003coption value=\"retro\"\u003eRetro\u003c\/option\u003e\n                \u003coption value=\"stitch\"\u003eEmbroidery Stitch\u003c\/option\u003e\n              \u003c\/select\u003e\n\n              \u003cdiv class=\"usaf-two\" style=\"margin-top:7px\"\u003e\n                \u003cinput id=\"usafTextColour\" class=\"usaf-input\" type=\"color\" value=\"#000000\" style=\"padding:4px\"\u003e\n                \u003cinput id=\"usafOutlineColour\" class=\"usaf-input\" type=\"color\" value=\"#ffffff\" style=\"padding:4px\"\u003e\n              \u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eOutline\u003c\/label\u003e\n              \u003cinput id=\"usafOutline\" class=\"usaf-range\" type=\"range\" min=\"0\" max=\"24\" value=\"0\"\u003e\n\n              \u003cbutton id=\"usafAddText\" class=\"usaf-btn primary\" type=\"button\" style=\"width:100%;margin-top:7px\"\u003e\n                + Add Text\n              \u003c\/button\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eLayers\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv id=\"usafLayers\"\u003e\u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eSize\u003c\/label\u003e\n              \u003cinput id=\"usafLayerSize\" class=\"usaf-range\" type=\"range\" min=\"20\" max=\"700\" value=\"220\"\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eRotation\u003c\/label\u003e\n              \u003cinput id=\"usafLayerRotation\" class=\"usaf-range\" type=\"range\" min=\"-180\" max=\"180\" value=\"0\"\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eOpacity\u003c\/label\u003e\n              \u003cinput id=\"usafLayerOpacity\" class=\"usaf-range\" type=\"range\" min=\"0.1\" max=\"1\" step=\"0.05\" value=\"1\"\u003e\n\n              \u003cdiv class=\"usaf-three\"\u003e\n                \u003cbutton id=\"usafCenterLayer\" class=\"usaf-btn\" type=\"button\"\u003eCenter\u003c\/button\u003e\n                \u003cbutton id=\"usafDuplicateLayer\" class=\"usaf-btn\" type=\"button\"\u003eCopy\u003c\/button\u003e\n                \u003cbutton id=\"usafDeleteLayer\" class=\"usaf-btn danger\" type=\"button\"\u003eDelete\u003c\/button\u003e\n              \u003c\/div\u003e\n\n              \u003cbutton id=\"usafMockToGang\" class=\"usaf-btn primary\" type=\"button\" style=\"width:100%;margin-top:7px\"\u003e\n                + Add Print Area to Gang\n              \u003c\/button\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eProduct Checkout\u003c\/div\u003e\n              \u003cdiv class=\"usaf-sub\"\u003eFor personalised products. Gang Sheet is download only.\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003clabel class=\"usaf-label\"\u003eQuantity\u003c\/label\u003e\n              \u003cinput id=\"usafMockQty\" class=\"usaf-input\" type=\"number\" min=\"1\" value=\"1\"\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eCustomer Note\u003c\/label\u003e\n              \u003ctextarea id=\"usafCustomerNote\" class=\"usaf-textarea\" placeholder=\"Example: Front center DTF, white logo.\"\u003e\u003c\/textarea\u003e\n\n              \u003cdiv class=\"usaf-two\" style=\"margin-top:9px\"\u003e\n                \u003cbutton id=\"usafMockAddCart\" class=\"usaf-btn green\" type=\"button\"\u003eAdd to Cart\u003c\/button\u003e\n                \u003cbutton id=\"usafMockBuyNow\" class=\"usaf-btn primary\" type=\"button\"\u003eBuy Now\u003c\/button\u003e\n              \u003c\/div\u003e\n\n              \u003cdiv id=\"usafMockCartStatus\" class=\"usaf-note\" style=\"margin-top:8px\"\u003e\n                Custom design will be saved with this product.\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/aside\u003e\n      \u003c\/div\u003e\n    \u003c\/section\u003e\n\n    \u003csection id=\"usafSection_halftone\" class=\"usaf-section\"\u003e\n      \u003cdiv class=\"usaf-half-layout\"\u003e\n        \u003caside class=\"usaf-panel\"\u003e\n          \u003cdiv class=\"usaf-panel-head\"\u003e\n            \u003cdiv class=\"usaf-title\"\u003eHalftone Studio\u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel-body\"\u003e\n            \u003cinput id=\"usafHalfUpload\" class=\"usaf-input\" type=\"file\" accept=\"image\/png,image\/jpeg,image\/webp\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eOutput Width — inches\u003c\/label\u003e\n            \u003cinput id=\"usafHalfWidth\" class=\"usaf-input\" type=\"number\" min=\"1\" max=\"22\" step=\".1\" value=\"11\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eDot Shape\u003c\/label\u003e\n            \u003cselect id=\"usafHalfShape\" class=\"usaf-select\"\u003e\n              \u003coption value=\"circle\"\u003eRound\u003c\/option\u003e\n              \u003coption value=\"square\"\u003eSquare\u003c\/option\u003e\n              \u003coption value=\"diamond\"\u003eDiamond\u003c\/option\u003e\n            \u003c\/select\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eDot Size\u003c\/label\u003e\n            \u003cinput id=\"usafHalfCell\" class=\"usaf-range\" type=\"range\" min=\"4\" max=\"40\" value=\"12\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eStrength\u003c\/label\u003e\n            \u003cinput id=\"usafHalfStrength\" class=\"usaf-range\" type=\"range\" min=\"20\" max=\"160\" value=\"100\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eBrightness\u003c\/label\u003e\n            \u003cinput id=\"usafHalfBrightness\" class=\"usaf-range\" type=\"range\" min=\"-100\" max=\"100\" value=\"0\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eContrast\u003c\/label\u003e\n            \u003cinput id=\"usafHalfContrast\" class=\"usaf-range\" type=\"range\" min=\"-100\" max=\"100\" value=\"10\"\u003e\n\n            \u003clabel class=\"usaf-label\"\u003eDot Colour\u003c\/label\u003e\n            \u003cinput id=\"usafHalfColour\" class=\"usaf-input\" type=\"color\" value=\"#000000\" style=\"padding:4px\"\u003e\n\n            \u003cdiv class=\"usaf-two\" style=\"margin-top:8px\"\u003e\n              \u003cbutton id=\"usafApplyHalf\" class=\"usaf-btn primary\" type=\"button\"\u003eApply\u003c\/button\u003e\n              \u003cbutton id=\"usafResetHalf\" class=\"usaf-btn\" type=\"button\"\u003eReset\u003c\/button\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-two\" style=\"margin-top:7px\"\u003e\n              \u003cbutton id=\"usafHalfToMock\" class=\"usaf-btn\" type=\"button\"\u003eTo Mockup\u003c\/button\u003e\n              \u003cbutton id=\"usafHalfToGang\" class=\"usaf-btn\" type=\"button\"\u003eTo Gang\u003c\/button\u003e\n            \u003c\/div\u003e\n\n            \u003cbutton id=\"usafDownloadHalf\" class=\"usaf-btn\" type=\"button\" style=\"width:100%;margin-top:7px\"\u003e\n              Download PNG\n            \u003c\/button\u003e\n\n            \u003cdiv id=\"usafHalfStatus\" class=\"usaf-note\" style=\"margin-top:8px\"\u003eSelect or upload artwork.\u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/aside\u003e\n\n        \u003cmain class=\"usaf-panel\"\u003e\n          \u003cdiv class=\"usaf-panel-head\"\u003e\n            \u003cdiv class=\"usaf-title\"\u003eHalftone Preview\u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel-body\"\u003e\n            \u003cdiv class=\"usaf-half-preview\"\u003e\n              \u003ccanvas id=\"usafHalfCanvas\" width=\"1000\" height=\"700\"\u003e\u003c\/canvas\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/main\u003e\n      \u003c\/div\u003e\n    \u003c\/section\u003e\n\n    \u003csection id=\"usafSection_gang\" class=\"usaf-section\"\u003e\n      \u003cdiv class=\"usaf-gang-layout\"\u003e\n        \u003caside class=\"usaf-stack\"\u003e\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eGang Artwork\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cbutton id=\"usafGangUploadBtn\" class=\"usaf-btn primary\" type=\"button\" style=\"width:100%\"\u003e\n                + Upload Artwork\n              \u003c\/button\u003e\n\n              \u003cinput id=\"usafGangUpload\" class=\"usaf-hidden\" type=\"file\" multiple accept=\"image\/png,image\/jpeg,image\/webp\"\u003e\n\n              \u003cbutton id=\"usafGangLibrary\" class=\"usaf-btn\" type=\"button\" style=\"width:100%;margin-top:7px\"\u003e\n                + Selected Library Design\n              \u003c\/button\u003e\n\n              \u003cdiv id=\"usafGangPieces\" style=\"margin-top:9px\"\u003e\u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eSheet Settings\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003clabel class=\"usaf-label\"\u003eFilm Width\u003c\/label\u003e\n              \u003cinput class=\"usaf-input\" value=\"56 cm\" readonly\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eSpacing — cm\u003c\/label\u003e\n              \u003cinput id=\"usafGangSpacing\" class=\"usaf-input\" type=\"number\" min=\"0\" max=\"5\" step=\".1\" value=\".3\"\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eBackground\u003c\/label\u003e\n              \u003cselect id=\"usafGangBackground\" class=\"usaf-select\"\u003e\n                \u003coption value=\"transparent\"\u003eTransparent\u003c\/option\u003e\n                \u003coption value=\"white\"\u003eWhite\u003c\/option\u003e\n                \u003coption value=\"black\"\u003eBlack\u003c\/option\u003e\n              \u003c\/select\u003e\n\n              \u003clabel style=\"display:flex;gap:8px;margin-top:9px;font-size:10px\"\u003e\n                \u003cinput id=\"usafGangSnap\" type=\"checkbox\" checked\u003e\n                Snap movement to 0.1 cm\n              \u003c\/label\u003e\n\n              \u003cbutton id=\"usafAutoNest\" class=\"usaf-btn primary\" type=\"button\" style=\"width:100%;margin-top:9px\"\u003e\n                Smart Auto Nest\n              \u003c\/button\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/aside\u003e\n\n        \u003cmain class=\"usaf-panel\"\u003e\n          \u003cdiv class=\"usaf-panel-head\"\u003e\n            \u003cdiv class=\"usaf-title\"\u003eGang Sheet Workspace\u003c\/div\u003e\n            \u003cdiv class=\"usaf-sub\"\u003eDrag artwork anywhere on the 56 cm sheet.\u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel-body\"\u003e\n            \u003cdiv class=\"usaf-gang-workspace\"\u003e\n              \u003cdiv class=\"usaf-gang-inner\"\u003e\n                \u003cdiv class=\"usaf-width-badge\"\u003e56 CM PRINT WIDTH\u003c\/div\u003e\n                \u003ccanvas id=\"usafGangCanvas\" width=\"672\" height=\"1200\"\u003e\u003c\/canvas\u003e\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/main\u003e\n\n        \u003caside class=\"usaf-stack usaf-gang-right\"\u003e\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eSelected Artwork\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv id=\"usafGangSelected\" class=\"usaf-note\"\u003eTap artwork on the sheet.\u003c\/div\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eWidth — cm\u003c\/label\u003e\n              \u003cinput id=\"usafGangSelectedWidth\" class=\"usaf-input\" type=\"number\" min=\"1\" max=\"56\" step=\".1\" disabled\u003e\n\n              \u003clabel class=\"usaf-label\"\u003eQuick Resize\u003c\/label\u003e\n              \u003cinput id=\"usafGangWidthRange\" class=\"usaf-range\" type=\"range\" min=\"1\" max=\"56\" step=\".1\" disabled\u003e\n\n              \u003cdiv id=\"usafGangQuality\" class=\"usaf-quality good\"\u003eResolution appears here.\u003c\/div\u003e\n\n              \u003cdiv class=\"usaf-two\" style=\"margin-top:7px\"\u003e\n                \u003cbutton id=\"usafGangRotate\" class=\"usaf-btn\" type=\"button\"\u003eRotate 90°\u003c\/button\u003e\n                \u003cbutton id=\"usafGangDuplicate\" class=\"usaf-btn\" type=\"button\"\u003eDuplicate\u003c\/button\u003e\n              \u003c\/div\u003e\n\n              \u003cbutton id=\"usafGangDelete\" class=\"usaf-btn danger\" type=\"button\" style=\"width:100%;margin-top:7px\"\u003e\n                Delete\n              \u003c\/button\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv id=\"usafDownloadAccessPanel\" class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eDownload Access\u003c\/div\u003e\n              \u003cdiv class=\"usaf-sub\"\u003eLogin, fill form, approve, then download.\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv id=\"usafGangLoginStatus\" class=\"usaf-note\"\u003eChecking Shopify login…\u003c\/div\u003e\n\n              \u003ca id=\"usafGangLoginLink\" class=\"usaf-btn primary usaf-hidden\" href=\"\/account\/login\" style=\"width:100%;display:block;text-align:center;margin-top:8px\"\u003e\n                Login To Download\n              \u003c\/a\u003e\n\n              \u003cdiv id=\"usafGangDownloadForm\" class=\"usaf-hidden\"\u003e\n                \u003clabel class=\"usaf-label\"\u003eFull Name\u003c\/label\u003e\n                \u003cinput id=\"usafGangCustomerName\" class=\"usaf-input\" type=\"text\" placeholder=\"Customer full name\"\u003e\n\n                \u003clabel class=\"usaf-label\"\u003eEmail\u003c\/label\u003e\n                \u003cinput id=\"usafGangCustomerEmail\" class=\"usaf-input\" type=\"email\" placeholder=\"customer@email.com\"\u003e\n\n                \u003clabel class=\"usaf-label\"\u003ePhone\u003c\/label\u003e\n                \u003cinput id=\"usafGangCustomerPhone\" class=\"usaf-input\" type=\"tel\" placeholder=\"Phone number\"\u003e\n\n                \u003clabel class=\"usaf-label\"\u003eMessage \/ Notes\u003c\/label\u003e\n                \u003ctextarea id=\"usafGangCustomerMessage\" class=\"usaf-textarea\" placeholder=\"Optional notes about this gang sheet\"\u003e\u003c\/textarea\u003e\n\n                \u003cdiv class=\"usaf-price-card\" style=\"margin-top:9px\"\u003e\n                  \u003cdiv class=\"usaf-price-grid\"\u003e\n                    \u003cspan\u003eFilm Width\u003c\/span\u003e\u003cstrong\u003e56 cm\u003c\/strong\u003e\n                    \u003cspan\u003eArtwork Length\u003c\/span\u003e\u003cstrong id=\"usafGangActual\"\u003e0 cm\u003c\/strong\u003e\n                    \u003cspan\u003eDownload Length\u003c\/span\u003e\u003cstrong id=\"usafGangCharged\"\u003e100 cm\u003c\/strong\u003e\n                    \u003cspan\u003eArtwork Count\u003c\/span\u003e\u003cstrong id=\"usafGangCount\"\u003e0\u003c\/strong\u003e\n                  \u003c\/div\u003e\n\n                  \u003cdiv class=\"usaf-hidden\"\u003e\n                    \u003cstrong id=\"usafGangUnitPrice\"\u003eDownload only\u003c\/strong\u003e\n                    \u003cstrong id=\"usafGangUnits\"\u003e10\u003c\/strong\u003e\n                    \u003cstrong id=\"usafGangPrice\"\u003eDownload only\u003c\/strong\u003e\n                  \u003c\/div\u003e\n                \u003c\/div\u003e\n\n                \u003clabel style=\"display:flex;gap:8px;margin-top:10px;font-size:10px;line-height:1.5\"\u003e\n                  \u003cinput id=\"usafGangTerms\" type=\"checkbox\"\u003e\n                  I confirm this file is ready and I approve the layout for download.\n                \u003c\/label\u003e\n\n                \u003cdiv style=\"margin-top:9px\"\u003e\n                  \u003cbutton id=\"usafGangProduction\" class=\"usaf-btn primary\" type=\"button\" style=\"width:100%\" disabled\u003e\n                    Download 300 DPI PNG\n                  \u003c\/button\u003e\n                \u003c\/div\u003e\n\n                \u003cdiv id=\"usafGangCartStatus\" class=\"usaf-note\" style=\"margin-top:8px\"\u003e\n                  Add artwork, fill the form, and approve the layout.\n                \u003c\/div\u003e\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n\n          \u003cdiv class=\"usaf-panel\"\u003e\n            \u003cdiv class=\"usaf-panel-head\"\u003e\n              \u003cdiv class=\"usaf-title\"\u003eExport Info\u003c\/div\u003e\n            \u003c\/div\u003e\n\n            \u003cdiv class=\"usaf-panel-body\"\u003e\n              \u003cdiv id=\"usafGangExportStatus\" class=\"usaf-note\"\u003e\n                Production width is exactly 56 cm at 300 DPI.\n              \u003c\/div\u003e\n            \u003c\/div\u003e\n          \u003c\/div\u003e\n        \u003c\/aside\u003e\n      \u003c\/div\u003e\n    \u003c\/section\u003e\n  \u003c\/div\u003e\n\n  \u003cscript\u003e\n    (function(){\n      \"use strict\";\n\n      if (window.USAF_PRO_STUDIO_FULL_READY) return;\n      window.USAF_PRO_STUDIO_FULL_READY = true;\n\n      const DRIVE_API = \"https:\/\/script.google.com\/macros\/s\/AKfycbwvSSSZqM3M8--3wfKTXy9mU3gYeBMvnaooIoK-IH9liZOrUSUUGRzY29QeALOO95zb\/exec\";\n      const GOOGLE_SHEET_LOG_URL = \"https:\/\/script.google.com\/macros\/s\/AKfycbxP9xY2BmPv16iveyeMJ0Qtbfs-n7xVM5oRB9GC4N82nFi9BSUhZTRXcdg31X45-Y6O\/exec\";\n\n      const DPI = 300;\n      const GANG_WIDTH_CM = 56;\n      const GANG_MIN_CM = 100;\n      const GANG_STEP_CM = 10;\n      const PRINT_PX_CM = DPI \/ 2.54;\n      const MAX_UPLOAD_BASE64_CHARS = 36000000;\n\n      const $ = function(id){ return document.getElementById(id); };\n\n      const mockCanvas = $(\"usafMockCanvas\");\n      const mctx = mockCanvas.getContext(\"2d\");\n      const halfCanvas = $(\"usafHalfCanvas\");\n      const hctx = halfCanvas.getContext(\"2d\");\n      const gangCanvas = $(\"usafGangCanvas\");\n      const gctx = gangCanvas.getContext(\"2d\");\n\n      const state = {\n        library: [],\n        filtered: [],\n        category: \"All\",\n        search: \"\",\n        page: 1,\n        selected: null,\n        productType: \"tshirt\",\n        method: \"dtf\",\n        area: \"front\",\n        mockColour: \"#f2f2f2\",\n        scenes: {\n          tshirt: { front: [], back: [], leftSleeve: [], rightSleeve: [], neck: [] },\n          cap: { front: [], leftSide: [], rightSide: [], back: [] }\n        },\n        activeLayer: null,\n        customerImage: null,\n        mockDrag: null,\n        product: null,\n        options: [],\n        variant: null,\n        variantImage: null,\n        variantLock: false,\n        gangProduct: false,\n        halfImage: null,\n        halfSourceCanvas: null,\n        halfResult: null,\n        gangPieces: [],\n        gangSelected: null,\n        gangDrag: null,\n        gangBackground: \"transparent\",\n        gangSnap: true,\n        customer: {\n          loggedIn: false,\n          id: \"\",\n          name: \"\",\n          email: \"\",\n          phone: \"\"\n        }\n      };\n\n      function makeId(prefix) {\n        return prefix + \"_\" + Date.now() + \"_\" + Math.random().toString(36).slice(2, 8);\n      }\n\n      function createDownloadId() {\n        return \"USAF-\" + Date.now() + \"-\" + Math.random().toString(36).slice(2, 8).toUpperCase();\n      }\n\n      function clamp(value, min, max) {\n        return Math.max(min, Math.min(max, value));\n      }\n\n      function esc(value) {\n        return String(value || \"\")\n          .replace(\/\u0026\/g, \"\u0026amp;\")\n          .replace(\/\u003c\/g, \"\u0026lt;\")\n          .replace(\/\u003e\/g, \"\u0026gt;\")\n          .replace(\/\"\/g, \"\u0026quot;\");\n      }\n\n      function rootPath() {\n        return window.Shopify \u0026\u0026 Shopify.routes \u0026\u0026 Shopify.routes.root ? Shopify.routes.root : \"\/\";\n      }\n\n      function imageWidth(image) {\n        return image.naturalWidth || image.width || 1;\n      }\n\n      function imageHeight(image) {\n        return image.naturalHeight || image.height || 1;\n      }\n\n      function money(value) {\n        try {\n          return new Intl.NumberFormat(\"en-US\", {\n            style: \"currency\",\n            currency: Shopify.currency.active || \"USD\"\n          }).format(Number(value || 0));\n        } catch (error) {\n          return \"$\" + Number(value || 0).toFixed(2);\n        }\n      }\n\n      function setStatus(id, className, message) {\n        const element = $(id);\n        if (!element) return;\n        element.className = className;\n        element.textContent = message;\n      }\n\n      function readFile(file) {\n        return new Promise(function(resolve, reject) {\n          const reader = new FileReader();\n          reader.onload = function(event){ resolve(event.target.result); };\n          reader.onerror = reject;\n          reader.readAsDataURL(file);\n        });\n      }\n\n      function loadImage(src, cors) {\n        return new Promise(function(resolve) {\n          const image = new Image();\n          if (cors) image.crossOrigin = \"anonymous\";\n          image.onload = function(){ resolve(image); };\n          image.onerror = function(){ resolve(null); };\n          image.src = src;\n        });\n      }\n\n      function imageToCanvas(image) {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = imageWidth(image);\n        canvas.height = imageHeight(image);\n        canvas.getContext(\"2d\").drawImage(image, 0, 0);\n        return canvas;\n      }\n\n      function cloneCanvas(source) {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = source.width;\n        canvas.height = source.height;\n        canvas.getContext(\"2d\").drawImage(source, 0, 0);\n        return canvas;\n      }\n\n      function isMobileSafari() {\n        return \/iPhone|iPad|iPod\/i.test(navigator.userAgent) \u0026\u0026\n          \/Safari\/i.test(navigator.userAgent) \u0026\u0026\n          !\/CriOS|FxiOS|EdgiOS\/i.test(navigator.userAgent);\n      }\n\n      function canvasToBlob(canvas) {\n        return new Promise(function(resolve) {\n          canvas.toBlob(function(blob) {\n            resolve(blob || null);\n          }, \"image\/png\", 1);\n        });\n      }\n\n      function canvasToDataUrlSafe(canvas) {\n        try {\n          return canvas.toDataURL(\"image\/png\");\n        } catch (error) {\n          return \"\";\n        }\n      }\n\n      function downloadBlob(blob, filename, statusId) {\n        if (!blob) {\n          setStatus(statusId, \"usaf-note error\", \"Could not create PNG.\");\n          return;\n        }\n\n        const url = URL.createObjectURL(blob);\n\n        if (isMobileSafari()) {\n          const popup = window.open(url, \"_blank\");\n\n          if (!popup) {\n            setStatus(statusId, \"usaf-note error\", \"Popup blocked. Allow popups and click download again.\");\n            setTimeout(function(){ URL.revokeObjectURL(url); }, 30000);\n            return;\n          }\n\n          setStatus(statusId, \"usaf-note good\", \"PNG opened. Hold image and save to Photos or Files.\");\n          setTimeout(function(){ URL.revokeObjectURL(url); }, 60000);\n          return;\n        }\n\n        const link = document.createElement(\"a\");\n        link.href = url;\n        link.download = filename;\n        link.rel = \"noopener\";\n        link.style.display = \"none\";\n\n        document.body.appendChild(link);\n        link.click();\n        link.remove();\n\n        setStatus(statusId, \"usaf-note good\", \"Download started. Check your Downloads folder.\");\n\n        if (\/Android|iPhone|iPad|iPod\/i.test(navigator.userAgent)) {\n          setTimeout(function(){\n            window.open(url, \"_blank\");\n          }, 800);\n        }\n\n        setTimeout(function(){ URL.revokeObjectURL(url); }, 60000);\n      }\n\n      async function downloadCanvas(canvas, filename, statusId) {\n        const blob = await canvasToBlob(canvas);\n        downloadBlob(blob, filename, statusId);\n        return blob;\n      }\n\n      function loginUrl() {\n        return \"\/account\/login?return_url=\" + encodeURIComponent(location.pathname + location.search + \"#usaf-download\");\n      }\n\n      function readLiquidCustomer() {\n        const element = $(\"usafCustomerJson\");\n        if (!element) return;\n\n        try {\n          const data = JSON.parse(element.textContent);\n\n          if (data \u0026\u0026 (data.loggedIn || data.id || data.email)) {\n            state.customer.loggedIn = true;\n            state.customer.id = data.id || \"\";\n            state.customer.name = data.name || \"\";\n            state.customer.email = data.email || \"\";\n            state.customer.phone = data.phone || \"\";\n          }\n        } catch (error) {}\n      }\n\n      function readAnalyticsCustomer() {\n        try {\n          const id = ShopifyAnalytics.meta.page.customerId;\n\n          if (id) {\n            state.customer.loggedIn = true;\n            state.customer.id = id;\n          }\n        } catch (error) {}\n      }\n\n      async function verifyCustomerLogin() {\n        readLiquidCustomer();\n        readAnalyticsCustomer();\n\n        if (!state.customer.loggedIn) {\n          try {\n            const response = await fetch(\"\/account\", { credentials: \"include\" });\n            const finalUrl = response.url || \"\";\n            const text = await response.text();\n            const lower = text.toLowerCase();\n\n            if (\n              response.ok \u0026\u0026\n              !finalUrl.includes(\"\/account\/login\") \u0026\u0026\n              !lower.includes(\"customer_login\") \u0026\u0026\n              !lower.includes(\"login to your account\")\n            ) {\n              state.customer.loggedIn = true;\n            }\n          } catch (error) {}\n        }\n\n        if ($(\"usafGangCustomerName\") \u0026\u0026 state.customer.name) {\n          $(\"usafGangCustomerName\").value = state.customer.name;\n        }\n\n        if ($(\"usafGangCustomerEmail\") \u0026\u0026 state.customer.email) {\n          $(\"usafGangCustomerEmail\").value = state.customer.email;\n        }\n\n        if ($(\"usafGangCustomerPhone\") \u0026\u0026 state.customer.phone) {\n          $(\"usafGangCustomerPhone\").value = state.customer.phone;\n        }\n\n        updateGangButtons();\n      }\n\n      function isGangText(text) {\n        text = String(text || \"\").toLowerCase();\n\n        return (\n          text.includes(\"gang sheet\") ||\n          text.includes(\"gangsheet\") ||\n          text.includes(\"gang-sheet\") ||\n          text.includes(\"dtf gang\") ||\n          text.includes(\"gang dtf\")\n        );\n      }\n\n      function enableGangMode() {\n        state.gangProduct = true;\n        $(\"usafMode\").textContent = \"Gang Sheet Download\";\n        $(\"usafPersonaliseTab\").classList.add(\"usaf-hidden\");\n        $(\"usafSection_studio\").classList.add(\"usaf-hidden\");\n        openTab(\"gang\");\n      }\n\n      function detectGangFromPage() {\n        const text = decodeURIComponent(location.pathname) + \" \" + document.title;\n        if (isGangText(text)) enableGangMode();\n      }\n\n      function openTab(name) {\n        if (name === \"studio\" \u0026\u0026 state.gangProduct) name = \"gang\";\n\n        document.querySelectorAll(\"#usaf-pro-studio .usaf-section\").forEach(function(section) {\n          section.classList.remove(\"active\");\n        });\n\n        const section = $(\"usafSection_\" + name);\n        if (section) section.classList.add(\"active\");\n\n        document.querySelectorAll(\"#usaf-pro-studio [data-tab]\").forEach(function(button) {\n          button.classList.toggle(\"active\", button.dataset.tab === name);\n        });\n\n        if (name === \"studio\") {\n          renderAreas();\n          renderMock();\n          renderLayers();\n        }\n\n        if (name === \"gang\") {\n          renderGang();\n          updateGangButtons();\n        }\n      }\n\n      document.querySelectorAll(\"#usaf-pro-studio [data-tab]\").forEach(function(button) {\n        button.onclick = function() {\n          openTab(button.dataset.tab);\n        };\n      });\n\n      function galleryPageSize() {\n        if (innerWidth \u003c 720) return 4;\n        if (innerWidth \u003c 1100) return 6;\n        return 8;\n      }\n\n      function loadLibrary() {\n        $(\"usafLibraryStatus\").className = \"usaf-note\";\n        $(\"usafLibraryStatus\").textContent = \"Loading Google Drive…\";\n\n        const callback = \"USAF_LIBRARY_\" + Date.now();\n\n        window[callback] = function(data) {\n          const items = data \u0026\u0026 Array.isArray(data.items) ? data.items : [];\n\n          state.library = items\n            .map(function(item, index) {\n              return {\n                id: item.libraryId || item.id || \"design_\" + index,\n                name: item.name || \"Design \" + (index + 1),\n                category: item.category || \"Other\",\n                description: item.version || item.category || \"Artwork\",\n                folder: item.folder || item.sourceFolder || \"\",\n                folderPath: item.folderPath || \"\",\n                imageUrl: item.imageUrl || \"\"\n              };\n            })\n            .filter(function(item){ return !!item.imageUrl; });\n\n          $(\"usafLibraryStatus\").className = \"usaf-note good\";\n          $(\"usafLibraryStatus\").innerHTML = \"Loaded \u003cstrong\u003e\" + state.library.length + \"\u003c\/strong\u003e designs.\";\n\n          renderFilters();\n          filterLibrary();\n\n          try {\n            delete window[callback];\n          } catch (error) {}\n        };\n\n        const script = document.createElement(\"script\");\n        script.src = DRIVE_API + \"?callback=\" + encodeURIComponent(callback) + \"\u0026t=\" + Date.now();\n\n        script.onerror = function() {\n          $(\"usafLibraryStatus\").className = \"usaf-note error\";\n          $(\"usafLibraryStatus\").textContent = \"Could not load Google Drive. You can still upload local artwork.\";\n          renderFilters();\n          filterLibrary();\n        };\n\n        document.body.appendChild(script);\n      }\n\n      function renderFilters() {\n        const wrapper = $(\"usafFilters\");\n        wrapper.innerHTML = \"\";\n\n        const categories = [\"All\"].concat(\n          Array.from(new Set(state.library.map(function(item){ return item.category; })))\n        ).sort(function(a, b) {\n          if (a === \"All\") return -1;\n          if (b === \"All\") return 1;\n          return a.localeCompare(b);\n        });\n\n        categories.forEach(function(category) {\n          const count = category === \"All\"\n            ? state.library.length\n            : state.library.filter(function(item){ return item.category === category; }).length;\n\n          const button = document.createElement(\"button\");\n          button.type = \"button\";\n          button.className = \"usaf-chip\" + (state.category === category ? \" active\" : \"\");\n          button.textContent = category + \" (\" + count + \")\";\n\n          button.onclick = function() {\n            state.category = category;\n            state.page = 1;\n            renderFilters();\n            filterLibrary();\n          };\n\n          wrapper.appendChild(button);\n        });\n      }\n\n      function filterLibrary() {\n        const search = state.search.trim().toLowerCase();\n\n        state.filtered = state.library.filter(function(item) {\n          const categoryMatch = state.category === \"All\" || item.category === state.category;\n          const haystack = (\n            item.name + \" \" +\n            item.category + \" \" +\n            item.description + \" \" +\n            item.folder + \" \" +\n            item.folderPath\n          ).toLowerCase();\n\n          return categoryMatch \u0026\u0026 (!search || haystack.includes(search));\n        });\n\n        renderGallery();\n      }\n\n      function selectDesign(item) {\n        state.selected = item;\n\n        $(\"usafSelectedDesign\").innerHTML =\n          \"\u003cstrong\u003e\" + esc(item.name) + \"\u003c\/strong\u003e\u003cbr\u003e\" + esc(item.category);\n\n        renderGallery();\n      }\n\n      function renderGallery() {\n        const wrapper = $(\"usafGallery\");\n        wrapper.innerHTML = \"\";\n\n        const perPage = galleryPageSize();\n        const totalPages = Math.max(1, Math.ceil(state.filtered.length \/ perPage));\n        state.page = Math.min(state.page, totalPages);\n\n        const start = (state.page - 1) * perPage;\n        const visible = state.filtered.slice(start, start + perPage);\n\n        $(\"usafGalleryInfo\").textContent =\n          state.filtered.length + \" designs · Page \" + state.page + \" of \" + totalPages;\n\n        visible.forEach(function(item) {\n          const card = document.createElement(\"article\");\n          card.className = \"usaf-card\" + (state.selected \u0026\u0026 state.selected.id === item.id ? \" selected\" : \"\");\n\n          card.innerHTML =\n            '\u003cdiv class=\"usaf-thumb\"\u003e' +\n              '\u003cimg src=\"' + esc(item.imageUrl) + '\" loading=\"lazy\" draggable=\"false\"\u003e' +\n              '\u003cspan class=\"usaf-cat\"\u003e' + esc(item.category) + '\u003c\/span\u003e' +\n            '\u003c\/div\u003e' +\n            '\u003cdiv class=\"usaf-card-body\"\u003e' +\n              '\u003cspan class=\"usaf-card-name\"\u003e' + esc(item.name) + '\u003c\/span\u003e' +\n              '\u003cdiv class=\"usaf-card-meta\"\u003e' + esc(item.description) + '\u003c\/div\u003e' +\n              '\u003cdiv class=\"usaf-card-actions\"\u003e' +\n                '\u003cbutton data-select class=\"usaf-btn\" type=\"button\"\u003eSelect\u003c\/button\u003e' +\n                '\u003cbutton data-mock class=\"usaf-btn primary\" type=\"button\"\u003eMockup\u003c\/button\u003e' +\n                '\u003cbutton data-half class=\"usaf-btn\" type=\"button\"\u003eHalftone\u003c\/button\u003e' +\n                '\u003cbutton data-gang class=\"usaf-btn\" type=\"button\"\u003e+ Gang\u003c\/button\u003e' +\n              '\u003c\/div\u003e' +\n            '\u003c\/div\u003e';\n\n          card.querySelector(\"img\").oncontextmenu = function(event){ event.preventDefault(); };\n\n          card.querySelector(\"[data-select]\").onclick = function() {\n            selectDesign(item);\n          };\n\n          card.querySelector(\"[data-mock]\").onclick = async function() {\n            selectDesign(item);\n            await addLibraryToMock(item);\n            openTab(\"studio\");\n          };\n\n          card.querySelector(\"[data-half]\").onclick = function() {\n            selectDesign(item);\n            loadLibraryToHalf(item);\n            openTab(\"halftone\");\n          };\n\n          card.querySelector(\"[data-gang]\").onclick = async function() {\n            selectDesign(item);\n            await addLibraryToGang(item);\n            openTab(\"gang\");\n          };\n\n          wrapper.appendChild(card);\n        });\n\n        renderPages(totalPages);\n      }\n\n      function renderPages(total) {\n        const wrapper = $(\"usafPages\");\n        wrapper.innerHTML = \"\";\n\n        if (total \u003c= 1) return;\n\n        let first = Math.max(1, state.page - 2);\n        let last = Math.min(total, first + 4);\n        first = Math.max(1, last - 4);\n\n        for (let index = first; index \u003c= last; index += 1) {\n          const button = document.createElement(\"button\");\n          button.type = \"button\";\n          button.className = \"usaf-page\" + (index === state.page ? \" active\" : \"\");\n          button.textContent = index;\n\n          button.onclick = function() {\n            state.page = index;\n            renderGallery();\n          };\n\n          wrapper.appendChild(button);\n        }\n      }\n\n      $(\"usafSearch\").oninput = function() {\n        state.search = this.value;\n        state.page = 1;\n        filterLibrary();\n      };\n\n      $(\"usafRefreshLibrary\").onclick = loadLibrary;\n      $(\"usafRefreshTop\").onclick = loadLibrary;\n\n      $(\"usafLibraryUploadBtn\").onclick = function() {\n        $(\"usafLibraryUpload\").click();\n      };\n\n      $(\"usafLibraryUpload\").onchange = function(event) {\n        Array.from(event.target.files || []).forEach(function(file) {\n          const reader = new FileReader();\n\n          reader.onload = function(loadEvent) {\n            state.library.unshift({\n              id: makeId(\"local\"),\n              name: file.name,\n              category: \"Local Uploads\",\n              description: \"Local Artwork\",\n              folder: \"\",\n              folderPath: \"\",\n              imageUrl: loadEvent.target.result\n            });\n\n            renderFilters();\n            filterLibrary();\n          };\n\n          reader.readAsDataURL(file);\n        });\n\n        event.target.value = \"\";\n      };\n\n      function productHandle() {\n        const match = location.pathname.match(\/\\\/products\\\/([^\\\/?#]+)\/);\n        return match ? decodeURIComponent(match[1]) : \"\";\n      }\n\n      function buildOptions(product) {\n        return (product.options || []).map(function(option, index) {\n          const name = typeof option === \"string\" ? option : option.name;\n          let values = [];\n\n          if (typeof option === \"object\" \u0026\u0026 Array.isArray(option.values)) {\n            values = option.values.slice();\n          } else {\n            values = Array.from(new Set(\n              product.variants\n                .map(function(variant){ return variant[\"option\" + (index + 1)]; })\n                .filter(Boolean)\n            ));\n          }\n\n          return { name: name, values: values };\n        });\n      }\n\n      function validVariantIds() {\n        if (!state.product) return new Set();\n\n        return new Set(\n          state.product.variants.map(function(variant){ return String(variant.id); })\n        );\n      }\n\n      function nativeForms() {\n        const valid = validVariantIds();\n\n        return Array.from(document.querySelectorAll('form[action*=\"\/cart\/add\"]')).filter(function(form) {\n          if (form.closest(\"#usaf-pro-studio\")) return false;\n\n          const input = form.querySelector('[name=\"id\"]');\n          if (!input || !input.value) return false;\n\n          return valid.has(String(input.value));\n        });\n      }\n\n      function nativeVariantId() {\n        const forms = nativeForms();\n\n        for (let index = 0; index \u003c forms.length; index += 1) {\n          const input = forms[index].querySelector('[name=\"id\"]');\n          if (input \u0026\u0026 input.value) return String(input.value);\n        }\n\n        const query = new URLSearchParams(location.search).get(\"variant\");\n\n        if (query \u0026\u0026 validVariantIds().has(String(query))) {\n          return String(query);\n        }\n\n        return \"\";\n      }\n\n      function currentNativeVariant() {\n        if (!state.product) return null;\n\n        const id = nativeVariantId();\n        if (!id) return null;\n\n        return state.product.variants.find(function(variant) {\n          return String(variant.id) === String(id);\n        }) || null;\n      }\n\n      function exactVariant(values) {\n        if (!state.product) return null;\n\n        return state.product.variants.find(function(variant) {\n          return state.options.every(function(option, index) {\n            return variant[\"option\" + (index + 1)] === values[index];\n          });\n        }) || null;\n      }\n\n      function nativeOptionControls(optionName, index) {\n        const controls = [];\n\n        document.querySelectorAll(\"select,input\").forEach(function(control) {\n          if (control.closest(\"#usaf-pro-studio\")) return;\n\n          if (control.name === \"options[\" + optionName + \"]\") {\n            controls.push(control);\n          }\n        });\n\n        const selects = Array.from(document.querySelectorAll(\"variant-selects select\"));\n\n        if (selects[index] \u0026\u0026 !controls.includes(selects[index])) {\n          controls.push(selects[index]);\n        }\n\n        const fields = Array.from(document.querySelectorAll(\"variant-radios fieldset,variant-selects fieldset\"));\n\n        if (fields[index]) {\n          fields[index].querySelectorAll('input[type=\"radio\"]').forEach(function(radio) {\n            if (!controls.includes(radio)) controls.push(radio);\n          });\n        }\n\n        return controls;\n      }\n\n      function pushVariantToShopify(variant) {\n        state.options.forEach(function(option, index) {\n          const value = variant[\"option\" + (index + 1)];\n\n          nativeOptionControls(option.name, index).forEach(function(control) {\n            if (control.tagName === \"SELECT\" \u0026\u0026 control.value !== value) {\n              control.value = value;\n              control.dispatchEvent(new Event(\"input\", { bubbles: true }));\n              control.dispatchEvent(new Event(\"change\", { bubbles: true }));\n            }\n\n            if (control.type === \"radio\" \u0026\u0026 control.value === value \u0026\u0026 !control.checked) {\n              control.checked = true;\n              control.dispatchEvent(new Event(\"change\", { bubbles: true }));\n            }\n          });\n        });\n\n        setTimeout(function() {\n          nativeForms().forEach(function(form) {\n            const input = form.querySelector('[name=\"id\"]');\n\n            if (input) {\n              input.value = variant.id;\n              input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n            }\n          });\n        }, 120);\n      }\n\n      function buildVariantSelectors() {\n        const wrapper = $(\"usafVariantOptions\");\n        wrapper.innerHTML = \"\";\n\n        state.options.forEach(function(option, index) {\n          if (option.values.length === 1 \u0026\u0026 option.values[0] === \"Default Title\") return;\n\n          const label = document.createElement(\"label\");\n          label.className = \"usaf-label\";\n          label.textContent = option.name;\n\n          const select = document.createElement(\"select\");\n          select.className = \"usaf-select\";\n          select.id = \"usafOption_\" + index;\n\n          option.values.forEach(function(value) {\n            const item = document.createElement(\"option\");\n            item.value = value;\n            item.textContent = value;\n            select.appendChild(item);\n          });\n\n          select.onchange = function() {\n            if (state.variantLock) return;\n\n            const values = state.options.map(function(optionItem, optionIndex) {\n              const control = $(\"usafOption_\" + optionIndex);\n              return control ? control.value : \"\";\n            });\n\n            const variant = exactVariant(values);\n\n            if (variant) {\n              applyVariant(variant, true);\n            }\n          };\n\n          wrapper.appendChild(label);\n          wrapper.appendChild(select);\n        });\n      }\n\n      async function applyVariant(variant, pushNative) {\n        if (!variant) return;\n\n        state.variantLock = true;\n        state.variant = variant;\n\n        state.options.forEach(function(option, index) {\n          const value = variant[\"option\" + (index + 1)];\n          const select = $(\"usafOption_\" + index);\n          if (select) select.value = value;\n        });\n\n        if ($(\"usafProductPrice\")) {\n          $(\"usafProductPrice\").textContent = money(Number(variant.price || 0) \/ 100);\n        }\n\n        if ($(\"usafProductStock\")) {\n          $(\"usafProductStock\").textContent = variant.available ? \"Available\" : \"Sold out\";\n        }\n\n        let imageUrl = \"\";\n\n        if (variant.featured_image) {\n          imageUrl = typeof variant.featured_image === \"string\"\n            ? variant.featured_image\n            : variant.featured_image.src;\n        }\n\n        if (!imageUrl \u0026\u0026 state.product \u0026\u0026 state.product.featured_image) {\n          imageUrl = typeof state.product.featured_image === \"string\"\n            ? state.product.featured_image\n            : state.product.featured_image.src;\n        }\n\n        if (imageUrl) {\n          $(\"usafProductImage\").src = imageUrl;\n          state.variantImage = await loadImage(imageUrl, true);\n\n          if (!state.variantImage) {\n            state.variantImage = await loadImage(imageUrl, false);\n          }\n        }\n\n        if (pushNative) pushVariantToShopify(variant);\n\n        renderMock();\n\n        setTimeout(function() {\n          state.variantLock = false;\n        }, 250);\n      }\n\n      async function loadShopifyProduct() {\n        const handle = productHandle();\n\n        if (!handle) {\n          $(\"usafMode\").textContent = \"Standalone Studio\";\n          $(\"usafProductStatus\").className = \"usaf-note error\";\n          $(\"usafProductStatus\").textContent = \"Open on a Shopify product page to use product mockup and checkout.\";\n          return;\n        }\n\n        try {\n          const response = await fetch(rootPath() + \"products\/\" + encodeURIComponent(handle) + \".js\", { cache: \"no-store\" });\n\n          if (!response.ok) {\n            throw new Error(\"Could not load Shopify product.\");\n          }\n\n          state.product = await response.json();\n          state.options = buildOptions(state.product);\n\n          if (isGangText(state.product.title + \" \" + state.product.handle)) {\n            enableGangMode();\n          } else {\n            $(\"usafMode\").textContent = \"Personalisation Product\";\n          }\n\n          $(\"usafProductStatus\").className = \"usaf-note good\";\n          $(\"usafProductStatus\").textContent = \"Shopify product detected.\";\n          $(\"usafProductBox\").classList.remove(\"usaf-hidden\");\n          $(\"usafProductTitle\").textContent = state.product.title;\n\n          buildVariantSelectors();\n\n          let variant = currentNativeVariant();\n\n          if (!variant) {\n            const queryId = new URLSearchParams(location.search).get(\"variant\");\n\n            if (queryId) {\n              variant = state.product.variants.find(function(item) {\n                return String(item.id) === String(queryId);\n              });\n            }\n          }\n\n          if (!variant) {\n            variant = state.product.variants.find(function(item){ return item.available; }) || state.product.variants[0];\n          }\n\n          await applyVariant(variant, false);\n        } catch (error) {\n          $(\"usafProductStatus\").className = \"usaf-note error\";\n          $(\"usafProductStatus\").textContent = error.message;\n        }\n      }\n\n      function pullShopifyVariant() {\n        if (state.variantLock || !state.product) return;\n\n        const variant = currentNativeVariant();\n        if (!variant) return;\n\n        if (state.variant \u0026\u0026 String(state.variant.id) === String(variant.id)) return;\n\n        applyVariant(variant, false);\n      }\n\n      document.addEventListener(\"change\", function(event) {\n        if (event.target.closest \u0026\u0026 event.target.closest(\"#usaf-pro-studio\")) return;\n        setTimeout(pullShopifyVariant, 80);\n      });\n\n      setInterval(pullShopifyVariant, 700);\n\n      function currentScene() {\n        return state.scenes[state.productType][state.area];\n      }\n\n      function areaList() {\n        if (state.productType === \"cap\") {\n          return [\n            [\"front\", \"Front\"],\n            [\"leftSide\", \"Left\"],\n            [\"rightSide\", \"Right\"],\n            [\"back\", \"Back\"]\n          ];\n        }\n\n        return [\n          [\"front\", \"Front\"],\n          [\"back\", \"Back\"],\n          [\"leftSleeve\", \"Left Sleeve\"],\n          [\"rightSleeve\", \"Right Sleeve\"],\n          [\"neck\", \"Neck\"]\n        ];\n      }\n\n      function renderAreas() {\n        const wrapper = $(\"usafAreas\");\n        wrapper.innerHTML = \"\";\n\n        areaList().forEach(function(area) {\n          const button = document.createElement(\"button\");\n          button.type = \"button\";\n          button.className = \"usaf-chip\" + (state.area === area[0] ? \" active\" : \"\");\n          button.textContent = area[1];\n\n          button.onclick = function() {\n            state.area = area[0];\n            state.activeLayer = null;\n            renderAreas();\n            renderMock();\n            renderLayers();\n          };\n\n          wrapper.appendChild(button);\n        });\n      }\n\n      document.querySelectorAll(\"#usaf-pro-studio [data-product]\").forEach(function(button) {\n        button.onclick = function() {\n          document.querySelectorAll(\"#usaf-pro-studio [data-product]\").forEach(function(element) {\n            element.classList.remove(\"primary\");\n          });\n\n          button.classList.add(\"primary\");\n          state.productType = button.dataset.product;\n          state.area = \"front\";\n          state.activeLayer = null;\n\n          renderAreas();\n          renderMock();\n          renderLayers();\n        };\n      });\n\n      document.querySelectorAll(\"#usaf-pro-studio [data-method]\").forEach(function(button) {\n        button.onclick = function() {\n          document.querySelectorAll(\"#usaf-pro-studio [data-method]\").forEach(function(element) {\n            element.classList.remove(\"primary\");\n          });\n\n          button.classList.add(\"primary\");\n          state.method = button.dataset.method;\n\n          if (state.method === \"embroidery\") {\n            $(\"usafTextStyle\").value = \"stitch\";\n          }\n\n          renderMock();\n        };\n      });\n\n      function printRect() {\n        if (state.productType === \"cap\") {\n          if (state.area === \"front\") return { x: 300, y: 290, w: 260, h: 120 };\n          return { x: 320, y: 295, w: 210, h: 110 };\n        }\n\n        if (state.area === \"front\") return { x: 280, y: 210, w: 300, h: 360 };\n        if (state.area === \"back\") return { x: 270, y: 210, w: 320, h: 380 };\n        if (state.area === \"neck\") return { x: 340, y: 130, w: 180, h: 90 };\n\n        return { x: 170, y: 230, w: 180, h: 180 };\n      }\n\n      function drawGeneric() {\n        mctx.save();\n        mctx.fillStyle = state.mockColour;\n        mctx.strokeStyle = \"rgba(0,0,0,.15)\";\n\n        if (state.productType === \"cap\") {\n          mctx.beginPath();\n          mctx.ellipse(430, 350, 245, 135, 0, Math.PI, Math.PI * 2);\n          mctx.lineTo(675, 420);\n          mctx.quadraticCurveTo(560, 485, 430, 465);\n          mctx.quadraticCurveTo(300, 485, 185, 420);\n          mctx.closePath();\n          mctx.fill();\n          mctx.stroke();\n        } else {\n          mctx.beginPath();\n          mctx.moveTo(280, 90);\n          mctx.lineTo(210, 130);\n          mctx.lineTo(150, 250);\n          mctx.lineTo(225, 290);\n          mctx.lineTo(245, 780);\n          mctx.lineTo(615, 780);\n          mctx.lineTo(635, 290);\n          mctx.lineTo(710, 250);\n          mctx.lineTo(650, 130);\n          mctx.lineTo(580, 90);\n          mctx.quadraticCurveTo(540, 165, 430, 165);\n          mctx.quadraticCurveTo(320, 165, 280, 90);\n          mctx.closePath();\n          mctx.fill();\n          mctx.stroke();\n        }\n\n        mctx.restore();\n      }\n\n      function drawTextLayerMock(layer) {\n        mctx.save();\n        mctx.font = \"bold \" + layer.fontSize + \"px \" + layer.font;\n        mctx.textAlign = \"center\";\n        mctx.textBaseline = \"middle\";\n        mctx.lineJoin = \"round\";\n\n        if (layer.style === \"shadow\") {\n          mctx.shadowColor = layer.outlineColour;\n          mctx.shadowBlur = 6;\n          mctx.shadowOffsetX = 6;\n          mctx.shadowOffsetY = 6;\n        }\n\n        if (layer.style === \"glow\") {\n          mctx.shadowColor = layer.outlineColour;\n          mctx.shadowBlur = 20;\n        }\n\n        if (layer.style === \"retro\") {\n          mctx.fillStyle = layer.outlineColour;\n\n          for (let offset = 12; offset \u003e= 3; offset -= 3) {\n            mctx.fillText(layer.text, offset, offset);\n          }\n        }\n\n        if (layer.style === \"sticker\") {\n          mctx.lineWidth = Math.max(layer.outline * 2 + 10, 12);\n          mctx.strokeStyle = \"#ffffff\";\n          mctx.strokeText(layer.text, 0, 0);\n        }\n\n        if (layer.style === \"stitch\") {\n          mctx.strokeStyle = layer.colour;\n          mctx.lineWidth = 1;\n\n          for (let x = -layer.w \/ 2; x \u003c layer.w \/ 2; x += 8) {\n            mctx.beginPath();\n            mctx.moveTo(x, -layer.h \/ 2);\n            mctx.lineTo(x + layer.h, layer.h \/ 2);\n            mctx.stroke();\n          }\n        }\n\n        if (layer.outline \u003e 0) {\n          mctx.lineWidth = layer.outline * 2;\n          mctx.strokeStyle = layer.outlineColour;\n          mctx.strokeText(layer.text, 0, 0);\n        }\n\n        mctx.fillStyle = layer.colour;\n        mctx.fillText(layer.text, 0, 0);\n        mctx.restore();\n      }\n\n      function drawMockLayer(layer) {\n        const rect = printRect();\n\n        mctx.save();\n        mctx.beginPath();\n        mctx.rect(rect.x, rect.y, rect.w, rect.h);\n        mctx.clip();\n        mctx.translate(rect.x + layer.x, rect.y + layer.y);\n        mctx.rotate(layer.rotation * Math.PI \/ 180);\n        mctx.globalAlpha = layer.opacity;\n\n        if (layer.type === \"image\") {\n          mctx.drawImage(layer.image, -layer.w \/ 2, -layer.h \/ 2, layer.w, layer.h);\n        } else {\n          drawTextLayerMock(layer);\n        }\n\n        mctx.restore();\n      }\n\n      function renderMock() {\n        if (state.gangProduct) return;\n\n        mctx.clearRect(0, 0, 860, 860);\n        mctx.fillStyle = \"#e5e9ed\";\n        mctx.fillRect(0, 0, 860, 860);\n\n        if (state.variantImage \u0026\u0026 $(\"usafUseVariant\").checked) {\n          const image = state.variantImage;\n          const scale = Math.min(700 \/ imageWidth(image), 760 \/ imageHeight(image));\n          const width = imageWidth(image) * scale;\n          const height = imageHeight(image) * scale;\n\n          mctx.drawImage(image, (860 - width) \/ 2, (860 - height) \/ 2, width, height);\n        } else {\n          drawGeneric();\n        }\n\n        const rect = printRect();\n\n        mctx.save();\n        mctx.strokeStyle = state.method === \"embroidery\" ? \"#9f7aea\" : \"#ffd400\";\n        mctx.fillStyle = state.method === \"embroidery\" ? \"rgba(159,122,234,.08)\" : \"rgba(255,212,0,.08)\";\n        mctx.setLineDash([7, 7]);\n        mctx.fillRect(rect.x, rect.y, rect.w, rect.h);\n        mctx.strokeRect(rect.x, rect.y, rect.w, rect.h);\n        mctx.restore();\n\n        currentScene().forEach(drawMockLayer);\n\n        const layer = activeLayer();\n\n        if (layer) {\n          mctx.save();\n          mctx.translate(rect.x + layer.x, rect.y + layer.y);\n          mctx.rotate(layer.rotation * Math.PI \/ 180);\n          mctx.strokeStyle = \"#ffd400\";\n          mctx.lineWidth = 3;\n          mctx.setLineDash([7, 5]);\n          mctx.strokeRect(-layer.w \/ 2, -layer.h \/ 2, layer.w, layer.h);\n          mctx.restore();\n        }\n      }\n\n      function addImageLayer(image, name, designId) {\n        const rect = printRect();\n        const width = Math.min(rect.w * 0.8, 260);\n        const ratio = imageWidth(image) \/ imageHeight(image);\n\n        const layer = {\n          id: makeId(\"layer\"),\n          type: \"image\",\n          mode: state.method,\n          image: image,\n          name: name,\n          designId: designId || \"\",\n          w: width,\n          h: width \/ ratio,\n          x: rect.w \/ 2,\n          y: rect.h \/ 2,\n          rotation: 0,\n          opacity: 1\n        };\n\n        currentScene().push(layer);\n        state.activeLayer = layer.id;\n\n        renderMock();\n        renderLayers();\n      }\n\n      async function addLibraryToMock(item) {\n        let image = await loadImage(item.imageUrl, true);\n        if (!image) image = await loadImage(item.imageUrl, false);\n        if (!image) return;\n\n        addImageLayer(image, item.name, item.id);\n      }\n\n      $(\"usafAddLibraryMock\").onclick = async function() {\n        if (!state.selected) return;\n        await addLibraryToMock(state.selected);\n      };\n\n      $(\"usafCustomerUpload\").onchange = async function(event) {\n        const file = event.target.files \u0026\u0026 event.target.files[0];\n        if (!file) return;\n\n        state.customerImage = await loadImage(await readFile(file), false);\n        event.target.value = \"\";\n      };\n\n      $(\"usafAddCustomer\").onclick = function() {\n        if (!state.customerImage) return;\n        addImageLayer(state.customerImage, \"Customer Artwork\", \"\");\n      };\n\n      function measureTextLayer(layer) {\n        mctx.save();\n        mctx.font = \"bold \" + layer.fontSize + \"px \" + layer.font;\n        const width = Math.max(90, mctx.measureText(layer.text).width + 36);\n        mctx.restore();\n\n        layer.w = width;\n        layer.h = layer.fontSize * 1.45;\n      }\n\n      $(\"usafAddText\").onclick = function() {\n        const text = $(\"usafText\").value.trim();\n        if (!text) return;\n\n        const rect = printRect();\n        const style = state.method === \"embroidery\" ? \"stitch\" : $(\"usafTextStyle\").value;\n\n        const layer = {\n          id: makeId(\"layer\"),\n          type: \"text\",\n          mode: state.method,\n          text: text,\n          font: $(\"usafFont\").value,\n          fontSize: Number($(\"usafTextSize\").value) || 56,\n          colour: $(\"usafTextColour\").value,\n          outlineColour: $(\"usafOutlineColour\").value,\n          outline: Number($(\"usafOutline\").value) || 0,\n          style: style,\n          w: 220,\n          h: 80,\n          x: rect.w \/ 2,\n          y: rect.h \/ 2,\n          rotation: 0,\n          opacity: 1\n        };\n\n        measureTextLayer(layer);\n        currentScene().push(layer);\n        state.activeLayer = layer.id;\n\n        renderMock();\n        renderLayers();\n      };\n\n      function activeLayer() {\n        return currentScene().find(function(layer) {\n          return layer.id === state.activeLayer;\n        }) || null;\n      }\n\n      function renderLayers() {\n        if (state.gangProduct) return;\n\n        const wrapper = $(\"usafLayers\");\n        wrapper.innerHTML = \"\";\n\n        if (!currentScene().length) {\n          wrapper.innerHTML = '\u003cdiv class=\"usaf-note\"\u003eNo layers.\u003c\/div\u003e';\n          return;\n        }\n\n        currentScene().forEach(function(layer, index) {\n          const element = document.createElement(\"div\");\n          element.className = \"usaf-layer\" + (state.activeLayer === layer.id ? \" active\" : \"\");\n          element.textContent = (index + 1) + \". \" + (layer.type === \"text\" ? layer.text : layer.name);\n\n          element.onclick = function() {\n            state.activeLayer = layer.id;\n            renderLayers();\n            renderMock();\n          };\n\n          wrapper.appendChild(element);\n        });\n\n        const layer = activeLayer();\n\n        if (layer) {\n          $(\"usafLayerSize\").value = Math.round(layer.w);\n          $(\"usafLayerRotation\").value = Math.round(layer.rotation);\n          $(\"usafLayerOpacity\").value = layer.opacity || 1;\n        }\n      }\n\n      $(\"usafLayerSize\").oninput = function() {\n        const layer = activeLayer();\n        if (!layer) return;\n\n        const oldWidth = layer.w || 1;\n        const ratio = layer.h \/ oldWidth;\n\n        layer.w = Number(this.value);\n        layer.h = layer.w * ratio;\n\n        if (layer.type === \"text\") {\n          layer.fontSize *= layer.w \/ oldWidth;\n          measureTextLayer(layer);\n        }\n\n        renderMock();\n      };\n\n      $(\"usafLayerRotation\").oninput = function() {\n        const layer = activeLayer();\n        if (!layer) return;\n\n        layer.rotation = Number(this.value);\n        renderMock();\n      };\n\n      $(\"usafLayerOpacity\").oninput = function() {\n        const layer = activeLayer();\n        if (!layer) return;\n\n        layer.opacity = Number(this.value);\n        renderMock();\n      };\n\n      $(\"usafCenterLayer\").onclick = function() {\n        const layer = activeLayer();\n        if (!layer) return;\n\n        const rect = printRect();\n        layer.x = rect.w \/ 2;\n        layer.y = rect.h \/ 2;\n\n        renderMock();\n      };\n\n      $(\"usafDuplicateLayer\").onclick = function() {\n        const layer = activeLayer();\n        if (!layer) return;\n\n        const copy = Object.assign({}, layer, {\n          id: makeId(\"layer\"),\n          x: layer.x + 15,\n          y: layer.y + 15\n        });\n\n        currentScene().push(copy);\n        state.activeLayer = copy.id;\n\n        renderMock();\n        renderLayers();\n      };\n\n      $(\"usafDeleteLayer\").onclick = function() {\n        const index = currentScene().findIndex(function(layer) {\n          return layer.id === state.activeLayer;\n        });\n\n        if (index \u003c 0) return;\n\n        currentScene().splice(index, 1);\n        state.activeLayer = null;\n\n        renderMock();\n        renderLayers();\n      };\n\n      $(\"usafMockColour\").oninput = function() {\n        state.mockColour = this.value;\n        renderMock();\n      };\n\n      $(\"usafUseVariant\").onchange = renderMock;\n\n      function mockPointer(event) {\n        const rect = mockCanvas.getBoundingClientRect();\n\n        return {\n          x: (event.clientX - rect.left) * mockCanvas.width \/ rect.width,\n          y: (event.clientY - rect.top) * mockCanvas.height \/ rect.height\n        };\n      }\n\n      function hitMock(point) {\n        const rect = printRect();\n        const localX = point.x - rect.x;\n        const localY = point.y - rect.y;\n\n        for (let index = currentScene().length - 1; index \u003e= 0; index -= 1) {\n          const layer = currentScene()[index];\n\n          if (\n            Math.abs(localX - layer.x) \u003c= layer.w \/ 2 \u0026\u0026\n            Math.abs(localY - layer.y) \u003c= layer.h \/ 2\n          ) {\n            return layer;\n          }\n        }\n\n        return null;\n      }\n\n      mockCanvas.addEventListener(\"pointerdown\", function(event) {\n        if (state.gangProduct) return;\n\n        const point = mockPointer(event);\n        const layer = hitMock(point);\n\n        if (!layer) {\n          state.activeLayer = null;\n          renderLayers();\n          renderMock();\n          return;\n        }\n\n        state.activeLayer = layer.id;\n        state.mockDrag = {\n          x: point.x,\n          y: point.y,\n          layerX: layer.x,\n          layerY: layer.y\n        };\n\n        try {\n          mockCanvas.setPointerCapture(event.pointerId);\n        } catch (error) {}\n\n        renderLayers();\n        renderMock();\n      });\n\n      mockCanvas.addEventListener(\"pointermove\", function(event) {\n        if (!state.mockDrag) return;\n\n        const layer = activeLayer();\n        if (!layer) return;\n\n        const point = mockPointer(event);\n        const rect = printRect();\n\n        layer.x = clamp(state.mockDrag.layerX + point.x - state.mockDrag.x, -100, rect.w + 100);\n        layer.y = clamp(state.mockDrag.layerY + point.y - state.mockDrag.y, -100, rect.h + 100);\n\n        renderMock();\n      });\n\n      mockCanvas.addEventListener(\"pointerup\", function(event) {\n        state.mockDrag = null;\n\n        try {\n          mockCanvas.releasePointerCapture(event.pointerId);\n        } catch (error) {}\n      });\n\n      function mockupLayerSummary() {\n        const all = [];\n\n        Object.keys(state.scenes[state.productType]).forEach(function(area) {\n          state.scenes[state.productType][area].forEach(function(layer, index) {\n            all.push([\n              \"Area: \" + area,\n              \"Layer: \" + (index + 1),\n              \"Type: \" + layer.type,\n              \"Method: \" + (layer.mode === \"embroidery\" ? \"Embroidery\" : \"DTF\"),\n              \"Name\/Text: \" + (layer.type === \"text\" ? layer.text : layer.name),\n              \"X: \" + Math.round(layer.x),\n              \"Y: \" + Math.round(layer.y),\n              \"Width: \" + Math.round(layer.w),\n              \"Height: \" + Math.round(layer.h),\n              \"Rotation: \" + Math.round(layer.rotation || 0),\n              \"Opacity: \" + (layer.opacity || 1)\n            ].join(\" | \"));\n          });\n        });\n\n        return all.length ? all.join(\"\\n\") : \"No custom layers added.\";\n      }\n\n      function mockupOrderProperties() {\n        const summary = mockupLayerSummary();\n        const layerCount = summary === \"No custom layers added.\" ? 0 : summary.split(\"\\n\").length;\n\n        return {\n          \"Custom Design\": \"Yes\",\n          \"Design Type\": \"Personalised Product\",\n          \"Print Method\": state.method === \"embroidery\" ? \"Embroidery\" : \"DTF\",\n          \"Product Type\": state.productType,\n          \"Selected Area\": state.area,\n          \"Variant\": state.variant ? state.variant.title : \"\",\n          \"Variant ID\": state.variant ? String(state.variant.id) : \"\",\n          \"Customer Note\": $(\"usafCustomerNote\").value || \"\",\n          \"Layer Count\": String(layerCount),\n          \"Design Layers\": summary.substring(0, 900)\n        };\n      }\n\n      async function addMockupToCart(checkout) {\n        if (!state.product || !state.variant) {\n          $(\"usafMockCartStatus\").className = \"usaf-note error\";\n          $(\"usafMockCartStatus\").textContent = \"Shopify product or variant was not detected.\";\n          return;\n        }\n\n        if (!state.variant.available) {\n          $(\"usafMockCartStatus\").className = \"usaf-note error\";\n          $(\"usafMockCartStatus\").textContent = \"This variant is sold out.\";\n          return;\n        }\n\n        const quantity = Math.max(1, Number($(\"usafMockQty\").value || 1));\n\n        $(\"usafMockCartStatus\").className = \"usaf-note\";\n        $(\"usafMockCartStatus\").textContent = \"Adding custom product to cart…\";\n\n        try {\n          const response = await fetch(rootPath() + \"cart\/add.js\", {\n            method: \"POST\",\n            headers: {\n              \"Content-Type\": \"application\/json\",\n              \"Accept\": \"application\/json\"\n            },\n            body: JSON.stringify({\n              items: [\n                {\n                  id: state.variant.id,\n                  quantity: quantity,\n                  properties: mockupOrderProperties()\n                }\n              ]\n            })\n          });\n\n          let result = {};\n\n          try {\n            result = await response.json();\n          } catch (error) {}\n\n          if (!response.ok) {\n            throw new Error(result.description || result.message || \"Could not add custom product to cart.\");\n          }\n\n          $(\"usafMockCartStatus\").className = \"usaf-note good\";\n          $(\"usafMockCartStatus\").textContent = \"✓ Custom product added.\";\n\n          document.dispatchEvent(new CustomEvent(\"cart:refresh\"));\n\n          window.location.href = checkout ? rootPath() + \"checkout\" : rootPath() + \"cart\";\n        } catch (error) {\n          $(\"usafMockCartStatus\").className = \"usaf-note error\";\n          $(\"usafMockCartStatus\").textContent = error.message;\n        }\n      }\n\n      $(\"usafMockAddCart\").onclick = function() {\n        addMockupToCart(false);\n      };\n\n      $(\"usafMockBuyNow\").onclick = function() {\n        addMockupToCart(true);\n      };\n\n      async function loadLibraryToHalf(item) {\n        state.halfResult = null;\n        $(\"usafHalfStatus\").className = \"usaf-note\";\n        $(\"usafHalfStatus\").textContent = \"Loading artwork…\";\n\n        let image = await loadImage(item.imageUrl, true);\n        if (!image) image = await loadImage(item.imageUrl, false);\n\n        if (!image) {\n          $(\"usafHalfStatus\").className = \"usaf-note error\";\n          $(\"usafHalfStatus\").textContent = \"Could not load artwork.\";\n          return;\n        }\n\n        state.halfImage = image;\n\n        try {\n          state.halfSourceCanvas = imageToCanvas(image);\n          state.halfSourceCanvas.getContext(\"2d\", { willReadFrequently: true }).getImageData(0, 0, 1, 1);\n          drawHalfSourcePreview();\n          $(\"usafHalfStatus\").className = \"usaf-note good\";\n          $(\"usafHalfStatus\").textContent = item.name + \" loaded.\";\n        } catch (error) {\n          state.halfSourceCanvas = null;\n          drawHalfImagePreview(image);\n          $(\"usafHalfStatus\").className = \"usaf-note error\";\n          $(\"usafHalfStatus\").textContent = \"This image cannot be processed by browser CORS. Upload it locally.\";\n        }\n      }\n\n      $(\"usafLibraryHalf\").onclick = function() {\n        if (!state.selected) return;\n\n        loadLibraryToHalf(state.selected);\n        openTab(\"halftone\");\n      };\n\n      $(\"usafCustomerHalf\").onclick = function() {\n        if (!state.customerImage) return;\n\n        state.halfImage = state.customerImage;\n        state.halfSourceCanvas = imageToCanvas(state.customerImage);\n        state.halfResult = null;\n\n        drawHalfSourcePreview();\n        openTab(\"halftone\");\n      };\n\n      $(\"usafHalfUpload\").onchange = async function(event) {\n        const file = event.target.files \u0026\u0026 event.target.files[0];\n        if (!file) return;\n\n        const image = await loadImage(await readFile(file), false);\n        if (!image) return;\n\n        state.halfImage = image;\n        state.halfSourceCanvas = imageToCanvas(image);\n        state.halfResult = null;\n\n        drawHalfSourcePreview();\n        event.target.value = \"\";\n      };\n\n      function drawHalfImagePreview(image) {\n        halfCanvas.width = Math.min(1000, imageWidth(image));\n        halfCanvas.height = Math.round(halfCanvas.width * imageHeight(image) \/ imageWidth(image));\n\n        hctx.clearRect(0, 0, halfCanvas.width, halfCanvas.height);\n        hctx.drawImage(image, 0, 0, halfCanvas.width, halfCanvas.height);\n      }\n\n      function drawHalfSourcePreview() {\n        if (!state.halfSourceCanvas) return;\n\n        const source = state.halfSourceCanvas;\n        halfCanvas.width = Math.min(1000, source.width);\n        halfCanvas.height = Math.round(halfCanvas.width * source.height \/ source.width);\n\n        hctx.clearRect(0, 0, halfCanvas.width, halfCanvas.height);\n        hctx.drawImage(source, 0, 0, halfCanvas.width, halfCanvas.height);\n      }\n\n      function halftoneLuma(r, g, b, brightness, contrast) {\n        let value = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n        value += brightness * 2.55;\n\n        const factor = (259 * (contrast + 255)) \/ (255 * (259 - contrast));\n        value = factor * (value - 128) + 128;\n\n        return clamp(value, 0, 255);\n      }\n\n      function makeHalftone(targetWidth) {\n        if (!state.halfSourceCanvas) return null;\n\n        const source = state.halfSourceCanvas;\n        const ratio = source.height \/ source.width;\n        const width = Math.max(100, Math.min(6600, Math.round(targetWidth)));\n        const height = Math.max(100, Math.round(width * ratio));\n\n        const sample = document.createElement(\"canvas\");\n        sample.width = width;\n        sample.height = height;\n\n        const sctx = sample.getContext(\"2d\", { willReadFrequently: true });\n        sctx.drawImage(source, 0, 0, width, height);\n\n        let pixels;\n\n        try {\n          pixels = sctx.getImageData(0, 0, width, height).data;\n        } catch (error) {\n          return null;\n        }\n\n        const output = document.createElement(\"canvas\");\n        output.width = width;\n        output.height = height;\n\n        const out = output.getContext(\"2d\");\n        const cell = Number($(\"usafHalfCell\").value) || 12;\n        const strength = (Number($(\"usafHalfStrength\").value) || 100) \/ 100;\n        const brightness = Number($(\"usafHalfBrightness\").value) || 0;\n        const contrast = Number($(\"usafHalfContrast\").value) || 0;\n        const shape = $(\"usafHalfShape\").value;\n\n        out.clearRect(0, 0, width, height);\n        out.fillStyle = $(\"usafHalfColour\").value;\n\n        for (let y = cell \/ 2; y \u003c height; y += cell) {\n          for (let x = cell \/ 2; x \u003c width; x += cell) {\n            const px = Math.min(width - 1, Math.floor(x));\n            const py = Math.min(height - 1, Math.floor(y));\n            const index = (py * width + px) * 4;\n            const alpha = pixels[index + 3] \/ 255;\n\n            if (alpha \u003c= 0.01) continue;\n\n            const luma = halftoneLuma(\n              pixels[index],\n              pixels[index + 1],\n              pixels[index + 2],\n              brightness,\n              contrast\n            );\n\n            const darkness = (1 - luma \/ 255) * alpha;\n            const dot = Math.min(cell * 1.25, cell * Math.sqrt(darkness) * strength);\n\n            if (dot \u003c= 0.3) continue;\n\n            if (shape === \"square\") {\n              out.fillRect(x - dot \/ 2, y - dot \/ 2, dot, dot);\n            } else if (shape === \"diamond\") {\n              out.save();\n              out.translate(x, y);\n              out.rotate(Math.PI \/ 4);\n              out.fillRect(-dot \/ 2, -dot \/ 2, dot, dot);\n              out.restore();\n            } else {\n              out.beginPath();\n              out.arc(x, y, dot \/ 2, 0, Math.PI * 2);\n              out.fill();\n            }\n          }\n        }\n\n        return output;\n      }\n\n      $(\"usafApplyHalf\").onclick = function() {\n        if (!state.halfSourceCanvas) {\n          $(\"usafHalfStatus\").className = \"usaf-note error\";\n          $(\"usafHalfStatus\").textContent = \"Upload the original image locally if processing is blocked.\";\n          return;\n        }\n\n        state.halfResult = makeHalftone((Number($(\"usafHalfWidth\").value) || 11) * DPI);\n        if (!state.halfResult) return;\n\n        halfCanvas.width = state.halfResult.width;\n        halfCanvas.height = state.halfResult.height;\n\n        hctx.clearRect(0, 0, halfCanvas.width, halfCanvas.height);\n        hctx.drawImage(state.halfResult, 0, 0);\n\n        $(\"usafHalfStatus\").className = \"usaf-note good\";\n        $(\"usafHalfStatus\").textContent = \"Halftone created.\";\n      };\n\n      $(\"usafResetHalf\").onclick = function() {\n        state.halfResult = null;\n        drawHalfSourcePreview();\n      };\n\n      $(\"usafDownloadHalf\").onclick = async function() {\n        if (!state.halfResult) return;\n\n        await downloadCanvas(\n          state.halfResult,\n          \"USASTARFASHION-halftone.png\",\n          \"usafHalfStatus\"\n        );\n      };\n\n      $(\"usafHalfToMock\").onclick = async function() {\n        if (state.gangProduct || !state.halfResult) return;\n\n        const image = await loadImage(state.halfResult.toDataURL(\"image\/png\"), false);\n\n        addImageLayer(\n          image,\n          \"Halftone Artwork\",\n          state.selected ? state.selected.id : \"\"\n        );\n\n        openTab(\"studio\");\n      };\n\n      function rotatedWidth(piece) {\n        const angle = (piece.rotation % 360 + 360) % 360;\n        return angle === 90 || angle === 270 ? piece.hCm : piece.wCm;\n      }\n\n      function rotatedHeight(piece) {\n        const angle = (piece.rotation % 360 + 360) % 360;\n        return angle === 90 || angle === 270 ? piece.wCm : piece.hCm;\n      }\n\n      function addGangPiece(image, name, widthCm, designId) {\n        const ratio = imageWidth(image) \/ imageHeight(image);\n        const width = clamp(widthCm, 1, GANG_WIDTH_CM);\n\n        state.gangPieces.push({\n          id: makeId(\"gang\"),\n          image: image,\n          name: name,\n          designId: designId || \"\",\n          sourceWidth: imageWidth(image),\n          sourceHeight: imageHeight(image),\n          wCm: width,\n          hCm: width \/ ratio,\n          rotation: 0,\n          xCm: 0,\n          yCm: 0\n        });\n\n        renderGangPieces();\n        autoNest();\n        updateGangButtons();\n      }\n\n      async function addLibraryToGang(item) {\n        let image = await loadImage(item.imageUrl, true);\n        if (!image) image = await loadImage(item.imageUrl, false);\n        if (!image) return;\n\n        addGangPiece(image, item.name, 20, item.id);\n      }\n\n      $(\"usafGangLibrary\").onclick = async function() {\n        if (!state.selected) return;\n        await addLibraryToGang(state.selected);\n      };\n\n      $(\"usafGangUploadBtn\").onclick = function() {\n        $(\"usafGangUpload\").click();\n      };\n\n      $(\"usafGangUpload\").onchange = async function(event) {\n        const files = Array.from(event.target.files || []);\n\n        for (const file of files) {\n          const image = await loadImage(await readFile(file), false);\n\n          if (image) {\n            addGangPiece(image, file.name, 15, \"\");\n          }\n        }\n\n        event.target.value = \"\";\n      };\n\n      $(\"usafHalfToGang\").onclick = function() {\n        if (!state.halfResult) return;\n\n        addGangPiece(\n          cloneCanvas(state.halfResult),\n          \"Halftone Artwork\",\n          Math.min(GANG_WIDTH_CM, (Number($(\"usafHalfWidth\").value) || 11) * 2.54),\n          state.selected ? state.selected.id : \"\"\n        );\n\n        openTab(\"gang\");\n      };\n\n      $(\"usafMockToGang\").onclick = function() {\n        if (state.gangProduct || !currentScene().length) return;\n\n        const rect = printRect();\n        const output = document.createElement(\"canvas\");\n        output.width = 2200;\n        output.height = Math.round(output.width * rect.h \/ rect.w);\n\n        const out = output.getContext(\"2d\");\n        const sx = output.width \/ rect.w;\n        const sy = output.height \/ rect.h;\n\n        currentScene().forEach(function(layer) {\n          out.save();\n          out.translate(layer.x * sx, layer.y * sy);\n          out.rotate(layer.rotation * Math.PI \/ 180);\n          out.globalAlpha = layer.opacity || 1;\n\n          if (layer.type === \"image\") {\n            out.drawImage(\n              layer.image,\n              -layer.w * sx \/ 2,\n              -layer.h * sy \/ 2,\n              layer.w * sx,\n              layer.h * sy\n            );\n          } else {\n            out.font = \"bold \" + layer.fontSize * sx + \"px \" + layer.font;\n            out.textAlign = \"center\";\n            out.textBaseline = \"middle\";\n\n            if (layer.outline \u003e 0) {\n              out.lineWidth = layer.outline * sx * 2;\n              out.strokeStyle = layer.outlineColour;\n              out.strokeText(layer.text, 0, 0);\n            }\n\n            out.fillStyle = layer.colour;\n            out.fillText(layer.text, 0, 0);\n          }\n\n          out.restore();\n        });\n\n        addGangPiece(output, state.productType + \" \" + state.area, 20, \"mockup-print-area\");\n        openTab(\"gang\");\n      };\n\n      function renderGangPieces() {\n        const wrapper = $(\"usafGangPieces\");\n        wrapper.innerHTML = \"\";\n\n        if (!state.gangPieces.length) {\n          wrapper.innerHTML = '\u003cdiv class=\"usaf-note\"\u003eNo artwork added.\u003c\/div\u003e';\n          return;\n        }\n\n        state.gangPieces.forEach(function(piece) {\n          const element = document.createElement(\"div\");\n          element.className = \"usaf-gang-piece\";\n\n          element.innerHTML =\n            \"\u003cstrong\u003e\" + esc(piece.name) + \"\u003c\/strong\u003e\" +\n            \"\u003csmall style='display:block;margin-top:3px;color:var(--muted);font-size:8px'\u003eArtwork\u003c\/small\u003e\" +\n            '\u003cdiv class=\"usaf-two\" style=\"margin-top:5px\"\u003e' +\n              '\u003cinput data-width class=\"usaf-input\" type=\"number\" min=\"1\" max=\"56\" step=\".1\" value=\"' + piece.wCm.toFixed(1) + '\"\u003e' +\n              '\u003cbutton data-remove class=\"usaf-btn danger\" type=\"button\"\u003eRemove\u003c\/button\u003e' +\n            \"\u003c\/div\u003e\";\n\n          element.querySelector(\"[data-width]\").onchange = function() {\n            const ratio = piece.hCm \/ piece.wCm;\n            piece.wCm = clamp(Number(this.value) || 1, 1, GANG_WIDTH_CM);\n            piece.hCm = piece.wCm * ratio;\n            autoNest();\n          };\n\n          element.querySelector(\"[data-remove]\").onclick = function() {\n            state.gangPieces = state.gangPieces.filter(function(item) {\n              return item.id !== piece.id;\n            });\n\n            if (state.gangSelected === piece.id) {\n              state.gangSelected = null;\n            }\n\n            renderGangPieces();\n            autoNest();\n            updateGangButtons();\n          };\n\n          wrapper.appendChild(element);\n        });\n      }\n\n      function autoNest() {\n        const spacing = Math.max(0, Number($(\"usafGangSpacing\").value) || 0);\n        const pieces = state.gangPieces.slice().sort(function(a, b) {\n          return b.wCm * b.hCm - a.wCm * a.hCm;\n        });\n\n        let x = 0;\n        let y = 0;\n        let rowHeight = 0;\n\n        pieces.forEach(function(piece) {\n          const width = rotatedWidth(piece);\n          const height = rotatedHeight(piece);\n\n          if (x \u003e 0 \u0026\u0026 x + width \u003e GANG_WIDTH_CM) {\n            x = 0;\n            y += rowHeight + spacing;\n            rowHeight = 0;\n          }\n\n          piece.xCm = x;\n          piece.yCm = y;\n\n          x += width + spacing;\n          rowHeight = Math.max(rowHeight, height);\n        });\n\n        renderGang();\n      }\n\n      $(\"usafAutoNest\").onclick = autoNest;\n      $(\"usafGangSpacing\").onchange = autoNest;\n\n      $(\"usafGangBackground\").onchange = function() {\n        state.gangBackground = this.value;\n        renderGang();\n      };\n\n      $(\"usafGangSnap\").onchange = function() {\n        state.gangSnap = this.checked;\n      };\n\n      function gangLength() {\n        if (!state.gangPieces.length) return 0;\n\n        return Math.max.apply(\n          null,\n          state.gangPieces.map(function(piece) {\n            return piece.yCm + rotatedHeight(piece);\n          })\n        );\n      }\n\n      function gangCharged() {\n        const actual = gangLength();\n\n        if (actual \u003c= GANG_MIN_CM) return GANG_MIN_CM;\n\n        return Math.ceil(actual \/ GANG_STEP_CM) * GANG_STEP_CM;\n      }\n\n      function gangCartQuantity() {\n        return Math.max(10, Math.round(gangCharged() \/ GANG_STEP_CM));\n      }\n\n      function gangPreviewScale() {\n        return Math.min(12, 3500 \/ Math.max(100, gangLength()));\n      }\n\n      function drawBackground(context, width, height, preview) {\n        if (state.gangBackground === \"white\") {\n          context.fillStyle = \"#fff\";\n          context.fillRect(0, 0, width, height);\n          return;\n        }\n\n        if (state.gangBackground === \"black\") {\n          context.fillStyle = \"#000\";\n          context.fillRect(0, 0, width, height);\n          return;\n        }\n\n        if (!preview) return;\n\n        const size = 16;\n\n        for (let y = 0; y \u003c height; y += size) {\n          for (let x = 0; x \u003c width; x += size) {\n            context.fillStyle = ((x \/ size + y \/ size) % 2) ? \"#e1e4e8\" : \"#fff\";\n            context.fillRect(x, y, size, size);\n          }\n        }\n      }\n\n      function drawGangPiece(context, piece, pxPerCm, selection) {\n        const x = piece.xCm * pxPerCm;\n        const y = piece.yCm * pxPerCm;\n        const sourceW = piece.wCm * pxPerCm;\n        const sourceH = piece.hCm * pxPerCm;\n        const displayW = rotatedWidth(piece) * pxPerCm;\n        const displayH = rotatedHeight(piece) * pxPerCm;\n\n        context.save();\n        context.translate(x + displayW \/ 2, y + displayH \/ 2);\n        context.rotate(piece.rotation * Math.PI \/ 180);\n        context.drawImage(piece.image, -sourceW \/ 2, -sourceH \/ 2, sourceW, sourceH);\n        context.restore();\n\n        if (selection \u0026\u0026 piece.id === state.gangSelected) {\n          context.save();\n          context.strokeStyle = \"#ffd400\";\n          context.lineWidth = 2;\n          context.setLineDash([7, 5]);\n          context.strokeRect(x, y, displayW, displayH);\n          context.restore();\n        }\n      }\n\n      function renderGang() {\n        const scale = gangPreviewScale();\n        const length = Math.max(100, gangLength());\n\n        gangCanvas.width = Math.round(GANG_WIDTH_CM * scale);\n        gangCanvas.height = Math.max(500, Math.round(length * scale));\n\n        gctx.clearRect(0, 0, gangCanvas.width, gangCanvas.height);\n\n        drawBackground(gctx, gangCanvas.width, gangCanvas.height, true);\n\n        state.gangPieces.forEach(function(piece) {\n          drawGangPiece(gctx, piece, scale, true);\n        });\n\n        $(\"usafGangActual\").textContent = gangLength().toFixed(1) + \" cm\";\n        $(\"usafGangCharged\").textContent = gangCharged() + \" cm\";\n        $(\"usafGangCount\").textContent = state.gangPieces.length;\n        $(\"usafGangUnits\").textContent = gangCartQuantity();\n        $(\"usafGangUnitPrice\").textContent = \"Download only\";\n        $(\"usafGangPrice\").textContent = \"Download only\";\n\n        updateSelectedGang();\n        updateGangButtons();\n      }\n\n      function selectedGang() {\n        return state.gangPieces.find(function(piece) {\n          return piece.id === state.gangSelected;\n        }) || null;\n      }\n\n      function updateSelectedGang() {\n        const piece = selectedGang();\n\n        if (!piece) {\n          $(\"usafGangSelected\").textContent = \"Tap artwork on the sheet.\";\n          $(\"usafGangSelectedWidth\").disabled = true;\n          $(\"usafGangWidthRange\").disabled = true;\n          $(\"usafGangQuality\").className = \"usaf-quality good\";\n          $(\"usafGangQuality\").textContent = \"Resolution appears here.\";\n          return;\n        }\n\n        $(\"usafGangSelectedWidth\").disabled = false;\n        $(\"usafGangWidthRange\").disabled = false;\n        $(\"usafGangSelectedWidth\").value = piece.wCm.toFixed(1);\n        $(\"usafGangWidthRange\").value = piece.wCm;\n\n        $(\"usafGangSelected\").innerHTML =\n          \"\u003cstrong\u003e\" + esc(piece.name) + \"\u003c\/strong\u003e\u003cbr\u003e\" +\n          piece.wCm.toFixed(1) + \" × \" + piece.hCm.toFixed(1) + \" cm\";\n\n        const effectiveDpi = piece.sourceWidth \/ (piece.wCm \/ 2.54);\n\n        if (effectiveDpi \u003e= 250) {\n          $(\"usafGangQuality\").className = \"usaf-quality good\";\n          $(\"usafGangQuality\").textContent = Math.round(effectiveDpi) + \" DPI — good resolution\";\n        } else if (effectiveDpi \u003e= 150) {\n          $(\"usafGangQuality\").className = \"usaf-quality warn\";\n          $(\"usafGangQuality\").textContent = Math.round(effectiveDpi) + \" DPI — moderate resolution\";\n        } else {\n          $(\"usafGangQuality\").className = \"usaf-quality bad\";\n          $(\"usafGangQuality\").textContent = Math.round(effectiveDpi) + \" DPI — low resolution\";\n        }\n      }\n\n      function setSelectedGangWidth(value) {\n        const piece = selectedGang();\n        if (!piece) return;\n\n        const ratio = piece.hCm \/ piece.wCm;\n\n        piece.wCm = clamp(Number(value) || 1, 1, GANG_WIDTH_CM);\n        piece.hCm = piece.wCm * ratio;\n        piece.xCm = clamp(piece.xCm, 0, Math.max(0, GANG_WIDTH_CM - rotatedWidth(piece)));\n\n        renderGang();\n      }\n\n      $(\"usafGangSelectedWidth\").oninput = function() {\n        setSelectedGangWidth(this.value);\n      };\n\n      $(\"usafGangWidthRange\").oninput = function() {\n        setSelectedGangWidth(this.value);\n      };\n\n      $(\"usafGangRotate\").onclick = function() {\n        const piece = selectedGang();\n        if (!piece) return;\n\n        piece.rotation = (piece.rotation + 90) % 360;\n        piece.xCm = clamp(piece.xCm, 0, Math.max(0, GANG_WIDTH_CM - rotatedWidth(piece)));\n\n        renderGang();\n      };\n\n      $(\"usafGangDuplicate\").onclick = function() {\n        const piece = selectedGang();\n        if (!piece) return;\n\n        const copy = Object.assign({}, piece, {\n          id: makeId(\"gang\"),\n          xCm: piece.xCm + 1,\n          yCm: piece.yCm + 1\n        });\n\n        state.gangPieces.push(copy);\n        state.gangSelected = copy.id;\n\n        renderGangPieces();\n        renderGang();\n        updateGangButtons();\n      };\n\n      $(\"usafGangDelete\").onclick = function() {\n        if (!state.gangSelected) return;\n\n        state.gangPieces = state.gangPieces.filter(function(piece) {\n          return piece.id !== state.gangSelected;\n        });\n\n        state.gangSelected = null;\n\n        renderGangPieces();\n        renderGang();\n        updateGangButtons();\n      };\n\n      function gangPointer(event) {\n        const rect = gangCanvas.getBoundingClientRect();\n\n        return {\n          x: (event.clientX - rect.left) * gangCanvas.width \/ rect.width,\n          y: (event.clientY - rect.top) * gangCanvas.height \/ rect.height\n        };\n      }\n\n      function hitGang(x, y) {\n        const scale = gangPreviewScale();\n\n        for (let index = state.gangPieces.length - 1; index \u003e= 0; index -= 1) {\n          const piece = state.gangPieces[index];\n          const px = piece.xCm * scale;\n          const py = piece.yCm * scale;\n          const width = rotatedWidth(piece) * scale;\n          const height = rotatedHeight(piece) * scale;\n\n          if (x \u003e= px \u0026\u0026 x \u003c= px + width \u0026\u0026 y \u003e= py \u0026\u0026 y \u003c= py + height) {\n            return piece;\n          }\n        }\n\n        return null;\n      }\n\n      gangCanvas.addEventListener(\"pointerdown\", function(event) {\n        const point = gangPointer(event);\n        const piece = hitGang(point.x, point.y);\n\n        if (!piece) {\n          state.gangSelected = null;\n          renderGang();\n          return;\n        }\n\n        state.gangSelected = piece.id;\n\n        const scale = gangPreviewScale();\n\n        state.gangDrag = {\n          x: point.x \/ scale - piece.xCm,\n          y: point.y \/ scale - piece.yCm\n        };\n\n        try {\n          gangCanvas.setPointerCapture(event.pointerId);\n        } catch (error) {}\n\n        renderGang();\n      });\n\n      gangCanvas.addEventListener(\"pointermove\", function(event) {\n        if (!state.gangDrag) return;\n\n        const piece = selectedGang();\n        if (!piece) return;\n\n        const point = gangPointer(event);\n        const scale = gangPreviewScale();\n\n        let x = point.x \/ scale - state.gangDrag.x;\n        let y = point.y \/ scale - state.gangDrag.y;\n\n        x = clamp(x, 0, Math.max(0, GANG_WIDTH_CM - rotatedWidth(piece)));\n        y = Math.max(0, y);\n\n        if (state.gangSnap) {\n          x = Math.round(x * 10) \/ 10;\n          y = Math.round(y * 10) \/ 10;\n        }\n\n        piece.xCm = x;\n        piece.yCm = y;\n\n        renderGang();\n      });\n\n      gangCanvas.addEventListener(\"pointerup\", function(event) {\n        state.gangDrag = null;\n\n        try {\n          gangCanvas.releasePointerCapture(event.pointerId);\n        } catch (error) {}\n      });\n\n      gangCanvas.addEventListener(\"pointercancel\", function() {\n        state.gangDrag = null;\n      });\n\n      function gangDesignReferences() {\n        return Array.from(new Set(\n          state.gangPieces\n            .map(function(piece){ return piece.designId; })\n            .filter(Boolean)\n        ));\n      }\n\n      function validEmail(value) {\n        return \/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/.test(String(value || \"\").trim());\n      }\n\n      function gangDownloadFormValues() {\n        return {\n          name: $(\"usafGangCustomerName\") ? $(\"usafGangCustomerName\").value.trim() : \"\",\n          email: $(\"usafGangCustomerEmail\") ? $(\"usafGangCustomerEmail\").value.trim() : \"\",\n          phone: $(\"usafGangCustomerPhone\") ? $(\"usafGangCustomerPhone\").value.trim() : \"\",\n          message: $(\"usafGangCustomerMessage\") ? $(\"usafGangCustomerMessage\").value.trim() : \"\"\n        };\n      }\n\n      function gangDownloadAllowed() {\n        const form = gangDownloadFormValues();\n        const terms = $(\"usafGangTerms\");\n\n        return !!(\n          state.customer.loggedIn \u0026\u0026\n          state.gangPieces.length \u0026\u0026\n          terms \u0026\u0026\n          terms.checked \u0026\u0026\n          form.name.length \u003e= 2 \u0026\u0026\n          validEmail(form.email) \u0026\u0026\n          form.phone.length \u003e= 7\n        );\n      }\n\n      function updateGangButtons() {\n        const loginStatus = $(\"usafGangLoginStatus\");\n        const loginLink = $(\"usafGangLoginLink\");\n        const formBox = $(\"usafGangDownloadForm\");\n        const status = $(\"usafGangCartStatus\");\n        const productionButton = $(\"usafGangProduction\");\n\n        if (!loginStatus || !loginLink || !formBox) return;\n\n        loginLink.href = loginUrl();\n\n        if (!state.customer.loggedIn) {\n          loginStatus.className = \"usaf-note error\";\n          loginStatus.textContent = \"Please login with your Shopify customer account to download.\";\n          loginLink.classList.remove(\"usaf-hidden\");\n          formBox.classList.add(\"usaf-hidden\");\n\n          if (productionButton) productionButton.disabled = true;\n          return;\n        }\n\n        loginStatus.className = \"usaf-note good\";\n        loginStatus.textContent = \"You are logged in. Complete the form to unlock download.\";\n        loginLink.classList.add(\"usaf-hidden\");\n        formBox.classList.remove(\"usaf-hidden\");\n\n        const allowed = gangDownloadAllowed();\n\n        if (productionButton) productionButton.disabled = !allowed;\n\n        if (!status) return;\n\n        if (!state.gangPieces.length) {\n          status.className = \"usaf-note\";\n          status.textContent = \"Add artwork before downloading.\";\n          return;\n        }\n\n        const form = gangDownloadFormValues();\n\n        if (!form.name || !validEmail(form.email) || form.phone.length \u003c 7) {\n          status.className = \"usaf-note error\";\n          status.textContent = \"Name, valid email, and phone are required.\";\n          return;\n        }\n\n        if (!$(\"usafGangTerms\").checked) {\n          status.className = \"usaf-note\";\n          status.textContent = \"Approve the layout to unlock download.\";\n          return;\n        }\n\n        status.className = \"usaf-note good\";\n        status.textContent = \"Download unlocked.\";\n      }\n\n      function goToGangDownloadSection() {\n        openTab(\"gang\");\n\n        setTimeout(function() {\n          const panel = $(\"usafDownloadAccessPanel\");\n\n          if (panel) {\n            panel.scrollIntoView({\n              behavior: \"smooth\",\n              block: \"center\"\n            });\n          }\n        }, 150);\n      }\n\n      function requireGangDownloadAccess() {\n        updateGangButtons();\n\n        if (!state.customer.loggedIn) {\n          window.location.href = loginUrl();\n          return false;\n        }\n\n        if (!gangDownloadAllowed()) {\n          alert(\"Please add artwork, complete name\/email\/phone, and approve the layout before downloading.\");\n          goToGangDownloadSection();\n          return false;\n        }\n\n        return true;\n      }\n\n      function gangDownloadLogProperties(type, downloadId, fileName, fileBase64) {\n        const form = gangDownloadFormValues();\n\n        return {\n          type: type,\n          downloadId: downloadId,\n          customerId: state.customer.id || \"\",\n          customerName: form.name,\n          customerEmail: form.email,\n          customerPhone: form.phone,\n          customerMessage: form.message,\n          filmWidth: \"56 cm\",\n          artworkLength: gangLength().toFixed(1) + \" cm\",\n          downloadLength: gangCharged() + \" cm\",\n          artworkCount: String(state.gangPieces.length),\n          downloadedAt: new Date().toISOString(),\n          pageUrl: window.location.href,\n          userAgent: navigator.userAgent,\n          designReferences: gangDesignReferences().join(\",\"),\n          background: state.gangBackground,\n          fileName: fileName,\n          fileBase64: fileBase64 || \"\"\n        };\n      }\n\n      async function sendGangDownloadRecord(type, downloadId, fileName, fileBase64) {\n        const record = gangDownloadLogProperties(type, downloadId, fileName, fileBase64);\n\n        localStorage.setItem(\n          \"USAF_LAST_GANG_DOWNLOAD\",\n          JSON.stringify(Object.assign({}, record, { fileBase64: fileBase64 ? \"[included]\" : \"\" }))\n        );\n\n        await fetch(GOOGLE_SHEET_LOG_URL, {\n          method: \"POST\",\n          mode: \"no-cors\",\n          headers: {\n            \"Content-Type\": \"text\/plain;charset=utf-8\"\n          },\n          body: JSON.stringify(record)\n        });\n      }\n\n      [\"usafGangCustomerName\", \"usafGangCustomerEmail\", \"usafGangCustomerPhone\", \"usafGangCustomerMessage\"].forEach(function(id) {\n        const input = $(id);\n\n        if (input) {\n          input.addEventListener(\"input\", updateGangButtons);\n        }\n      });\n\n      if ($(\"usafGangTerms\")) {\n        $(\"usafGangTerms\").addEventListener(\"change\", updateGangButtons);\n      }\n\n      function createGangExport(production) {\n        const length = Math.max(100, gangLength());\n        const pxPerCm = production ? PRINT_PX_CM : Math.min(12, 2200 \/ length);\n\n        const output = document.createElement(\"canvas\");\n        output.width = Math.round(GANG_WIDTH_CM * pxPerCm);\n        output.height = Math.round(length * pxPerCm);\n\n        const out = output.getContext(\"2d\");\n\n        drawBackground(out, output.width, output.height, false);\n\n        state.gangPieces.forEach(function(piece) {\n          drawGangPiece(out, piece, pxPerCm, false);\n        });\n\n        return output;\n      }\n\n      $(\"usafGangProduction\").onclick = async function() {\n        if (!requireGangDownloadAccess()) return;\n        if (!state.gangPieces.length) return;\n\n        const outputHeight = Math.round(Math.max(100, gangLength()) * PRINT_PX_CM);\n        const outputWidth = Math.round(GANG_WIDTH_CM * PRINT_PX_CM);\n        const megapixels = outputWidth * outputHeight;\n\n        if (megapixels \u003e 90000000) {\n          $(\"usafGangExportStatus\").className = \"usaf-note error\";\n          $(\"usafGangExportStatus\").textContent =\n            \"This 300 DPI file is too large for mobile browser export. Use desktop Chrome or split into shorter sheets.\";\n          return;\n        }\n\n        const downloadId = createDownloadId();\n        const fileName = \"USASTARFASHION-\" + downloadId + \"-56cm-gang-300dpi.png\";\n\n        $(\"usafGangCartStatus\").className = \"usaf-note\";\n        $(\"usafGangCartStatus\").textContent = \"Creating 300 DPI file…\";\n\n        try {\n          const exportCanvas = createGangExport(true);\n          let fileBase64 = canvasToDataUrlSafe(exportCanvas);\n\n          if (fileBase64.length \u003e MAX_UPLOAD_BASE64_CHARS) {\n            fileBase64 = \"\";\n            $(\"usafGangCartStatus\").textContent =\n              \"File is large. Downloading to device and saving customer record without upload attachment…\";\n          } else {\n            $(\"usafGangCartStatus\").textContent =\n              \"Saving file to Google Drive, Google Sheet, and emailing customer…\";\n          }\n\n          await sendGangDownloadRecord(\"production-300dpi\", downloadId, fileName, fileBase64);\n\n          await downloadCanvas(\n            exportCanvas,\n            fileName,\n            \"usafGangExportStatus\"\n          );\n\n          $(\"usafGangCartStatus\").className = \"usaf-note good\";\n          $(\"usafGangCartStatus\").textContent =\n            fileBase64\n              ? \"Download started. File sent to Google Drive, Sheet, and email.\"\n              : \"Download started. Record saved and email sent without attachment because file was too large.\";\n        } catch (error) {\n          $(\"usafGangCartStatus\").className = \"usaf-note error\";\n          $(\"usafGangCartStatus\").textContent =\n            \"Download failed or logging failed. Check Apps Script deployment and browser console.\";\n        }\n      };\n\n      window.addEventListener(\"resize\", function() {\n        if ($(\"usafSection_gang\").classList.contains(\"active\")) {\n          renderGang();\n        }\n      });\n\n      detectGangFromPage();\n      renderAreas();\n      renderMock();\n      renderLayers();\n      renderGangPieces();\n      renderGang();\n      loadLibrary();\n      loadShopifyProduct();\n      verifyCustomerLogin();\n\n      if (location.hash === \"#usaf-download\") {\n        setTimeout(goToGangDownloadSection, 700);\n      }\n    })();\n  \u003c\/script\u003e\n\u003c\/div\u003e","brand":"USASTARFASHION","offers":[{"title":"Black \/ Custom DTF Printing","offer_id":58518888022347,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Black \/ Custom Embroidery","offer_id":58518888055115,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Green \/ Custom DTF Printing","offer_id":58518888087883,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Green \/ Custom Embroidery","offer_id":58518888120651,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Gray \/ Custom DTF Printing","offer_id":58518888153419,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Gray \/ Custom Embroidery","offer_id":58518888186187,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Navy \/ Custom DTF Printing","offer_id":58518888218955,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Navy \/ Custom Embroidery","offer_id":58518888251723,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"White \/ Custom DTF Printing","offer_id":58518888284491,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"White \/ Custom Embroidery","offer_id":58518888317259,"sku":null,"price":24.0,"currency_code":"GBP","in_stock":true},{"title":"Blue \/ Custom DTF Printing","offer_id":58518888350027,"sku":null,"price":16.99,"currency_code":"GBP","in_stock":true},{"title":"Blue \/ Custom Embroidery","offer_id":58518888382795,"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-copy-copy","provider":"USASTARFASHION","version":"1.0","type":"link"}