{
  "version": 3,
  "sources": ["/cdn/shop/t/14/assets/configurator.js?rev=31"],
  "sourcesContent": ["/* Downforce 3D \u2014 hex set builder, ported from TrackConfigurator.jsx + ProductDetail.jsx.\n *\n * \u2B50 WHY VANILLA AND NOT BUNDLED REACT. The migration analysis offered \"lift the React component\n * into a theme asset\" as the cheap path. Rejected for this piece: it means a second build pipeline\n * (Vite -> IIFE) shipped inside a Liquid theme, which nobody maintaining a Shopify theme will\n * expect to find. The UI here is forms and a list \u2014 the genuinely delicate part is\n * `resolveHex()`, and that is ported line-for-line below regardless of language.\n *\n * \u26D4\u26D4 THE ONE FUNCTION THAT MUST NOT DRIFT IS `resolveHex()`.\n * It maps a configured row to a variant of a DIFFERENT product. Every guard in it is scar tissue\n * from a shipped bug, and its failure mode is not a crash \u2014 it is a PAID, PLAUSIBLE-LOOKING ORDER\n * FOR THE WRONG THING. Two recorded instances:\n *   \u2022 a Large set silently resolving to Medium variants (wrong price AND wrong product)\n *   \u2022 a \"$75 Monza 10 Inch\" line item labelled `Track: Silverstone`\n * \u26A0 It will not surface in a click-through. Test it against a written matrix: wrong tier, missing\n * edition, unknown circuit, unavailable variant.\n *\n * THE CART CALL. `cartCreate(lines: [...])` becomes `POST /cart/add.js` with `items: []`, and\n * Storefront `attributes` become line item `properties`. Same destination on the order.\n * \u2B50 Shopify keys a cart line on variant + properties, so the same circuit in two colours stays two\n * lines \u2014 the behaviour the React source says was measured on the live store, preserved for free.\n *\n * \u2B50 2026-09-19 CLARITY HEX CRO \u2014 deltas only, on live rev=27 (gallery-sync) base.\n * Do NOT replace this file with a pre-gallery-sync / &rev=17 asset. syncGallery, thumbHtml,\n * catalogue images, and DF3DVolume preview stay. Bump Liquid `&rev=` past 27 when this changes.\n * \u2B50 2026-10-20 DFM-32: seed the first hex from ?variant= when that id is a Large edition,\n * and keep the header / was prices on the selected hex. Liquid `&rev=` is 31.\n */\n(function () {\n  'use strict';\n\n  var root = document.getElementById('df3d-configurator');\n  if (!root) return;\n\n  /* \u2B50\u2B50 TWO CATALOGUES, ONE PICKER. A hex now carries its own SIZE, so a single set can mix\n   * standard and 9-inch. The two tiers are separate PRODUCTS (road-atlanta / road-atlanta-10-inch)\n   * with separate variant ids and prices, so they arrive as separate catalogues and are joined\n   * here on the BASE handle.\n   * \u26D4 Join on the base handle, never on the display name. Names are edited (the trailing \" 9 Inch\"\n   * was stripped on 2026-08-18) and two tiers of one circuit would silently stop matching. */\n  function readCat(id) {\n    var el = document.getElementById(id);\n    if (!el) return [];\n    try { return JSON.parse(el.textContent) || []; } catch (e) { return []; }\n  }\n  var CAT_MEDIUM = readCat('df3d-catalogue-medium');\n  var CAT_LARGE  = readCat('df3d-catalogue-large');\n  /* Back-compat: older markup emitted a single #df3d-catalogue for the page's own tier. */\n  if (!CAT_MEDIUM.length && !CAT_LARGE.length) CAT_MEDIUM = readCat('df3d-catalogue');\n\n  var LARGE_SUFFIX = '-10-inch';\n  var LARGE_RE = /-10-inch$/;\n  function baseOf(handle) {\n    return String(handle || '').replace(/-10-inch$/, '');\n  }\n\n  /* The pickable list is the UNION of both tiers, one row per circuit, carrying which sizes exist.\n   *\n   * \u2B50 2026-09-10: Standard and Large now live on the SAME product as Edition values, same handle\n   * (road-atlanta, not road-atlanta-10-inch). Live catalogues split variants by edition. Ours\n   * used to treat \"has a large catalogue row\" as \"has a large size\" \u2014 when both collections\n   * listed the same handle, or the large collection was empty, `sizesFor` returned one size and\n   * the per-hex switch never rendered. Split on Edition (and handle suffix) so both sizes exist\n   * on one circuit even from a single catalogue. */\n  var BY_BASE = {};\n  function isLargeEdition(ed) { return /large|9\\s*inch/i.test(ed || ''); }\n  function variantsMatching(p, large) {\n    return (p.variants || []).filter(function (v) { return isLargeEdition(v.edition) === !!large; });\n  }\n  function sizedCopy(p, vars) {\n    if (!p || !vars.length) return null;\n    return { handle: p.handle, name: p.name, variants: vars, images: p.images };\n  }\n  function ingest(p, fallbackSize) {\n    if (!p || !p.handle) return;\n    var b = baseOf(p.handle);\n    var row = BY_BASE[b] = BY_BASE[b] || { base: b, name: p.name, medium: null, large: null, images: [] };\n    if (p.name) row.name = p.name;\n    if (p.images && p.images.length) row.images = p.images;\n    var medVars = variantsMatching(p, false);\n    var lrgVars = variantsMatching(p, true);\n    if (medVars.length) row.medium = sizedCopy(p, medVars);\n    if (lrgVars.length) row.large = sizedCopy(p, lrgVars);\n    if (!medVars.length && !lrgVars.length) {\n      if (fallbackSize === 'large' || LARGE_RE.test(p.handle)) row.large = p;\n      else row.medium = p;\n    }\n  }\n  CAT_MEDIUM.forEach(function (p) { ingest(p, 'medium'); });\n  CAT_LARGE.forEach(function (p) { ingest(p, 'large'); });\n  var CATALOGUE = Object.keys(BY_BASE).map(function (k) { return BY_BASE[k]; });\n\n  function entryFor(base) { return BY_BASE[base] || null; }\n  function productFor(base, size) {\n    var e = entryFor(base);\n    if (!e) return null;\n    /* \u26D4 A MISSING size means \"this page's tier\", NOT medium. Defaulting to medium made a 9-inch\n     * page resolve an un-sized row to the $39.99 product \u2014 the exact wrong-tier failure this\n     * function exists to prevent. Every hex built by newHex() carries a size, so this only bites\n     * on hand-built rows (tests, and any future caller), which is precisely where a silent wrong\n     * default does the most damage. */\n    var want = (size === 'large' || size === 'medium') ? size : DEFAULT_SIZE;\n    return want === 'large' ? e.large : e.medium;\n  }\n  function sizesFor(base) {\n    var e = entryFor(base);\n    if (!e) return [];\n    var out = [];\n    if (e.medium) out.push('medium');\n    if (e.large) out.push('large');\n    return out;\n  }\n\n  /* \u2B50 Alphabetical by the name the customer actually READS, not by collection order (which came\n   * out as Grattan, AMP Karting, Roebling, Schenley \u2014 no order at all from a browsing point of\n   * view). With ~49 options and no way to jump to a letter, an unsorted list means scanning the\n   * whole thing every time.\n   * \u26A0 localeCompare, not a plain > comparison: the catalogue contains N\u00FCrburgring, Aut\u00F3dromo\n   * Hermanos Rodr\u00EDguez and S\u00E3o Paulo, and a byte-wise sort files accented characters after Z.\n   * \u2B50 Safe to reorder \u2014 resolveHex() looks circuits up by HANDLE, never by index. */\n  CATALOGUE.sort(function (a, b) { return String(a.name).localeCompare(String(b.name), \"en\", { sensitivity: \"base\" }); });\n\n  /* Live per-hex Size labels/dims (theme 145030611028). Do not restyle to a two-up page-nav grid. */\n  var SIZE_LABEL = { medium: 'Standard', large: 'Large' };\n  var SIZE_DIMS = { medium: '144 &times; 125 mm', large: '229 &times; 198 mm' };\n  var SIZE_FALLBACK_PRICE = { medium: 3999, large: 6999 };\n\n  function sizeSwitchHtml(h, picked) {\n    var base = (picked && picked.base) || baseOf(h.trackHandle);\n    var s = '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Size</label>' +\n      '<div class=\"inline-flex border border-border\">';\n    ['medium', 'large'].forEach(function (sz) {\n      var prod = base ? productFor(base, sz) : null;\n      var v = prod && prod.variants && prod.variants[0];\n      var price = v ? v.price : SIZE_FALLBACK_PRICE[sz];\n      var on = h.size === sz;\n      s += '<button type=\"button\" data-size=\"' + sz + '\" ' +\n        'class=\"px-3 py-1.5 text-xs text-left transition-colors ' +\n        (on ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-secondary/60') + '\">' +\n        '<span class=\"block\">' + SIZE_LABEL[sz] + '</span>' +\n        '<span class=\"block text-[10px] opacity-70\" style=\"line-height:1.2\">' + SIZE_DIMS[sz] +\n          ' &middot; ' + money(price) + '</span></button>';\n    });\n    return s + '</div></div>';\n  }\n\n  var MODE = root.dataset.mode || 'preset';            // 'preset' (pick a circuit) | 'custom' (type one)\n  var PRODUCT_HANDLE = root.dataset.productHandle || '';\n  var PRODUCT_TITLE = root.dataset.productTitle || '';\n  var MAX_HEXES = parseInt(root.dataset.max, 10) || 25;\n  var DEFAULT_SIZE = root.dataset.defaultSize === 'large' ? 'large' : 'medium';\n\n  /* Gallery sync (restored from t/5): left [data-gallery] follows committed hex track photos. */\n  var GAL = document.querySelector('[data-gallery]');\n  var GAL_ORIGINAL = GAL ? GAL.innerHTML : null;\n  /* Lazy: MODE/PRODUCT_HANDLE must be read at call time, not capture undefined at parse. */\n  function galOwned() {\n    /* Circuit Hex (not in catalogue) always owns the left gallery.\n     * Named track PDPs with 0 catalogue images also own it \u2014 otherwise an empty\n     * \"studio photography coming soon\" placeholder never yields to other tracks'\n     * photos when the buyer builds a multi-track set (Thunderhill, 2026-09-15). */\n    if (!GAL || MODE !== 'preset') return false;\n    if (!catalogueHas(PRODUCT_HANDLE)) return true;\n    var e = entryFor(baseOf(PRODUCT_HANDLE));\n    return !(e && e.images && e.images.length);\n  }\n  function atWidth(url, w) {\n    var u = String(url);\n    return /[?&]width=\\d+/.test(u) ? u.replace(/([?&])width=\\d+/, '$1width=' + w) : u;\n  }\n  function galleryPicks() {\n    var seen = {}, out = [];\n    hexes.forEach(function (h) {\n      var base = baseOf(h.trackHandle), e = entryFor(base);\n      if (!e || !e.images || !e.images.length || seen[base]) return;\n      seen[base] = 1;\n      e.images.forEach(function (u, i) {\n        out.push({ url: u, base: base, name: e.name + (i ? ' hex, view ' + (i + 1) : ' hex') });\n      });\n    });\n    return out;\n  }\n  function slideIndexFor(base) {\n    var picks = galleryPicks();\n    for (var i = 0; i < picks.length; i++) if (picks[i].base === base) return i;\n    return -1;\n  }\n  function syncGallery() {\n    if (!galOwned()) return;\n    var picks = galleryPicks();\n    if (!picks.length) {\n      if (GAL.getAttribute('data-df3d-set') === '1') {\n        GAL.innerHTML = GAL_ORIGINAL;\n        GAL.removeAttribute('data-df3d-set');\n        GAL.removeAttribute('data-df3d-key');\n        GAL.dispatchEvent(new CustomEvent('df3d:gallery-rebuilt', { bubbles: true }));\n      }\n      return;\n    }\n    var key = picks.map(function (p) { return p.url; }).join('|');\n    if (GAL.getAttribute('data-df3d-key') === key) return;\n    /* CLARITY HEX CRO: keep syncGallery markup; only enlarge arrows to 44px and use the shared\n     * gallery frame class so a JS rebuild does not drop the mobile 4:3 crop. */\n    var main = '<div class=\"df3d-gallery-frame aspect-square bg-secondary overflow-hidden border border-border relative\" data-gallery-stage>';\n    picks.forEach(function (p, i) {\n      main += '<img src=\"' + esc(atWidth(p.url, 1200)) + '\" alt=\"' + esc(p.name) + '\" ' +\n              'width=\"1200\" height=\"1200\" loading=\"' + (i ? 'lazy' : 'eager') + '\" data-gallery-main=\"' + i + '\" ' +\n              'class=\"w-full h-full object-cover' + (i ? ' hidden' : '') + '\">';\n    });\n    if (picks.length > 1) {\n      main += '<button type=\"button\" data-gallery-prev aria-label=\"Previous image\" class=\"absolute left-2 top-1/2 -translate-y-1/2 w-11 h-11 min-w-[44px] min-h-[44px] flex items-center justify-center bg-background/70 border border-border text-foreground\">&#8249;</button>' +\n              '<button type=\"button\" data-gallery-next aria-label=\"Next image\" class=\"absolute right-2 top-1/2 -translate-y-1/2 w-11 h-11 min-w-[44px] min-h-[44px] flex items-center justify-center bg-background/70 border border-border text-foreground\">&#8250;</button>' +\n              '<div data-gallery-count class=\"absolute bottom-2 right-2 px-2 py-0.5 bg-background/70 border border-border text-[10px] font-heading tracking-[0.1em] text-muted-foreground tabular-nums\">1 / ' + picks.length + '</div>';\n    }\n    main += '</div>';\n    if (picks.length > 1) {\n      main += '<div class=\"grid grid-cols-5 gap-2 mt-2\">';\n      picks.forEach(function (p, i) {\n        main += '<button type=\"button\" data-gallery-thumb=\"' + i + '\" aria-label=\"Show ' + esc(p.name) + '\" ' +\n                'class=\"aspect-square bg-secondary overflow-hidden border transition-colors ' +\n                (i === 0 ? 'border-primary' : 'border-border hover:border-muted-foreground') + '\">' +\n                '<img src=\"' + esc(atWidth(p.url, 300)) + '\" alt=\"' + esc(p.name) + '\" loading=\"lazy\" width=\"300\" height=\"300\" class=\"w-full h-full object-cover\"></button>';\n      });\n      main += '</div>';\n    }\n    GAL.innerHTML = main;\n    GAL.setAttribute('data-df3d-set', '1');\n    GAL.setAttribute('data-df3d-key', key);\n    GAL.dispatchEvent(new CustomEvent('df3d:gallery-rebuilt', { bubbles: true }));\n  }\n  function thumbHtml(base) {\n    var e = entryFor(base);\n    if (!e) return '';\n    if (!e.images || !e.images.length) {\n      return '<span class=\"shrink-0 w-10 h-10 border border-border bg-background flex items-center justify-center\" ' +\n             'title=\"No photo of this circuit yet. It is printed to order either way.\">' +\n             '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" ' +\n             'class=\"text-muted-foreground/70\" aria-hidden=\"true\">' +\n             '<path d=\"M12 2.5 20.5 7v10L12 21.5 3.5 17V7z\" stroke-linejoin=\"round\"/></svg>' +\n             '<span class=\"sr-only\">No photo yet</span></span>';\n    }\n    return '<img src=\"' + esc(e.images[0]) + '\" alt=\"' + esc(e.name) + ' hex\" width=\"40\" height=\"40\" loading=\"lazy\" ' +\n           'class=\"shrink-0 w-10 h-10 object-cover border border-border\">';\n  }\n\n\n  /* \u2B50 THE DESK STAND IS A DIFFERENT PRODUCT, offered per hex. Josh 2026-08-19: \"we also need the\n   * option to add a desk stand for every single hex the person is buying instead of just adding\n   * one when you hit add to cart.\"\n   * \u26D4 Absent attributes mean the stand is unpublished or sold out. Everything below is gated on\n   * STAND_ID so the feature disappears cleanly rather than posting a line that 422s the basket \u2014\n   * a failed add loses the WHOLE set, not just the stand.\n   * \u26A0 Stands are identical to each other, so they go to the cart as ONE line with quantity N, not\n   * N lines. Prints needs the COUNT, not which hex each belongs to. */\n  var STAND_ID = parseInt(root.dataset.standId, 10) || 0;\n  var STAND_PRICE = parseInt(root.dataset.standPrice, 10) || 0;\n  /* Live (theme 145030611028, measured 2026-09-10): stands fit Standard only. Switching a hex\n   * to Large clears its stand \u2014 that is part of the Std \u2194 Large interaction, not a separate offer. */\n  function standEligible(h) { return !!STAND_ID && h.size !== 'large'; }\n  function standsWanted() {\n    if (!STAND_ID) return 0;\n    return hexes.reduce(function (n, h) { return n + (h.stand && standEligible(h) ? qtyOf(h) : 0); }, 0);\n  }\n\n  /* \u26D4 COLOURS: hexes must match the live palette EXACTLY. Every value here is a filament that is\n   * actually stocked and has a verified hex read off the spool's RFID \u2014 never invent one, and\n   * never \"correct\" one. Red changed #BB3D43 -> #C12E1F on 2026-08-14 as a PRODUCT decision\n   * (it keeps red available on both printers); do not restore the old value. */\n  var COLORS = [\n    { name: 'White',       value: '#FFFFFF' },\n    { name: 'Black',       value: '#000000' },\n    { name: 'Red',         value: '#C12E1F' },\n    { name: 'Cobalt Blue', value: '#0056B8' },\n    { name: 'Blue Grey',   value: '#5B6579' },\n    { name: 'Olive',       value: '#68724D' },\n    { name: 'Gold',        value: '#E4BD68' },\n    { name: 'Grass Green', value: '#61C680' }\n  ];\n  var ANY_COLOR = '__any__';\n  /* \u2B50 The Shopify `Color` option value for a hex \u2014 the same name `Route color` records. Restored\n   * 2026-10-06 (first added 2026-08-22, lost when a stale-base branch went live ~Sep 10). */\n  var ANY_COLOR_NAME = \"Any \u2014 maker's choice\";\n  function colorNameFor(h) {\n    if (h.color === ANY_COLOR) return ANY_COLOR_NAME;\n    if (OFFER_CUSTOM_COLOR && h.wantCustomColor) return (h.customColor || '').trim() || 'Customer to specify';\n    var match = COLORS.filter(function (c) { return c.value === h.color; })[0];\n    return match ? match.name : h.color;\n  }\n\n  /* \u2500\u2500 state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n  var seq = 0;\n  function newHex(trackHandle, size) {\n    return {\n      id: ++seq,\n      trackHandle: baseOf(trackHandle) || '',\n      /* \u2B50 SIZE LIVES ON THE HEX, not on the page. That is the whole change: a set can hold both\n       * tiers, and switching a hex's size no longer navigates anywhere. Falls back to the page's\n       * own tier so a 9-inch page still starts you on 9-inch. */\n      size: (size === 'large' || size === 'medium') ? size : DEFAULT_SIZE,\n      trackName: '',\n      routeLink: '',\n      color: COLORS[2].value,   // Red \u2014 the most-printed track colour\n      wantCustomColor: false,\n      customColor: '',\n      textLine1: '', textLine2: '',\n      blank1: false, blank2: false,\n      notes: '',\n      qty: 1,\n      stand: false      // a desk stand for THIS hex \u2014 see STAND_ID\n    };\n  }\n\n  /* \u2B50 Is this handle a circuit the customer could actually have picked? The generic set-builder\n   * products (circuit-hex, circuit-hex-10-inch) are excluded from the catalogue by design, so\n   * \"this page's own handle\" is NOT a valid answer on those pages. See validate(). */\n  /* \u26A0 h.trackHandle is a BASE handle now (road-atlanta), not a product handle \u2014 the size decides\n   * which product it resolves to. Callers pass either; baseOf() normalises. */\n  function catalogueHas(handle) {\n    if (!handle) return false;\n    return !!entryFor(baseOf(handle));\n  }\n\n  /* \u2500\u2500 this product's own variants (needed before the first hex is seeded) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n  /* \u26D4 READS THIS PRODUCT'S OWN VARIANTS, NOT THE CATALOGUE. The catalogue excludes the generic\n   * set-builder products by design, so looking ourselves up in it returned null on exactly the\n   * pages that matter most \u2014 and every price rendered $0.00. Separate source, separate concern. */\n  var OWN_VARIANTS = [];\n  try { OWN_VARIANTS = JSON.parse(document.getElementById('df3d-product-variants').textContent); } catch (e) { OWN_VARIANTS = []; }\n\n  /* \u2B50 DFM-32: a ?variant= Large deep link must seed the first hex as large. DEFAULT_SIZE is\n   * the page tier (named circuit pages are Standard) and used to ignore the URL, so the header\n   * painted Large from Liquid while the builder and sticky ATC started on Standard. */\n  function variantFromSearch() {\n    var search = '';\n    try {\n      search = (typeof location !== 'undefined' && location.search) ? String(location.search) : '';\n    } catch (e) { search = ''; }\n    var q = search.charAt(0) === '?' ? search.slice(1) : search;\n    var want = 0;\n    q.split('&').forEach(function (part) {\n      if (!part) return;\n      var eq = part.indexOf('=');\n      var k = eq === -1 ? part : part.slice(0, eq);\n      var val = eq === -1 ? '' : part.slice(eq + 1);\n      try { k = decodeURIComponent(k.replace(/\\+/g, ' ')); } catch (err) {}\n      if (k !== 'variant') return;\n      try { val = decodeURIComponent(val.replace(/\\+/g, ' ')); } catch (err) {}\n      want = parseInt(val, 10) || 0;\n    });\n    if (!want) return null;\n    return OWN_VARIANTS.filter(function (v) { return v.id === want; })[0] || null;\n  }\n  function seedSize() {\n    var v = variantFromSearch();\n    if (!v) return DEFAULT_SIZE;\n    return isLargeEdition(v.edition) ? 'large' : 'medium';\n  }\n  var SEED_SIZE = seedSize();\n\n  /* \u26D4 Seed from PRODUCT_HANDLE only when it is a REAL catalogue entry. On a named circuit page\n   * that pre-selects the circuit you are looking at (correct); on the generic set-builder it must\n   * seed empty so the picker reads as unanswered \u2014 which is what it already displays. */\n  /* \u26D4\u26D4 START EMPTY RATHER THAN WITH A HEX THAT CANNOT BE FILLED. On the generic set-builder\n   * pages (circuit-hex, circuit-hex-10-inch) this page's own handle is deliberately excluded from\n   * the catalogue, so the seeded hex had no circuit \u2014 and once the per-hex picker was removed on\n   * 2026-08-19 there was no way to give it one. It sat there permanently reading \"Choose a circuit\n   * above\" in red while the circuits the customer added appended BELOW it as hexes 2, 3, 4.\n   * \u2B50 A named circuit page still seeds itself, because there the handle IS a real catalogue entry.\n   * \u26A0 Zero hexes is now a REACHABLE state, so validate() has to reject it \u2014 see below. It did not\n   * before: the loop ran zero times and returned null, which reads as valid. */\n  var hexes = [];\n  if (MODE !== 'preset' || catalogueHas(PRODUCT_HANDLE)) {\n    hexes.push(newHex(MODE === 'preset' ? PRODUCT_HANDLE : '', SEED_SIZE));\n  }\n\n  function qtyOf(h) { var n = Number(h.qty); return n > 0 ? Math.floor(n) : 1; }\n  function totalTiles() { return hexes.reduce(function (n, h) { return n + qtyOf(h); }, 0); }\n\n  function currentVariant() {\n    var input = document.querySelector('[name=\"id\"]');\n    var id = input ? Number(input.value) : 0;\n    return OWN_VARIANTS.filter(function (v) { return v.id === id; })[0] || OWN_VARIANTS[0] || null;\n  }\n\n  /* \u26D4\u26D4 MUST BE READ LIVE, NEVER FROZEN AT BOOT. This was `var SHOW_TEXT = root.dataset.showText`,\n   * captured once, and the Edition <select> lives OUTSIDE this component so nothing re-derived it.\n   * Both directions shipped broken: picking \"Personalized\" bought the $49.99 variant with no\n   * Line 1/Line 2 fields on screen and validate() let it through \u2014 a paid personalised order with\n   * nothing to engrave; and going the other way left text typed under Personalized attached to the\n   * $39.99 Standard line. The comment above hexProperties() asserted the opposite and was wrong.\n   * \u2B50 The predicate is `/personaliz/i` on the EDITION OPTION VALUE, matching the headless\n   * editionNeedsCaption() (src/lib/catalog.js) rather than a fresh ad-hoc regex \u2014 \"Custom +\n   * personalized caption\" has to match too, and a `contains 'Personalized'` test misses it on case.\n   * \u26A0 The server-rendered data-show-text stays the FIRST-PAINT value, so a `?variant=` deep link\n   * still renders correctly before any change event fires. */\n  /* \u2B50 Route editions, mirroring the headless editionNeedsRoute/editionIsGpx (src/lib/catalog.js).\n   * \"From a public route\" -> paste a link. \"From my GPX file\" -> upload the file.\n   * \u26D4 These read the LIVE variant for the same reason showText() does. */\n  function needsRoute() {\n    var cur = currentVariant();\n    return !!cur && /route|gpx/i.test(cur.edition || '');\n  }\n  function isGpx() {\n    var cur = currentVariant();\n    return !!cur && /gpx/i.test(cur.edition || '');\n  }\n\n  /* \u26D4\u26D4 A ROUTE EDITION IS ONE ROUTE, NOT A SET.\n   * `your-race-route` sells exactly two editions \u2014 \"From a public route\" and \"From my GPX file\" \u2014\n   * and both are route captures. The GPX one CANNOT be a set at all: a File has no JSON\n   * representation, so it falls back to a native single-line-item submit (see submitNativeWithFile)\n   * and the file input is server-rendered ONCE, outside the hex list, so there is no second file to\n   * attach. Selling \"add more hexes\" on top of that is selling something the cart cannot carry.\n   * \u2B50 THE PORTED DESIGN ALREADY DREW THIS LINE AND THE LIQUID PORT LOST IT. ProductDetail.jsx:314:\n   *     useConfigurator = isCircuit && !editionNeedsRoute(editionTitle)\n   * \u2014 route editions never got the multi-hex configurator; they got a single route capture and a\n   * single `routeLink` string. This restores that boundary.\n   * \u26D4 Keyed on the EDITION being a route capture, not on a product-handle denylist: a second route\n   * product needs nothing added here, and PRESET (where \"+ Add circuits\" is load-bearing) can never\n   * reach it, because MODE is fixed at boot from data-mode. */\n  function singleRouteOnly() { return MODE === 'custom' && needsRoute(); }\n\n  /* TWO FEATURES ARE SWITCHED OFF - Josh 2026-08-18, \"drop the complexity\". Flip either constant\n   * to true to bring it back; nothing else needs changing and no data was deleted.\n   * WARNING: SELL_PERSONALIZED must be flipped in main-product.liquid TOO - the Liquid decides\n   * which variants reach the page at all, this only decides whether the fields render. */\n  var SELL_PERSONALIZED = false;\n  var OFFER_CUSTOM_COLOR = false;\n\n  function showText() {\n    if (!SELL_PERSONALIZED) return false;\n    var cur = currentVariant();\n    if (!cur) return root.dataset.showText === 'true';\n    return /personaliz/i.test(cur.edition || '');\n  }\n\n  /* \u26D4\u26D4 PORTED LINE-FOR-LINE from resolvePlaque() (ProductDetail.jsx:342). Read the file comment\n   * above before touching this.\n   *\n   * Match on the Edition OPTION VALUE rather than the variant title \u2014 the original comment notes\n   * this is deliberate, so it survives variant-title drift. Prefer an AVAILABLE variant of the\n   * right edition; fall back to an unavailable one of the RIGHT edition; and if the chosen circuit\n   * has no such edition at all, fall back to THIS page's variant so the buyer's edition and price\n   * stay correct. \u26D4 It must NEVER substitute a different edition \u2014 that silently changes both the\n   * price and the physical thing produced. */\n  function resolveHex(h) {\n    var cur = currentVariant();\n    var unitPrice = cur ? cur.price : 0;\n\n    if (MODE === 'custom') {\n      return {\n        variantId: cur && cur.id,\n        price: unitPrice,\n        compare: cur ? (Number(cur.compare || cur.compare_at) || 0) : 0,\n        trackName: (h.trackName || '').trim()\n      };\n    }\n\n    /* \u26D4\u26D4 RESOLVE WITHIN THE HEX'S OWN SIZE. This used to search one page-level catalogue; a hex\n     * now names a CIRCUIT and a SIZE, and those are two different Shopify products with different\n     * ids and prices. Picking the wrong tier here is the \"$69.99 product sold at $39.99\" failure\n     * this function's whole comment block is about.\n     * \u26A0 If the circuit does not exist in the requested size, fall back to THIS PAGE's product\n     * rather than to the other tier \u2014 silently changing someone's size is the same class of error\n     * as silently changing their edition. */\n    /* \u2B50 INFER THE SIZE FROM THE HANDLE when the row does not carry one. Every hex built by\n     * newHex() has a size, so this only fires for hand-built rows \u2014 but a row naming\n     * \"road-atlanta-10-inch\" with no size is unambiguously asking for the 9-inch product, and\n     * silently handing back the $39.99 one is the wrong-tier failure this function guards. Falling\n     * back to the page default there was defensible and still wrong. */\n    var wantSize = (h.size === 'large' || h.size === 'medium')\n      ? h.size\n      : (LARGE_RE.test(h.trackHandle || '') ? 'large' : DEFAULT_SIZE);\n    var t = productFor(baseOf(h.trackHandle), wantSize);\n    if (!t) t = productFor(baseOf(PRODUCT_HANDLE), wantSize) || productFor(baseOf(PRODUCT_HANDLE), DEFAULT_SIZE);\n\n    /* Size is Standard vs Large on the circuit product (2026-09). Do not use the page's hidden\n     * variant edition \u2014 that stays Standard on circuit-hex and would keep every Large hex on a\n     * Standard variant. */\n    var vs = (t && t.variants) || [];\n\n    /* \u26D4\u26D4 THE COLOUR AXIS. Restored 2026-10-06 \u2014 it was added 2026-08-22 and silently LOST when a\n     * branch cut from a stale GitHub copy went live (~Sep 10). From then until this fix, every tile\n     * was booked as the FIRST variant of its size = the product-photo colour, while `Route color`\n     * said what the customer picked: wrong colour on every packing slip, confirmation email and\n     * variant report (orders #1009-#1014). Every colour is the same price, so there is no\n     * wrong-total tell \u2014 the only symptom is the wrong word on the slip.\n     * \u2B50 Backwards compatible: a product with no Color option emits color:'' on every variant,\n     * hasColorAxis is false, and resolution is exactly what it was.\n     * \u26A0 If the picked colour has no variant (palette and catalogue drifted), land on \"Any \u2014 maker's\n     * choice\" \u2014 a real, honest choice \u2014 never on an arbitrary colour; and say so in the console. */\n    var wantColor = colorNameFor(h);\n    var hasColorAxis = vs.some(function (x) { return x.color; });\n    if (hasColorAxis) {\n      var byColor = vs.filter(function (x) { return x.color === wantColor; });\n      if (!byColor.length) {\n        console.error('[df3d] no \"' + wantColor + '\" variant for ' + ((t && t.name) || 'this circuit') +\n                      ' \u2014 the palette and the catalogue have drifted. Falling back to maker\\'s choice.');\n        byColor = vs.filter(function (x) { return x.color === ANY_COLOR_NAME; });\n      }\n      if (byColor.length) vs = byColor;\n    }\n    var sizeEd = function (v) {\n      var large = isLargeEdition(v.edition);\n      return wantSize === 'large' ? large : !large;\n    };\n    var v = vs.filter(function (x) { return sizeEd(x) && x.available; })[0] || vs.filter(sizeEd)[0];\n\n    /* \u26D4 STRIP THE SIZE FROM THE FALLBACK NAME TOO. Catalogue names have a trailing \" 9 Inch\"\n     * removed (Josh, 2026-08-18: the size is already the product, not part of the circuit name),\n     * but this fallback reads the PAGE TITLE, which still carries it. A fallback line therefore\n     * reached the packing slip as \"Road Atlanta 9 Inch\" while every other line said \"Road Atlanta\"\n     * \u2014 two names for one circuit, on the document Prints works from. */\n    var trackName = (t && t.name) ||\n      ((PRODUCT_TITLE.split(/[,|]/)[0] || '').trim().replace(/\\s+9\\s*inch$/i, '').trim());\n    if (!v) {\n      return {\n        variantId: cur && cur.id,\n        price: unitPrice,\n        compare: cur ? (Number(cur.compare || cur.compare_at) || 0) : 0,\n        trackName: trackName\n      };\n    }\n    return {\n      variantId: v.id,\n      price: v.price,\n      compare: Number(v.compare || v.compare_at) || 0,\n      trackName: trackName\n    };\n  }\n\n  /* \u26D4 PORTED from plaqueToProperties(). These keys are printed on the packing slip and read by\n   * Prints \u2014 they are an interface, not labels. Do not rename them.\n   * \u26A0 `showText` gates ONLY the engraved lines, so text typed and then abandoned by switching back\n   * to Standard never reaches a Standard order. Special instructions are deliberately NOT gated:\n   * they are not engraved, so they cannot leak onto a tile, and every edition needs somewhere to\n   * say \"this is a gift\". */\n  function hexProperties(h, trackName) {\n    var props = {};\n    var track = MODE === 'custom' ? (h.trackName || '').trim() : trackName;\n    if (track) props['Track'] = track;\n\n    if (h.color === ANY_COLOR) {\n      // \u2B50 Recorded as an explicit choice, not an absence. Prints reads this as \"allocate freely\",\n      // which is materially different from a colour we simply failed to capture.\n      props['Route color'] = \"Any \u2014 maker's choice\";\n    } else if (OFFER_CUSTOM_COLOR && h.wantCustomColor) {\n      props['Route color'] = (h.customColor || '').trim() || 'Customer to specify';\n    } else {\n      var match = COLORS.filter(function (c) { return c.value === h.color; })[0];\n      props['Route color'] = match ? match.name : h.color;\n    }\n\n    if (showText()) {\n      if (!h.blank1 && (h.textLine1 || '').trim()) props['Line 1'] = h.textLine1.trim();\n      if (!h.blank2 && (h.textLine2 || '').trim()) props['Line 2'] = h.textLine2.trim();\n    }\n    /* \u26A0 NEW KEY as of 2026-08-18 \u2014 `Route link` has never appeared on a real order, so unlike the\n     * five keys above it is not yet an interface Prints reads. Named to match the headless\n     * source exactly so it never becomes two names for one thing.\n     * \u26D4 The GPX FILE is not emitted here: a file cannot travel in the JSON /cart/add.js body.\n     * It rides a native multipart submit instead \u2014 see addToCart(). */\n    if (needsRoute() && (h.routeLink || '').trim()) props['Route link'] = h.routeLink.trim();\n    if ((h.notes || '').trim()) props['Special instructions'] = h.notes.trim();\n    return props;\n  }\n\n  /* \u2500\u2500 validation, ported from handleAddToCart's guards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n  function validate() {\n    /* \u26D4 An EMPTY set is not a valid set. Before the seed change this could not happen, so the\n     * loop's \"ran clean, return null\" was the same as \"there is nothing to check\" \u2014 and null means\n     * VALID. Zero hexes would have sailed through to a cart POST with no items. */\n    if (!hexes.length) return 'Choose a circuit to get started';\n\n    for (var i = 0; i < hexes.length; i++) {\n      var h = hexes[i];\n      // \u26A0 Same shape the preset branch below already uses: on a one-hex builder \"for hex 1\" points\n      // at numbering the customer cannot see, and on a route edition there can never be a hex 2.\n      var which = hexes.length > 1 ? ' for hex ' + (i + 1) : '';\n      if (MODE === 'custom' && !(h.trackName || '').trim()) {\n        return 'Add a track name' + which;\n      }\n      // \u26D4 In preset mode a hex can reach checkout naming NO circuit \u2014 the picker opens empty by\n      // design so no single track reads as the default. Without this guard that becomes a paid\n      // order with no circuit on it.\n      //\n      // \u26D4\u26D4 GUARD THE CLASS, NOT THE EMPTY STRING. `!h.trackHandle` was defeated by the seed on\n      // line 81: on /products/circuit-hex the first hex is seeded with PRODUCT_HANDLE, and\n      // hex-catalogue.liquid deliberately EXCLUDES circuit-hex / circuit-hex-10-inch from the\n      // catalogue. So trackHandle was truthy-but-unresolvable, validate() passed, the button still\n      // read \"Choose a circuit\u2026\", and the customer checked out with `Track: Circuit Hex` \u2014 which\n      // Prints cannot fulfil. That page is the header nav destination, both hero CTAs, the\n      // empty-cart CTA and the intended Meta ad landing page.\n      // \u2B50 Membership in CATALOGUE is the real requirement, and testing it also closes archived,\n      // renamed and unknown handles \u2014 the whole class, not the two handles that happened to bite.\n      if (MODE === 'preset' && !catalogueHas(h.trackHandle)) {\n        return hexes.length > 1 ? 'Choose a circuit for hex ' + (i + 1) : 'Choose a circuit';\n      }\n      /* \u26D4 The whole point of this product is the route, and it shipped with nowhere to put one:\n       * $49.99, page copy promising \"send us your course\", and no field. */\n      if (needsRoute()) {\n        if (isGpx()) {\n          if (!fileChosen()) return 'Attach your GPX file' + which;\n        } else if (!(h.routeLink || '').trim()) {\n          return 'Add the link to your route' + which;\n        }\n      }\n      if (showText() && !h.blank1 && !h.blank2 &&\n          !(h.textLine1 || '').trim() && !(h.textLine2 || '').trim()) {\n        return 'Add your engraving text for hex ' + (i + 1) + ', or tick \"leave blank\"';\n      }\n    }\n    return null;\n  }\n\n  /* \u2500\u2500 add to cart \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n  function fileEl() { return document.getElementById('df3d-route-file'); }\n  function fileChosen() { var f = fileEl(); return !!(f && f.files && f.files.length); }\n\n  /* \u26D4\u26D4 A FILE CANNOT TRAVEL IN THE JSON BODY. /cart/add.js is posted as application/json, and a\n   * File has no JSON representation. Shopify's documented mechanism for a file line-item property\n   * is a real <input type=\"file\" name=\"properties[...]\"> inside the product form, submitted as\n   * multipart/form-data \u2014 which this form already is (enctype set in main-product.liquid).\n   * \u2B50 So for the GPX path we simply DO NOT intercept: mirror the hex's properties into hidden\n   * inputs and let the browser submit natively. One line item, which is what a bespoke route is.\n   * \u26A0 Native submit is single-line-item, so the multi-hex set is not available on this path. That\n   * is stated to the customer rather than silently dropped. */\n  function submitNativeWithFile() {\n    var form = document.getElementById('product-form');\n    if (!form) { flash(\"Couldn't attach your file. Please email it to us.\", true); return; }\n    if (hexes.length > 1) {\n      flash('A GPX upload covers one route per order. Remove the extra hexes, or use a route link instead.', true);\n      return;\n    }\n    var h = hexes[0];\n    var r = resolveHex(h);\n    var props = hexProperties(h, r.trackName);\n\n    form.querySelectorAll('[data-df3d-prop]').forEach(function (n) { n.remove(); });\n    Object.keys(props).forEach(function (k) {\n      var i = document.createElement('input');\n      i.type = 'hidden'; i.name = 'properties[' + k + ']'; i.value = props[k];\n      i.setAttribute('data-df3d-prop', '');\n      form.appendChild(i);\n    });\n    var q = document.createElement('input');\n    q.type = 'hidden'; q.name = 'quantity'; q.value = String(qtyOf(h));\n    q.setAttribute('data-df3d-prop', '');\n    form.appendChild(q);\n\n    var btn = root.querySelector('[data-add]');\n    if (btn) { btn.disabled = true; btn.textContent = 'Uploading\u2026'; }\n    form.submit();   // native multipart POST -> Shopify stores the file and returns a CDN URL\n  }\n\n  /* Plan A (STAGE): sequential single-item POST /cart/add.js.\n   * Bulk { items: [...] } can make shop_events_listener drop product_added_to_cart when\n   * request/response item counts mismatch. One item per request keeps channel ATC reliable.\n   * No theme fbq / Meta pixel calls here \u2014 Meta rides the Shopify channel path. */\n  async function addToCart() {\n    var problem = validate();\n    if (problem) { flash(problem, true); return; }\n    if (isGpx() && fileChosen()) { submitNativeWithFile(); return; }\n\n    var items = hexes.map(function (h) {\n      var r = resolveHex(h);\n      return { id: r.variantId, quantity: qtyOf(h), properties: hexProperties(h, r.trackName) };\n    }).filter(function (it) {\n      // \u26D4 A line with no variant id would 422 the whole request and lose the entire set. Drop it\n      // loudly rather than failing the basket.\n      if (!it.id) { console.error('[df3d] a hex resolved to no variant \u2014 skipping it', it); return false; }\n      return true;\n    });\n\n    if (!items.length) { flash('Nothing to add. Check your circuits.', true); return; }\n\n    /* \u2B50 ONE stand line with quantity N, appended AFTER the hexes so the set still reads first in\n     * the cart. Stands are interchangeable, so N lines of one would be noise on the packing slip.\n     * \u26D4 Appended only when N > 0 \u2014 a quantity-0 line is a 422 and would lose the whole basket. */\n    var nStands = standsWanted();\n    if (STAND_ID && nStands > 0) {\n      items.push({ id: STAND_ID, quantity: nStands });\n    }\n\n    var btn = root.querySelector('[data-add]');\n    if (btn) { btn.disabled = true; btn.dataset.label = btn.textContent; btn.textContent = 'Adding\u2026'; }\n\n    var addUrl = window.Shopify && window.Shopify.routes ? window.Shopify.routes.root + 'cart/add.js' : '/cart/add.js';\n    var headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' };\n\n    try {\n      for (var i = 0; i < items.length; i++) {\n        var res = await fetch(addUrl, {\n          method: 'POST',\n          headers: headers,\n          body: JSON.stringify({ items: [items[i]] })\n        });\n        var body = await res.json();\n        if (!res.ok) {\n          throw new Error((body && (body.description || body.message)) || 'Add to cart failed');\n        }\n      }\n      await new Promise(function (resolve) { setTimeout(resolve, 300); });\n      window.location.href = '/cart';\n    } catch (err) {\n      console.error('[df3d] add to cart failed', err);\n      flash((err && err.message) || \"Couldn't add to cart\", true);\n      if (btn) { btn.disabled = false; btn.textContent = btn.dataset.label || 'Add to cart'; }\n    }\n  }\n\n  function flash(msg, isError) {\n    var el = root.querySelector('[data-flash]');\n    if (!el) { if (isError) alert(msg); return; }\n    el.textContent = msg;\n    el.className = 'text-xs mt-2 ' + (isError ? 'text-destructive' : 'text-muted-foreground');\n  }\n\n  /* \u2500\u2500 render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n   * Plain DOM. The React version re-rendered from state on every keystroke; here the inputs are\n   * uncontrolled and write back to state on 'input', so typing never re-renders and never loses\n   * cursor position. Only structural changes (add/remove/duplicate a hex) redraw.\n   * \u26A0 Tailwind classes below are copied from the JSX so the two look identical.\n   */\n\n  /* \u26D4\u26D4 THE ESCAPING CONTRACT \u2014 read before editing any *Html() function below.\n   *\n   * These builders assemble HTML strings and assign them with innerHTML. That is safe ONLY while\n   * this rule holds: **every interpolated value passes through esc(), with no exceptions.**\n   *\n   * Four of the fields here are free text the customer types \u2014 trackName, customColor, notes,\n   * textLine1/2 \u2014 and they are re-rendered into the DOM on every structural change. An unescaped\n   * one is a self-XSS today and a stored-XSS the moment that value is echoed anywhere else (a\n   * packing slip, an admin order view, a confirmation email).\n   *\n   * \u26A0 The catalogue values (t.name, t.handle) are merchant-controlled rather than customer-\n   * controlled, and are escaped anyway \u2014 \"the merchant would never\" is not a security boundary.\n   *\n   * \u2B50 If you need to inject real markup, do NOT relax esc(). Build the node with\n   * document.createElement and set .textContent, which cannot be got wrong by a later edit. */\n  var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>\"']/g, function (c) {\n    return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c];\n  }); };\n\n  function money(cents) { return '$' + (cents / 100).toFixed(2); }\n\n  /* \u26D4\u26D4 THE TRACK PICKER IS A MULTI-SELECT, NOT A DROPDOWN.\n   * The first port of this to Liquid used a plain <select> and Josh caught it immediately: \"the\n   * select multiple functionality is gone\". The React source is emphatic \u2014\n   *   \"\u26D4 THE PER-PLAQUE TRACK SELECTOR IS THE MULTI-SELECT \u2014 this is the control Josh actually\n   *    asked for\"\n   * \u2014 and it is the control that makes this store's actual basket possible. The measured order is\n   * 7-9 hexes; picking them one dropdown at a time, adding a row between each, is a different and\n   * much worse product. Ticking six circuits and getting six configured hexes is the feature.\n   *\n   * \u2B50 AND THE COLOUR INHERITS. Circuits picked here are created with the colour already chosen on\n   * THIS hex \u2014 six circuits after setting blue means six blue hexes, not one blue and five reds\n   * you then fix one at a time, which would defeat the point of multi-select entirely. */\n\n  // Transient UI state. Deliberately NOT on the hex objects: it must never reach the cart.\n  /* \u26D4 openFor STARTS AS 'add' IN PRESET MODE, and that is load-bearing. pickerHtml() renders a\n   * COLLAPSED button whenever ui.openFor !== idx, so a picker meant to be permanently open would\n   * have rendered as a \"Choose a circuit\u2026\" button that nothing opens \u2014 the per-hex opener was\n   * removed in the same change. It would have looked deployed and been dead.\n   * \u2B50 Nothing ever sets it back to null in preset mode: Cancel clears the SELECTION, not the\n   * picker (see the cancel handler). */\n  /* \u2B50 COLLAPSED BY DEFAULT. Josh 2026-08-19: \"can you still make it a dropdown so it is shorter?\"\n   * An always-open 41-row list dominated the page above the set it was meant to feed.\n   * \u26D4 openFor null means pickerHtml() renders its COLLAPSED branch \u2014 which is only safe because\n   * [data-openpicker] has a handler again (below). Removing that handler while leaving this null\n   * is what produced a dead \"Choose a circuit\u2026\" button an hour ago. The two must move together. */\n  /* CLARITY HEX CRO: empty Circuit Hex opens the picker on first paint. A collapsed\n   * \"Choose a circuit\u2026\" is an extra tap Clarity will keep calling a dead click if Add is the job.\n   * Named PDPs still start collapsed (they already have a seeded hex). */\n  var ui = { openFor: (MODE === 'preset' && hexes.length === 0) ? 'add' : null, picked: [], query: '' };\n\n  /* \u26D4 TWO CONTROLS, TWO BEHAVIOURS \u2014 and conflating them was a real bug.\n   *   PER-HEX picker (idx is a number): the first pick REPLACES this hex's circuit, the rest\n   *     become new hexes. \u26D4 STRUCK 2026-08-19 \u2014 see the pick handler. That was the React source's\n   *     behaviour, it surprised Josh twice, and it is gone: the per-hex picker is single-select.\n   *   ADD picker (idx === 'add'): replaces NOTHING. Every pick becomes a new hex.\n   * The first port only had the per-hex control, so \"select some, then go add more\" overwrote the\n   * circuit on whichever hex you happened to open \u2014 Josh: \"it clears the ones i had selected\".\n   * The React source had a separate dashed \"Add circuits\" affordance for exactly this; it is back. */\n  function pickerHtml(h, idx) {\n    var isAdd = idx === 'add';\n    var current = isAdd ? '' : ((entryFor(baseOf(h.trackHandle)) || {}).name || '');\n    var room = MAX_HEXES - hexes.length;\n\n    if (ui.openFor !== idx) {\n      return '<button type=\"button\" data-openpicker class=\"w-full min-h-[44px] flex items-center justify-between gap-2 bg-background border border-foreground px-3 py-2.5 text-sm text-left\">' +\n        /* \u26A0 The collapsed label describes ADDING, never the current circuit: this picker only ever\n         * appends, and a hex's own circuit is shown on its row. Saying \"Road Atlanta\" here would\n         * imply clicking it changes that. */\n        '<span class=\"text-muted-foreground truncate\">' +\n          (hexes.length ? 'Add another circuit\u2026' : 'Choose a circuit\u2026') + '</span>' +\n        '<span class=\"text-[10px] font-heading tracking-[0.1em] uppercase text-muted-foreground shrink-0\">' +\n          (current ? 'Change / add' : 'Pick') + '</span></button>';\n    }\n\n    var q = ui.query.toLowerCase();\n    var shown = q ? CATALOGUE.filter(function (t) { return t.name.toLowerCase().indexOf(q) > -1; }) : CATALOGUE;\n\n    var s = '<div class=\"border border-border bg-card/50\">';\n    s += '<div class=\"flex items-center gap-2 px-3 py-2 border-b border-border\">' +\n      '<input data-pickersearch type=\"text\" value=\"' + esc(ui.query) + '\" placeholder=\"Search circuits...\" ' +\n      'class=\"flex-1 bg-transparent text-sm outline-none text-foreground placeholder:text-muted-foreground\">' +\n      '<button type=\"button\" data-pickercancel class=\"text-[10px] font-heading tracking-[0.1em] uppercase text-muted-foreground hover:text-foreground\">Cancel</button></div>';\n\n    // data-pickerscroll: the marker render() uses to carry scroll position across a rebuild.\n    s += '<div data-pickerscroll class=\"max-h-64 overflow-y-auto\">';\n    if (!shown.length) {\n      // \u2B50 A dead end that reads as a rejection is the worst outcome of a search with ~49 options\n      // against a world of thousands. Name the way forward instead.\n      s += '<p class=\"px-3 py-4 text-xs text-muted-foreground\">No circuit matches &ldquo;' + esc(ui.query) +\n           '&rdquo;. We may still be able to make it. <a href=\"/pages/request\" class=\"text-primary underline underline-offset-2\">ask us</a>.</p>';\n    }\n    /* \u26D4 TWO DIFFERENT STATES, AND CONFLATING THEM IS THE BUG JOSH REPORTED 2026-08-19:\n     *   \"i have tracks selected and then i hit the dropdown again it shows no checkmarks.\"\n     * ui.picked is reset to [] every time the picker OPENS, so its ticks only ever describe the\n     * current session. The circuits already in the set live in hexes[].trackHandle. The list looked\n     * empty while the hexes sat visibly right below it.\n     * \u26D4 THE OBVIOUS FIX IS WRONG: seeding ui.picked from hexes would tick them, but in ADD mode\n     * Done APPENDS everything ticked \u2014 so every existing hex would be duplicated on the next Done.\n     * \u2B50 So they are drawn as separate states instead: \"in your set\" is a muted marker with a count,\n     * \"picking now\" keeps the accent tick. Both can be true at once, and picking a circuit you\n     * already own is legitimate (same circuit, second colour), so nothing is disabled. */\n    shown.forEach(function (t) {\n      var n = ui.picked.filter(function (x) { return x === t.base; }).length;\n      var on = n > 0;\n      var inSet = hexes.filter(function (h) { return h.trackHandle === t.base; }).length;\n      s += '<button type=\"button\" data-pick=\"' + esc(t.base) + '\" ' +\n        'class=\"w-full flex items-center gap-2.5 px-3 py-2 text-left text-sm transition-colors ' +\n        (on ? 'bg-primary/10 text-foreground' : 'text-muted-foreground hover:bg-secondary/60') + '\">' +\n        '<span class=\"w-3.5 h-3.5 border shrink-0 flex items-center justify-center ' +\n          (on ? 'border-primary bg-primary text-primary-foreground' : 'border-muted-foreground') + '\">' +\n          (on ? '&#10003;' : '') + '</span>' +\n        '<span class=\"truncate\">' + esc(t.name) + '</span>' +\n        (n > 1 ? '<span class=\"ml-auto text-[10px] text-primary\">x' + n + '</span>' : '') +\n        /* \u26A0 ml-auto only on the FIRST trailing badge, or two of them fight for the space. */\n        (inSet > 0\n          ? '<span class=\"' + (n > 1 ? '' : 'ml-auto ') + 'text-[10px] text-muted-foreground/70 whitespace-nowrap\">in set' +\n            (inSet > 1 ? ' x' + inSet : '') + '</span>'\n          : '') +\n        '</button>';\n    });\n    s += '</div>';\n\n    /* \u2B50 The footer is where \"what will Done actually do\" gets answered, so it has to acknowledge\n     * the already-in-set case or the muted markers above look like a bug rather than information. */\n    var alreadyN = hexes.filter(function (h) { return catalogueHas(h.trackHandle); }).length;\n    var note = ui.picked.length\n      ? ui.picked.length + ' selected \u00B7 adds ' + ui.picked.length + ' hex' + (ui.picked.length > 1 ? 'es' : '')\n      : (isAdd ? (alreadyN > 0\n                    ? 'Marked \"in set\" are already yours. Pick more to add, nothing is replaced'\n                    : 'Pick as many as you like. Nothing is replaced')\n               : (current ? 'Currently ' + esc(current) + ' \u00B7 pick one to change it' : 'Pick a circuit'));\n    if (room < 8) note += ' \u00B7 room for ' + room;\n\n    s += '<div class=\"flex items-center justify-between gap-2 px-3 py-2 border-t border-border\">' +\n      '<span class=\"text-[10px] text-muted-foreground\">' + note + '</span>' +\n      '<button type=\"button\" data-pickerdone' + (ui.picked.length ? '' : ' disabled') +\n      ' class=\"min-h-[44px] font-heading text-[10px] font-semibold tracking-[0.12em] uppercase border border-primary bg-primary text-primary-foreground px-4 py-2 disabled:opacity-40\">' +\n      /* \u26A0 Only the ADD picker reaches Done now \u2014 the per-hex one applies on click \u2014 so the label\n       * can say what it actually does instead of the neutral \"Done\". */\n      (isAdd ? 'Add ' + (ui.picked.length || '') : 'Done') + '</button></div>';\n\n    return s + '</div>';\n  }\n\n  function swatchesHtml(h) {\n    var out = '';\n    COLORS.forEach(function (c) {\n      var on = h.color === c.value && !h.wantCustomColor;\n      out += '<button type=\"button\" data-color=\"' + esc(c.value) + '\" title=\"' + esc(c.name) + '\" ' +\n        'class=\"w-11 h-11 min-w-[44px] min-h-[44px] border-2 ' + (on ? 'border-foreground' : 'border-border') + '\" ' +\n        'style=\"background-color:' + esc(c.value) + '\"></button>';\n    });\n    // \u2B50 \"Any\" is a peer choice, not an opt-out, so it sits in the swatch row. Drawn as a wheel of\n    // colours we actually stock so it never reads as one specific filament.\n    var anyOn = h.color === ANY_COLOR;\n    out += '<button type=\"button\" data-color=\"' + ANY_COLOR + '\" title=\"Any colour, we\\'ll choose\" ' +\n      'class=\"w-11 h-11 min-w-[44px] min-h-[44px] border-2 ' + (anyOn ? 'border-foreground' : 'border-border') + '\" ' +\n      'style=\"background:conic-gradient(#C12E1F,#E4BD68,#61C680,#0056B8,#5B6579,#C12E1F)\"></button>';\n    return out;\n  }\n\n  function textFieldHtml(idx, n, label, placeholder, value, isBlank) {\n    return '' +\n      '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">' + esc(label) + '</label>' +\n      '<input type=\"text\" data-field=\"textLine' + n + '\" placeholder=\"' + esc(placeholder) + '\" ' +\n        'value=\"' + esc(isBlank ? '' : value) + '\"' + (isBlank ? ' disabled' : '') + ' ' +\n        'class=\"w-full bg-background border border-border px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary ' + (isBlank ? 'opacity-40' : '') + '\">' +\n      '<button type=\"button\" data-blank=\"' + n + '\" class=\"mt-1.5 flex items-center gap-1.5 text-[10px] font-heading tracking-[0.1em] uppercase border px-2 py-0.5 transition-colors ' +\n        (isBlank ? 'border-primary text-primary bg-primary/10' : 'border-border text-muted-foreground hover:border-muted-foreground hover:text-foreground') + '\">' +\n        '<span class=\"w-2.5 h-2.5 border shrink-0 ' + (isBlank ? 'border-foreground bg-foreground' : 'border-muted-foreground bg-transparent') + '\"></span>Leave blank</button>' +\n      '</div>';\n  }\n\n  function hexHtml(h, idx) {\n    var selectedName = h.color === ANY_COLOR ? \"Any colour, we'll choose\"\n      : (COLORS.filter(function (c) { return c.value === h.color; })[0] || {}).name;\n\n    var s = '<div class=\"border border-border bg-card/50\" data-hex=\"' + idx + '\">';\n    s += '<div class=\"flex items-center justify-between gap-2 px-4 py-3 border-b border-border\">' +\n      '<span class=\"flex items-center gap-3' +\n        (function () {\n          var jb = baseOf(h.trackHandle), je = entryFor(jb);\n          return (MODE === 'preset' && galOwned() && je && je.images && je.images.length)\n            ? ' cursor-pointer group/jump\" data-jumpgallery title=\"Show this track in the images\"'\n            : '\"';\n        })() + '>' +\n      (MODE === 'preset' ? thumbHtml(baseOf(h.trackHandle)) : '') +\n      '<span class=\"font-heading text-xs font-semibold tracking-[0.12em] uppercase text-foreground\">Hex ' + (idx + 1) + '</span>' +\n      '</span>' +\n      '<div class=\"flex items-center gap-4\">' +\n        // \u26D4 Duplicate is the OTHER way to reach a second hex, so it goes wherever \"+ Add\" goes.\n        // Leaving it on a route edition would hand back exactly the basket the GPX path has to\n        // refuse at the last step (\"Remove the extra hexes\") \u2014 an offer we then take away.\n        // \u2B50 Nothing is lost: Quantity below already orders N copies of this same route.\n        (singleRouteOnly() ? '' :\n        /* \u26D4 DUPLICATE IS NOT REDUNDANT WITH THE QUANTITY STEPPER \u2014 KEEP IT.\n         * Raised and settled 2026-08-18: Josh asked whether it was redundant, weighed the\n         * distinction, and said \"keep duplicate\".\n         *   Quantity  -> N identical copies of ONE line.\n         *   Duplicate -> a NEW row, pre-filled, that you then CHANGE.\n         * The second is the same-circuit-different-colour case, which is a real pattern here: the\n         * cart keys a line on variant + PROPERTIES, so one circuit in two colours is genuinely two\n         * lines and quantity cannot express it. Duplicate also saves re-picking the circuit and\n         * re-typing the notes.\n         * \u26A0 Do not remove this on the \"looks like the quantity control\" reasoning \u2014 that reasoning\n         * has already been examined and rejected once. */\n        '<button type=\"button\" data-dup class=\"text-[10px] font-heading tracking-[0.1em] uppercase text-muted-foreground hover:text-primary transition-colors px-1\" title=\"Duplicate this hex\">Duplicate</button>') +\n        // \u26A0 Separated deliberately: these sit next to each other and one is destructive. A\n        // mis-tap on Remove loses a configured hex, so they get a divider and real space between.\n        (hexes.length > 1 || galOwned() ? '<span class=\"w-px h-3 bg-border\" aria-hidden=\"true\"></span><button type=\"button\" data-del class=\"text-[10px] font-heading tracking-[0.1em] uppercase text-muted-foreground hover:text-destructive transition-colors px-1\" title=\"Remove hex\">Remove</button>' : '') +\n      '</div></div>';\n\n    s += '<div class=\"p-4 space-y-3\">';\n\n    if (MODE === 'custom') {\n      s += '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Track / circuit name <span class=\"text-destructive\">*</span></label>' +\n        '<input type=\"text\" data-field=\"trackName\" value=\"' + esc(h.trackName) + '\" placeholder=\"e.g. Sebring International Raceway\" ' +\n        'class=\"w-full bg-background border border-border px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary\"></div>';\n      s += sizeSwitchHtml(h, null);\n\n      if (needsRoute() && !isGpx()) {\n        s += '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Link to your route <span class=\"text-destructive\">*</span></label>' +\n          '<input type=\"url\" data-field=\"routeLink\" value=\"' + esc(h.routeLink) + '\" placeholder=\"e.g. https://www.strava.com/routes/...\" ' +\n          'class=\"w-full bg-background border border-border px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary\">' +\n          '<p class=\"text-[11px] text-muted-foreground/70 mt-1.5\">Strava, Garmin, RideWithGPS, Komoot: any public route page.</p></div>';\n      }\n    } else {\n      /* \u26D4 NO PICKER ON A HEX ROW ANY MORE. There is exactly one picker and it is above the set.\n       * A hex row answers \"how should this one look\", never \"which circuit is it\" \u2014 to change the\n       * circuit you remove the hex and pick again. That is the whole point of the split: two\n       * controls that could both change a hex's circuit is what made this confusing three times.\n       * \u26A0 The circuit name is still shown, because a row that does not say what it is would be\n       * worse than the problem being solved. */\n      var picked = entryFor(baseOf(h.trackHandle));\n      s += '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Track</label>' +\n        '<p class=\"text-sm text-foreground\">' + (picked ? esc(picked.name) : '<span class=\"text-destructive\">Choose a circuit above</span>') + '</p></div>';\n\n      /* Per-hex Standard / Large \u2014 always, once a circuit is picked. Not gated on catalogue\n       * shape: Josh 2026-09-10, STAGE hid this whenever sizesFor() returned one tier. */\n      if (picked || h.trackHandle) s += sizeSwitchHtml(h, picked);\n    }\n\n    s += '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Quantity</label>' +\n      '<div class=\"inline-flex items-center border border-border\">' +\n      '<button type=\"button\" data-qty=\"-1\" class=\"w-11 h-11 min-w-[44px] min-h-[44px] flex items-center justify-center text-foreground bg-secondary/60 disabled:opacity-30\"' + (qtyOf(h) <= 1 ? ' disabled' : '') + '>&minus;</button>' +\n      '<span class=\"w-9 text-center font-heading text-sm text-muted-foreground tabular-nums\" aria-hidden=\"true\">' + qtyOf(h) + '</span>' +\n      '<button type=\"button\" data-qty=\"1\" class=\"w-11 h-11 min-w-[44px] min-h-[44px] flex items-center justify-center text-foreground bg-secondary/60 disabled:opacity-30\"' + (qtyOf(h) >= 50 ? ' disabled' : '') + '>+</button>' +\n      '</div></div>';\n\n    s += '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Route Color</label>' +\n      '<div class=\"flex items-center gap-2 flex-wrap\">' + swatchesHtml(h) + '</div>';\n    if (!h.wantCustomColor) s += '<p class=\"text-[10px] text-muted-foreground mt-1\">' + esc(selectedName) + '</p>';\n    if (h.color === ANY_COLOR) s += '<p class=\"text-[10px] text-muted-foreground mt-0.5\">Ordering a set and not fussed which track gets which colour? This is the one to pick.</p>';\n    /* OFF by Josh 2026-08-18. The control and its handler are left intact behind the flag so\n     * re-enabling is one constant, not a rebuild. */\n    if (OFFER_CUSTOM_COLOR) {\n    s += '<div class=\"mt-2 space-y-2\"><button type=\"button\" data-customcolor class=\"flex items-center gap-2 font-heading text-[10px] font-semibold tracking-[0.12em] uppercase border px-3 py-1.5 transition-colors ' +\n      (h.wantCustomColor ? 'border-primary bg-primary text-primary-foreground' : 'border-border text-muted-foreground hover:border-muted-foreground hover:text-foreground') + '\">' +\n      '<span class=\"w-2.5 h-2.5 border shrink-0 ' + (h.wantCustomColor ? 'border-primary-foreground bg-primary-foreground' : 'border-muted-foreground bg-transparent') + '\"></span>Request a different color</button>';\n    if (h.wantCustomColor) {\n      s += '<div><input type=\"text\" data-field=\"customColor\" value=\"' + esc(h.customColor) + '\" placeholder=\"e.g. Midnight Purple, Hex #7B2FBE, RAL 4005...\" ' +\n        'class=\"w-full bg-background border border-border px-3 py-2 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary\">' +\n        '<p class=\"text-[10px] text-muted-foreground mt-1\">Be as specific as possible: hex code, Pantone, or colour name. We\\'ll confirm availability before printing.</p></div>';\n    }\n    s += '</div>';\n    }\n    s += '</div>';\n\n    if (showText()) {\n      s += textFieldHtml(idx, 1, 'Custom Text Line 1', 'e.g. Track name, race name', h.textLine1, h.blank1);\n      s += textFieldHtml(idx, 2, 'Custom Text Line 2', 'e.g. Location, distance, date', h.textLine2, h.blank2);\n    }\n\n    // \u26D4 Outside the showText gate on purpose: not engraved, so it cannot leak onto a tile, and\n    // every edition needs somewhere to say \"this is a gift\".\n    s += '<div><label class=\"text-xs text-muted-foreground mb-1.5 block\">Special instructions <span class=\"opacity-60\">(not engraved)</span></label>' +\n      '<textarea data-field=\"notes\" rows=\"2\" placeholder=\"A deadline, matching an earlier order...\" ' +\n      'class=\"w-full bg-background border border-border px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary min-h-[60px]\">' + esc(h.notes) + '</textarea></div>';\n\n    /* \u2B50 PER-HEX, not one per order. Josh 2026-08-19. A stand holds ONE hex upright, so a 6-hex\n     * set that wants them all standing needs six \u2014 the old single-add block could only ever add\n     * one, which quietly under-served exactly the big baskets that are the product.\n     * \u26D4 Gated on STAND_ID: if the stand is unpublished or sold out the row does not render, so\n     * nothing can post a line that 422s the basket.\n     * \u26A0 Follows the hex's own quantity \u2014 3 of this hex with a stand means 3 stands, which is what\n     * \"a stand for every hex\" has to mean or the counts disagree at the cart. */\n    if (STAND_ID && standEligible(h)) {\n      s += '<label class=\"flex items-center gap-2.5 pt-1 cursor-pointer group/stand\">' +\n        '<input type=\"checkbox\" data-stand' + (h.stand ? ' checked' : '') +\n        ' class=\"w-3.5 h-3.5 shrink-0 accent-primary\">' +\n        '<span class=\"text-xs text-muted-foreground group-hover/stand:text-foreground transition-colors\">' +\n        'Add a desk stand <span class=\"opacity-60\">(+' + money(STAND_PRICE) + ')</span></span></label>';\n    } else if (STAND_ID) {\n      s += '<p class=\"text-[11px] text-muted-foreground pt-1 leading-snug\">The desk stand fits the Standard hex only. A Large hex stand is coming soon.</p>';\n    }\n\n    s += '</div></div>';\n    return s;\n  }\n\n  /* \u2500\u2500 what sits under the hex list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n   * \u26D4\u26D4 THE ADD SLOT IS MODE-DEPENDENT, AND THE PORT SHIPPED WITH ONLY THE PRESET ANSWER.\n   * On /products/your-race-route that put \"+ Add circuits\" on a page with no circuits: it opened\n   * the 49-entry catalogue multi-select, and every tick was then SILENTLY DISCARDED, because a\n   * custom hex is identified by the name the buyer TYPES (resolveHex short-circuits on MODE) and\n   * hexHtml never renders a picker in custom mode. The buyer picked six circuits and got six blank\n   * \"Add a track name\" errors. Josh, 2026-08-18: \"there should not be the add circuits button on\n   * the race route product page.\"\n   * \u2B50 Three answers now, in the order they are decided. The PRESET path is unchanged \u2014 same\n   * markup, same class string, same MAX_HEXES cut-off \u2014 because that button is load-bearing on\n   * every circuit page and was itself a bug fix. */\n  var ADD_BTN_CLASS = 'w-full min-h-[44px] flex items-center justify-center gap-2 border border-dashed border-border ' +\n    'text-muted-foreground py-3 ' +\n    'font-heading text-[11px] font-semibold tracking-[0.15em] uppercase';\n\n  function addSlotHtml() {\n    /* \u2B50 PRESET MODE: THE PICKER IS ALWAYS OPEN AND IS THE ONLY ONE. Josh 2026-08-19 asked for the\n     * dropdown separated from the hex container, with one place on the page that decides circuits.\n     * It used to be a dashed button that REVEALED the picker, which made \"where do circuits come\n     * from\" something you had to discover, and left a second picker on every hex row.\n     * \u26D4 At the cap, say why rather than rendering an unusable list \u2014 a list you cannot act on\n     * reads as broken, and the cap is the one state where \"add more\" is genuinely not an option. */\n    if (MODE === 'preset') {\n      if (hexes.length >= MAX_HEXES) {\n        return '<p class=\"text-[11px] text-muted-foreground border border-dashed border-border px-3 py-3 text-center\">' +\n               'That is ' + MAX_HEXES + ' hexes, the most one order can hold. Remove one to add another.</p>';\n      }\n      return pickerHtml(null, 'add');\n    }\n\n    if (ui.openFor === 'add') return pickerHtml(null, 'add');\n\n    /* \u26D4 SAY THE CAP OUT LOUD, DO NOT JUST ENFORCE IT. One route per builder is a real constraint\n     * (see singleRouteOnly), and a builder that simply has no \"add\" reads as a missing feature \u2014\n     * the buyer's next move is to assume we cannot do it and leave. Sits exactly where the button\n     * was, which is where the question gets asked.\n     * \u26A0 Deliberately does NOT promise that two routes land on ONE order. That is checkout\n     * behaviour nobody here has tested, and this file's scars are all unverified claims. */\n    if (singleRouteOnly()) {\n      return '<p class=\"text-[11px] text-muted-foreground/70\">One route at a time &mdash; add this one to your cart, then set up the next.</p>';\n    }\n\n    if (hexes.length >= MAX_HEXES) return '';\n\n    /* \u2B50 Parity with the React source, which had this exact branch (TrackConfigurator.jsx): \"custom\n     * mode has no catalogue to pick from \u2014 the buyer types the name themselves\". So it appends ONE\n     * blank hex and never opens the picker.\n     * \u26A0 UNREACHABLE TODAY: the only non-route custom product is `choose-your-circuit`, which 404s\n     * on the live store (checked 2026-08-18). It is here so that reviving that product cannot\n     * resurrect the discard-every-pick bug above \u2014 but it has never been exercised in a browser.\n     * \u2B50 Same class string as the preset button on purpose: identical look, and no Tailwind class\n     * enters the theme that the .liquid scan has not already seen. */\n    if (MODE === 'custom') {\n      return '<button type=\"button\" data-add-blank-hex class=\"' + ADD_BTN_CLASS + '\">+ Add another hex</button>';\n    }\n\n    return '<button type=\"button\" data-add-hex class=\"' + ADD_BTN_CLASS + '\">+ Add circuits</button>';\n  }\n\n  function render() {\n    try { syncGallery(); } catch (e) { /* gallery fault must never break the builder */ }\n    var list = root.querySelector('[data-hex-list]');\n    if (!list) return;\n\n    /* \u26D4 THE PICKER'S SCROLL POSITION MUST SURVIVE THE REBUILD.\n     * Every tick calls render(), which replaces innerHTML \u2014 that DESTROYS the scrolling <div>\n     * and recreates it at scrollTop 0. With 41 circuits, ticking anything below the fold threw\n     * the customer back to the top of the list on every single selection, which is exactly the\n     * multi-select flow this picker exists for. Josh, 2026-08-18: \"very jarring and annoying.\"\n     * \u2B50 Capture BEFORE the writes and restore AFTER, because the picker can live in either the\n     * hex list or the add slot and both are rewritten below.\n     * \u26A0 Focus + caret are carried too: ticking a circuit while the search box had focus used to\n     * blur it, so you had to click back into the box to keep filtering. */\n    var scroller = root.querySelector('[data-pickerscroll]');\n    var keepScroll = scroller ? scroller.scrollTop : null;\n    var active = document.activeElement;\n    var searchHadFocus = !!(active && active.hasAttribute && active.hasAttribute('data-pickersearch'));\n    var caret = searchHadFocus ? active.selectionStart : null;\n\n    /* \u2B50 An empty list must say so. A blank gap between the picker and ADD TO CART reads as a\n     * broken page, not as \"you have not picked anything yet\". */\n    list.innerHTML = hexes.length\n      ? hexes.map(hexHtml).join('')\n      : '<p class=\"text-[11px] text-muted-foreground border border-dashed border-border px-3 py-4 text-center\">' +\n        'No hexes yet. Pick a circuit above and it will appear here.</p>';\n    var slot = root.querySelector('[data-add-slot]');\n    if (slot) slot.innerHTML = addSlotHtml();\n\n    var scroller2 = root.querySelector('[data-pickerscroll]');\n    if (scroller2 && keepScroll !== null) scroller2.scrollTop = keepScroll;\n    if (searchHadFocus) {\n      var box = root.querySelector('[data-pickersearch]');\n      if (box) {\n        box.focus();\n        var c = caret === null ? box.value.length : caret;\n        try { box.setSelectionRange(c, c); } catch (e) {}\n      }\n    }\n    /* \u2B50 The file input is server-rendered OUTSIDE this list (see main-product.liquid) precisely so\n     * that re-rendering cannot destroy a chosen file. All we do here is show or hide it. */\n    var fileWrap = document.querySelector('[data-route-file]');\n    if (fileWrap) fileWrap.classList.toggle('hidden', !isGpx());\n    paintStandAll();\n\n    updateTotals();\n  }\n\n  function volumeUnits() {\n    var box = document.querySelector('[data-volume-box]');\n    var inCart = box ? parseInt(box.getAttribute('data-volume-cart-units'), 10) || 0 : 0;\n    return inCart + totalTiles();\n  }\n\n  /* Local copy of the locked ladder so title + ATC preview cannot miss if volume-tier.js is stale.\n   * 10% @3 \u00B7 15% @6 \u00B7 25% @8 \u00B7 30% @12 */\n  function volumeRung(n) {\n    if (window.DF3DVolume && window.DF3DVolume.rung) return window.DF3DVolume.rung(n);\n    n = Number(n) || 0;\n    if (n >= 12) return 30;\n    if (n >= 8) return 25;\n    if (n >= 6) return 15;\n    if (n >= 3) return 10;\n    return 0;\n  }\n\n  function volumeUnitPrice(base, n) {\n    if (window.DF3DVolume && window.DF3DVolume.unitPriceCents) {\n      return window.DF3DVolume.unitPriceCents(base, n);\n    }\n    return Math.round((Number(base) || 0) * (100 - volumeRung(n)) / 100);\n  }\n\n  function hexUnitList(h) {\n    var r = resolveHex(h);\n    var cur = currentVariant();\n    return r.price != null ? r.price : (cur ? cur.price : 0);\n  }\n\n  /* Display-only unlocked-tier preview. addToCart() still posts list prices. */\n  function previewHexTotal() {\n    var units = volumeUnits();\n    return hexes.reduce(function (s, h) {\n      return s + volumeUnitPrice(hexUnitList(h), units) * qtyOf(h);\n    }, 0);\n  }\n\n  /* Header unit + was prices follow the first hex (the URL-seeded row, then whatever sits\n   * in slot 1 after adds/removes). Volume still discounts that list price in place. */\n  function headerHex() { return hexes[0] || null; }\n\n  function headerListCents() {\n    var h = headerHex();\n    if (h) {\n      var list = hexUnitList(h);\n      if (list) return list;\n    }\n    var cur = currentVariant();\n    if (cur && cur.price) return cur.price;\n    return 0;\n  }\n\n  function headerCompareCents(list) {\n    var compare = 0;\n    var h = headerHex();\n    if (h) {\n      var r = resolveHex(h);\n      compare = Number(r.compare) || 0;\n      if (!compare && r.variantId) {\n        var own = OWN_VARIANTS.filter(function (x) { return x.id === r.variantId; })[0];\n        if (own) compare = Number(own.compare || own.compare_at) || 0;\n      }\n    } else {\n      var cur = currentVariant();\n      if (cur) compare = Number(cur.compare || cur.compare_at) || 0;\n    }\n    /* No compare-at above the price = no was price. Never infer one (false reference price). */\n    return compare > list ? compare : 0;\n  }\n\n  function paintWasPrice(list) {\n    var compare = headerCompareCents(list);\n    var was = document.querySelector('[data-df3d-was]');\n    var wrap = document.querySelector('[data-df3d-was-wrap]');\n    if (!compare) {\n      if (was) {\n        was.textContent = '';\n        was.setAttribute('hidden', '');\n      }\n      return;\n    }\n    if (was) {\n      was.textContent = money(compare);\n      was.removeAttribute('hidden');\n    } else if (wrap) {\n      wrap.innerHTML = '<s data-df3d-was style=\"text-decoration:line-through;opacity:.6;font-weight:400;margin-left:.45em\">' +\n        money(compare) + '</s>';\n    }\n  }\n\n  function paintPreviewUnitPrice() {\n    var el = document.querySelector('[data-volume-unit-price]');\n    if (!el) return;\n    var base = headerListCents();\n    if (!base) base = parseInt(el.getAttribute('data-volume-base-cents'), 10);\n    if (!base) base = 3999;\n    /* Write the live hex list price back so volume-tier.js discounts the same base. */\n    el.setAttribute('data-volume-base-cents', String(base));\n    var units = volumeUnits();\n    var shown = volumeUnitPrice(base, units);\n    el.textContent = money(shown);\n    var listEl = document.querySelector('[data-volume-list-price]');\n    if (listEl) {\n      listEl.textContent = money(base);\n      if (shown < base) listEl.removeAttribute('hidden');\n      else listEl.setAttribute('hidden', '');\n    }\n    paintWasPrice(base);\n    var notes = document.querySelectorAll('[data-volume-estimate]');\n    for (var i = 0; i < notes.length; i++) {\n      if (shown < base || volumeRung(units) > 0) notes[i].removeAttribute('hidden');\n      else notes[i].setAttribute('hidden', '');\n    }\n  }\n\n  function updateTotals() {\n    var n = totalTiles();\n    var t = previewHexTotal() + (standsWanted() * STAND_PRICE);\n    var label = root.querySelector('[data-total-label]');\n    if (label) label.textContent = n > 1 ? n + ' \u00D7 hexes \u00B7 ' + money(t) : money(t);\n    var btn = root.querySelector('[data-add]');\n    /* CLARITY HEX CRO: empty / invalid \u2014 do not leave a primary button that no-ops.\n     * Disabled + validate() string as the label. Volume preview still paints. */\n    if (btn) {\n      var problem = validate();\n      if (problem) {\n        btn.disabled = true;\n        btn.textContent = problem;\n      } else {\n        btn.disabled = false;\n        btn.textContent = 'ADD TO CART \u00B7 ' + money(t);\n      }\n    }\n    paintPreviewUnitPrice();\n    if (window.DF3DVolume && window.DF3DVolume.paintFromBuilder) window.DF3DVolume.paintFromBuilder();\n  }\n\n  /* Event delegation \u2014 one listener for the whole builder, so re-rendering rows never orphans\n   * handlers (the bug class that a React port most easily reintroduces by hand). */\n  root.addEventListener('click', function (e) {\n    var card = e.target.closest('[data-hex]');\n    var idx = card ? Number(card.dataset.hex) : -1;\n    var h = idx >= 0 ? hexes[idx] : null;\n\n    if (e.target.closest('[data-add-hex]')) {\n      // Opens the ADD picker rather than pushing one blank hex \u2014 multi-add is the point.\n      if (MODE === 'custom') return;   // control is never rendered here; ignore any stray click\n      ui.openFor = 'add'; ui.picked = []; ui.query = ''; render(); return;\n    }\n    /* \u2B50 CUSTOM MODE'S OWN ADD \u2014 one blank hex, NO picker. Handled up here, before the\n     * `if (!h) return` guard, for the same reason the ADD picker's clicks are: the add slot lives\n     * OUTSIDE every [data-hex] card, so that guard would swallow it. */\n    if (e.target.closest('[data-add-blank-hex]')) {\n      if (MODE === 'custom' && !singleRouteOnly() && hexes.length < MAX_HEXES) {\n        var lastHex = hexes[hexes.length - 1];\n        var nb = newHex('', lastHex ? lastHex.size : SEED_SIZE);\n        // \u2B50 Inherit the colour, matching the ADD picker: the buyer just chose it, and making them\n        // re-pick it on every row is the thing that made the old one-at-a-time flow tedious.\n        if (lastHex) {\n          nb.color = lastHex.color; nb.wantCustomColor = lastHex.wantCustomColor; nb.customColor = lastHex.customColor;\n        }\n        hexes.push(nb);\n        render();\n      }\n      return;\n    }\n    // \u26A0 The ADD picker lives OUTSIDE any [data-hex] card, so these must be handled before the\n    // `if (!h) return` guard or Done/Cancel/tick would all be swallowed there.\n    /* \u26A0 In preset mode the picker is always present, so its handlers must run whether or not\n     * ui.openFor was ever set. `!h` still scopes this to clicks OUTSIDE a hex card. */\n    /* \u2B50 OPENS THE ONE PICKER, and it must be handled HERE \u2014 before the `if (!h) return` guard \u2014\n     * because the collapsed button lives in [data-add-slot], which is a sibling of the hex list,\n     * not a child of any hex card. Handling it below that guard is why clicking the dropdown did\n     * nothing: the guard returned first, every time. */\n    if (e.target.closest('[data-openpicker]')) {\n      ui.openFor = 'add'; ui.picked = []; ui.query = ''; render(); return;\n    }\n    if ((ui.openFor === 'add' || MODE === 'preset') && !h) {\n      if (e.target.closest('[data-pickercancel]')) { ui.openFor = null; ui.picked = []; ui.query = ''; render(); return; }\n      var addPick = e.target.closest('[data-pick]');\n      if (addPick) {\n        var ah = addPick.dataset.pick;\n        /* \u26D4 ONE CONTROL, ONE JOB \u2014 changed 2026-08-19 after Josh hit the old behaviour twice.\n         * The per-hex picker used to be multi-select where the FIRST pick replaced this hex and\n         * the rest appended. That is genuinely surprising: you tick six expecting six, and the\n         * first one silently overwrites what you were looking at. It also meant the same control\n         * both changed and added, so neither meaning was ever safe to assume.\n         * \u2B50 Now: this picker CHANGES this hex, single pick, closes immediately. Adding is\n         * \"+ Add circuits\", which only ever adds. Two controls, two jobs, no overlap. */\n        if (ui.openFor !== 'add') {\n          var target = hexes[ui.openFor];\n          if (target) target.trackHandle = ah;\n          ui.openFor = null; ui.picked = []; ui.query = '';\n          render(); return;\n        }\n        var ai = ui.picked.indexOf(ah);\n        if (ai > -1) ui.picked.splice(ai, 1); else ui.picked.push(ah);\n        render(); return;\n      }\n      if (e.target.closest('[data-pickerdone]')) {\n        if (!ui.picked.length) return;\n        /* \u2B50 Done is now ADD-ONLY. The per-hex picker applies its single pick on click and never\n         * reaches here, so there is no replace branch left to get wrong. Colour is inherited from\n         * the LAST hex \u2014 the one the buyer was most recently looking at. */\n        /* \u26D4\u26D4 hexes CAN BE EMPTY since 2026-08-19, and hexes[-1] is undefined. Reading src.color off\n         * that THREW, which is why the Add button silently did nothing \u2014 the click handler died\n         * before it spliced anything in. Colour inheritance needs a source that always exists.\n         * \u2B50 Falls back to the same default newHex() uses, so the first added hex looks identical\n         * whether it is the first one or the seventh. */\n        var src = hexes.length ? hexes[hexes.length - 1]\n                               : { color: COLORS[2].value, wantCustomColor: false, customColor: '', size: SEED_SIZE };\n        var extras = ui.picked;\n        var room = MAX_HEXES - hexes.length;\n        var at = hexes.length;\n        extras.slice(0, room).forEach(function (handle, k) {\n          var nh = newHex(handle, src.size || SEED_SIZE);\n        /* \u26D4 If the circuit does not exist in the inherited size, drop to one it does have rather\n         * than creating a hex that cannot resolve. */\n        if (!productFor(baseOf(handle), nh.size)) {\n          var alt = sizesFor(baseOf(handle))[0];\n          if (alt) nh.size = alt;\n        }\n          // \u2B50 Inherit the colour so picking six after choosing blue gives six BLUE hexes.\n          nh.color = src.color; nh.wantCustomColor = src.wantCustomColor; nh.customColor = src.customColor;\n          hexes.splice(at + k, 0, nh);\n        });\n        /* Collapses after adding: the button that reopens it sits in the same place, so nothing is\n         * hidden and the page stays short. */\n        ui.openFor = null; ui.picked = []; ui.query = '';\n        render(); return;\n      }\n    }\n    if (!h) return;\n\n    /* \u2B50 Removing the LAST hex is allowed now: zero is a legal state and the picker above can\n     * always refill it. Blocking it was only correct while an empty set was unreachable. */\n    if (e.target.closest('[data-jumpgallery]')) {\n      var jumpTo = slideIndexFor(baseOf(h.trackHandle));\n      if (jumpTo >= 0 && GAL) {\n        GAL.dispatchEvent(new CustomEvent('df3d:gallery-show', { bubbles: true, detail: { index: jumpTo } }));\n        if (GAL.scrollIntoView) GAL.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n      }\n      return;\n    }\n    if (e.target.closest('[data-del]')) {\n      hexes.splice(idx, 1);\n      /* CLARITY HEX CRO: last-hex remove reopens the picker so Circuit Hex is never a dead empty set. */\n      if (!hexes.length && MODE === 'preset') ui.openFor = 'add';\n      render();\n      return;\n    }\n    /* \u26A0 'change' would be the idiomatic event for a checkbox, but this component already\n     * delegates every control through one click listener on root, and a second listener for one\n     * input is how handlers get orphaned by a re-render. Read .checked AFTER the click lands. */\n    /* The \"Add to every hex\" / \"Remove from every hex\" toggle lives OUTSIDE the hex list (it is a\n     * sibling of the builder), so it is handled on document, not root. Sets every hex to the same\n     * value rather than flipping each \u2014 a mixed selection should resolve to one obvious state. */\n    /* Size is a property of the hex, so this only ever rewrites state and re-renders \u2014 no\n     * navigation, which is the bug this whole change removes. */\n    var sizeBtn = e.target.closest('[data-size]');\n    if (sizeBtn) {\n      h.size = sizeBtn.dataset.size === 'large' ? 'large' : 'medium';\n      if (!standEligible(h)) h.stand = false;\n      render();\n      return;\n    }\n\n    var standBox = e.target.closest('[data-stand]');\n    if (standBox) { h.stand = !!standBox.checked; updateTotals(); return; }\n\n    if (e.target.closest('[data-dup]')) {\n      if (singleRouteOnly()) return;              // control is hidden here; ignore any stray click\n      // \u2B50 Duplicate so the SAME circuit can be ordered in a second colour \u2014 a real order shape\n      // that already worked but had no affordance. Inserted right after its source so the pair\n      // reads together; qty resets to 1 because duplicating a qty-5 row to change one colour\n      // should not silently double the order.\n      if (hexes.length < MAX_HEXES) {\n        var copy = JSON.parse(JSON.stringify(h)); copy.id = ++seq; copy.qty = 1;\n        hexes.splice(idx + 1, 0, copy); render();\n      }\n      return;\n    }\n    /* \u26D4 [data-openpicker] is NOT handled here. It lives in [data-add-slot], outside any hex card,\n     * so this point is unreachable for it \u2014 `if (!h) return` above bails first. Handled with the\n     * rest of the add-slot controls instead. That mistake shipped once: the dropdown rendered and\n     * clicking it did nothing. */\n    if (e.target.closest('[data-pickercancel]')) { ui.openFor = null; ui.picked = []; ui.query = ''; render(); return; }\n    var pick = e.target.closest('[data-pick]');\n    if (pick) {\n      var hnd = pick.dataset.pick, at = ui.picked.indexOf(hnd);\n      if (at > -1) ui.picked.splice(at, 1); else ui.picked.push(hnd);\n      render(); return;\n    }\n    /* \u26D4 THE Add HANDLER IS NOT HERE. It lives in the add-slot block ABOVE the `if (!h) return;`\n     * guard, because the picker is a SIBLING of the hex list \u2014 on an empty set h is undefined and\n     * this point is unreachable. That mistake has now been made twice in one night, once for\n     * [data-openpicker] and once for this. Anything that lives outside a hex card belongs up\n     * there; anything scoped to one hex belongs down here. */\n\n    var qtyBtn = e.target.closest('[data-qty]');\n    if (qtyBtn) {\n      var d = Number(qtyBtn.dataset.qty);\n      h.qty = Math.min(50, Math.max(1, qtyOf(h) + d));\n      render(); return;\n    }\n    var sw = e.target.closest('[data-color]');\n    if (sw) { h.color = sw.dataset.color; h.wantCustomColor = false; render(); return; }\n    if (e.target.closest('[data-customcolor]')) {\n      if (!OFFER_CUSTOM_COLOR) return;              // control is hidden; ignore any stray click\n      h.wantCustomColor = !h.wantCustomColor; render(); return;\n    }\n    var blank = e.target.closest('[data-blank]');\n    if (blank) { var n = blank.dataset.blank; h['blank' + n] = !h['blank' + n]; render(); return; }\n  });\n\n  root.addEventListener('input', function (e) {\n    if (e.target.hasAttribute('data-pickersearch')) {\n      ui.query = e.target.value;\n      render();\n      var box = root.querySelector('[data-pickersearch]');\n      if (box) { box.focus(); box.setSelectionRange(box.value.length, box.value.length); }\n      return;\n    }\n    var card = e.target.closest('[data-hex]');\n    if (!card) return;\n    var h = hexes[Number(card.dataset.hex)];\n    var field = e.target.dataset.field;\n    if (!h || !field) return;\n    h[field] = e.target.value;\n    // \u26A0 Only the total needs refreshing on keystroke \u2014 re-rendering would blur the field.\n    if (field === 'trackHandle') render(); else updateTotals();\n  });\n\n  var addToCartBtn = root.querySelector('[data-add]');\n  if (addToCartBtn) addToCartBtn.addEventListener('click', function (e) { e.preventDefault(); addToCart(); });\n\n  /* \u2B50 Stand-all toggle. Outside root, so it needs its own listener; it only ever writes hex state\n   * and calls render(), so it cannot drift from the per-hex checkboxes. */\n  document.addEventListener('click', function (e) {\n    if (!e.target.closest('[data-stand-all]')) return;\n    e.preventDefault();\n    if (!STAND_ID) return;\n    var elig = hexes.filter(standEligible);\n    if (!elig.length) return;\n    var allOn = elig.every(function (h) { return h.stand; });\n    elig.forEach(function (h) { h.stand = !allOn; });\n    render();\n  });\n\n  /* Label follows state so the control never lies about what it will do. */\n  function paintStandAll() {\n    var b = document.querySelector('[data-stand-all]');\n    if (!b || !STAND_ID) return;\n    var elig = hexes.filter(standEligible);\n    b.classList.toggle('hidden', elig.length === 0);\n    var allOn = elig.length > 0 && elig.every(function (h) { return h.stand; });\n    b.textContent = allOn ? 'Remove from every hex' : 'Add to every hex';\n  }\n\n  // The Edition picker lives outside this component; when it changes, prices and resolution move.\n  // \u26D4 render(), not just updateTotals(): the Edition decides whether the ENGRAVING FIELDS exist,\n  // and updateTotals() only rewrites the price line. Refreshing totals alone is what let a\n  // Personalized order reach checkout with nowhere to type Line 1/Line 2. See showText().\n  document.addEventListener('change', function (e) {\n    if (e.target && e.target.name === 'id') render();\n  });\n\n  /* \u2500\u2500 expose \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n  window.DF3D = {\n    render: render,\n    get hexes() { return hexes; },\n    newHex: newHex,\n    colors: COLORS,\n    ANY_COLOR: ANY_COLOR,\n    catalogue: CATALOGUE,\n    mode: MODE,\n    showText: showText,\n    maxHexes: MAX_HEXES,\n    qtyOf: qtyOf,\n    totalTiles: totalTiles,\n    /* Volume-tier: Standard AND Large/9in count. Stands are not in `hexes`. Colorways do not\n     * change the count. The retired Buy-5-Get-1 exclusion of Premium does not apply. */\n    eligibleTiles: function () {\n      return hexes.reduce(function (n, h) {\n        return n + qtyOf(h);\n      }, 0);\n    },\n    resolveHex: resolveHex,\n    sizesFor: sizesFor,\n    productFor: productFor,\n    hexProperties: hexProperties,\n    validate: validate,\n    addToCart: addToCart,\n    totalPrice: function () {\n      /* LIST total \u2014 what addToCart posts. The ATC button and title price use previewHexTotal()\n       * so Josh Preview can show unlocked-tier math before a Shopify discount is enabled.\n       * \u26A0 Do not point the button at this number once a volume rung is unlocked. */\n      return hexes.reduce(function (s, h) {\n        var r = resolveHex(h);\n        var cur = currentVariant();\n        return s + (r.price != null ? r.price : (cur ? cur.price : 0)) * qtyOf(h);\n      }, 0) + (standsWanted() * STAND_PRICE);\n    },\n    standsWanted: standsWanted\n  };\n  /* CLARITY HEX CRO: first paint \u2014 disable empty ATC / open picker before a visitor can tap a no-op.\n   * Liquid also calls render() on DOMContentLoaded; a second pass is cheap. */\n  render();\n})();\n"],
  "mappings": "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",
  "names": ["v", "h"]
}
