{"product_id":"christenson-cafe-racer-2-0","title":"Christenson - Cafe Racer 2.0","description":"\u003cdiv id=\"ff-configurator-wrapper\" style=\"width:100vw;position:relative;left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;max-width:100vw;overflow:visible;\"\u003e\n  \u003ciframe id=\"ff-configurator\" style=\"width:100%;height:900px;min-height:600px;border:none;border-radius:0;display:block;overflow:hidden;\" frameborder=\"0\" scrolling=\"no\" title=\"Board Configurator\"\u003e\u003c\/iframe\u003e\n\u003c\/div\u003e\n\u003cscript\u003e\n(function(){\n  var CONFIGURATOR_URL = \"https:\/\/sales.shapersportal.app\/configurator?shape_id=6a97cca05c9227f086b6a7b7\u0026v=1788407847648\u0026app_id=6a026ac871b32618c4472776\";\n  var DRAFT_ORDER_URL = \"https:\/\/sales.shapersportal.app\/functions\/createDraftOrder\";\n  var SHAPE_ID = \"6a97cca05c9227f086b6a7b7\";\n  var submitting = false;\n\n  \/\/ Un-clip only ancestors that actually CLIP the iframe (overflow hidden\/clip\n  \/\/ or a max-height). NEVER touch scrollable containers (overflow auto\/scroll)\n  \/\/ — some themes (e.g. Horizon) scroll the page through an inner wrapper, and\n  \/\/ forcing it to overflow:visible kills page scrolling entirely.\n  function clearClipping(f){\n    var p = f.parentElement;\n    while (p \u0026\u0026 p !== document.body){\n      var cs = window.getComputedStyle(p);\n      var oy = cs.overflowY;\n      if (oy === 'hidden' || oy === 'clip'){\n        p.style.overflow = 'visible';\n        p.style.maxHeight = 'none';\n        p.style.height = 'auto';\n      } else if (cs.maxHeight !== 'none'){\n        p.style.maxHeight = 'none';\n      }\n      p = p.parentElement;\n    }\n  }\n\n  \/\/ Resolve the buyer's SELECTED country from Shopify's storefront (the\n  \/\/ currency\/country dropdown), so the configurator prices for the country the\n  \/\/ buyer picked rather than their geo-IP. Shopify exposes the active country\n  \/\/ on window.Shopify.country (ISO-2). Fall back to nothing → the configurator\n  \/\/ uses geo-IP. Append it as a ?country= param on the iframe src.\n  function buyerCountry(){\n    try { if (window.Shopify \u0026\u0026 window.Shopify.country) return String(window.Shopify.country).toUpperCase(); } catch(e){}\n    return '';\n  }\n  \/\/ Carry through the build options the customer arrived with (e.g. a quick\n  \/\/ order link from a board wall: ?only=stock_dims,hardware\u0026blank=pu), plus\n  \/\/ their saved surfer details and any retail referral code.\n  function passThroughParams(){\n    var keep = ['only','blank','prefs','ref','resume','height','weight','ability','unit','voucher','consult'];\n    var qs = new URLSearchParams(window.location.search);\n    var out = '';\n    keep.forEach(function(k){\n      var v = qs.get(k);\n      if (v) out += '\u0026' + k + '=' + encodeURIComponent(v);\n    });\n    return out;\n  }\n  \/\/ Read the store page's fonts and colours (the nearest painted background\n  \/\/ behind the embed, the body text colour, a heading font and a theme button)\n  \/\/ and post them into the configurator so it styles itself to match.\n  function postTheme(){\n    var f = document.getElementById('ff-configurator');\n    if (!f || !f.contentWindow) return;\n    var bg = '';\n    var p = f.parentElement;\n    while (p){\n      var c = getComputedStyle(p).backgroundColor;\n      if (c \u0026\u0026 c !== 'rgba(0, 0, 0, 0)' \u0026\u0026 c !== 'transparent'){ bg = c; break; }\n      p = p.parentElement;\n    }\n    var b = getComputedStyle(document.body);\n    var h = document.querySelector('h1,h2'), hs = h ? getComputedStyle(h) : null;\n    var btn = document.querySelector('button,.button,.btn,a[class*=\"button\"]'), bs = btn ? getComputedStyle(btn) : null;\n    f.contentWindow.postMessage({ type: 'sp_embed_theme', font: b.fontFamily, headingFont: hs ? hs.fontFamily : '', color: b.color, bg: bg || b.backgroundColor, accent: bs ? bs.backgroundColor : '', accentColor: bs ? bs.color : '' }, '*');\n  }\n  (function(){\n    var f = document.getElementById('ff-configurator');\n    if (f){\n      f.addEventListener('load', postTheme);\n      var c = buyerCountry();\n      f.src = CONFIGURATOR_URL + (c ? '\u0026country=' + encodeURIComponent(c) : '') + passThroughParams();\n    }\n  })();\n\n  window.addEventListener('message', function(event){\n    var data = event.data || {};\n    if (data.type === 'ff_add_to_cart'){\n      if (submitting) return;\n      submitting = true;\n      fetch(DRAFT_ORDER_URL, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\/json', 'Accept': 'application\/json' },\n        body: JSON.stringify({\n          shape_id: SHAPE_ID,\n          config: data.config || {},\n          deposit_price: data.price,\n          total_price: data.totalPrice,\n          currency: data.currency,\n          country_code: data.country_code,\n          fin_variant_id: data.fin_variant_id,\n          fin_variant_price: data.fin_variant_price\n        })\n      }).then(function(r){\n        return r.json().then(function(body){ return { ok: r.ok, body: body }; });\n      }).then(function(res){\n        submitting = false;\n        if (!res.ok || !res.body || !res.body.invoice_url){\n          throw new Error((res.body \u0026\u0026 res.body.error) || 'Could not start checkout');\n        }\n        window.top.location.href = res.body.invoice_url;\n      }).catch(function(err){\n        submitting = false;\n        alert(err.message || 'Could not start checkout. Please try again.');\n      });\n    }\n    \/\/ The configurator asks how many boards are already in the checkout, so a\n    \/\/ multi-board (bulk) deal can be applied to the board being built.\n    if (data.type === 'ff_get_cart'){\n      var frame = document.getElementById('ff-configurator');\n      fetch('\/cart.js', { headers: { 'Accept': 'application\/json' } })\n        .then(function(r){ return r.json(); })\n        .then(function(cart){\n          var n = (cart \u0026\u0026 cart.items ? cart.items : []).reduce(function(sum, it){\n            return sum + (Number(it.quantity) || 0);\n          }, 0);\n          if (frame \u0026\u0026 frame.contentWindow) frame.contentWindow.postMessage({ type: 'ff_cart', boards: n }, '*');\n        }).catch(function(){});\n    }\n    if (data.type === 'ff_resize'){\n      var f = document.getElementById('ff-configurator');\n      if (f \u0026\u0026 data.height){\n        f.style.height = data.height + 'px';\n        f.style.minHeight = data.height + 'px';\n        clearClipping(f);\n      }\n    }\n  });\n\n  \/\/ Clear any inherited clipping on the wrapper chain immediately on load, so a\n  \/\/ theme that ships its description area as overflow-hidden doesn't trap the\n  \/\/ iframe before the first resize message lands.\n  (function(){\n    var f = document.getElementById('ff-configurator');\n    if (f) clearClipping(f);\n  })();\n})();\n\u003c\/script\u003e","brand":"Flexframe","offers":[{"title":"Default Title","offer_id":46478573437064,"sku":null,"price":0.0,"currency_code":"NZD","in_stock":true}],"url":"https:\/\/www.flexframe.tech\/products\/christenson-cafe-racer-2-0","provider":"Flexframe","version":"1.0","type":"link"}