{
  "version": 3,
  "sources": ["/cdn/shop/t/111/assets/taste-profile-quiz.js?t=1785230339"],
  "sourcesContent": ["/**\n * Taste Profile Quiz\n * Helps users discover wines based on their taste preferences\n */\n\n// Version des regles de selection du kit, ecrite dans chaque commande (`_Kit algo`).\n// A changer a chaque modification de la selection, avec une entree dans CHANGELOG.md.\n// Kit choisi dans la bande 32,00-32,50 EUR, bouteilles <= 14 EUR, prix dans le score. Facture 29,90 EUR.\nconst KIT_ALGO_VERSION = '2026-07-30 bande-prix';\n\nclass TasteProfileQuiz {\n  constructor() {\n    console.log('\uD83C\uDF77 TasteProfileQuiz: Initializing...');\n    \n    this.currentQuestion = 1;\n    this.totalQuestions = 6;\n    this.targetX = 0;\n    this.targetY = 0;\n    this.targetZ = 0;\n    this.colorPreference = 'both'; // Q1: 'red', 'white', or 'both'\n    this.colorMixPreference = null;\n    this.answers = [];\n    this.quizSavePromise = null;\n    this.lastQuizSaveSignature = '';\n    this.isProcessing = false;\n    this.isPaidAdTraffic = this._detectPaidAdTraffic();\n    this.axisTweakActive = false;\n    this.axisUpdateRequestId = 0;\n    // Extra bottles upsell on the result page. The percentage here is display only: the\n    // real reduction comes from the \"Buy 1 Test Kit, get N at 20% off\" automatic discount.\n    // `upsellMaxBottles` MUST equal that discount's \"Customer gets\" quantity, otherwise the\n    // page shows -20% on a bottle Shopify will charge in full.\n    // Master on/off for the whole upsell.\n    // NOTE: this value differs per theme by deliberate manual push, not by runtime detection\n    // (Shopify.theme.role reports 'main' even under ?preview_theme_id, so it cannot tell dev\n    // from live).\n    // 2026-07-28 : coup\u00E9 partout (dev + live + repo). Six jours de campagne ads sur la page\n    // test-kit sans une seule vente, on change d'approche ; la remise -20% sur bouteilles\n    // suppl\u00E9mentaires sort du parcours en attendant. L'\u00E9tat complet de la campagne est fig\u00E9\n    // sur la branche `archive/ads-hero-testkit-20260728`.\n    // Pour r\u00E9activer : repasser \u00E0 `true` ici, et confirmer d'abord que la remise automatique\n    // Shopify \u00AB Buy 1 Test Kit, get N at 20% off \u00BB s'applique bien au checkout.\n    this.upsellEnabled = false;\n    this.upsellDiscountRate = 0.2;\n    this.upsellMaxBottles = 9;\n    this.upsellSwapOptions = 3;\n    // Keep every eligible candidate. Only the wines actually displayed are exclusive, so\n    // the pool just has to hold at least `upsellMaxBottles` wines for a full box. It is\n    // ranked by taste distance, so the deep end is only reached by a customer who already\n    // added many bottles.\n    this.upsellPoolSize = 40;\n    this._upsell = null;\n    this.distanceLookupCache = new Map();\n    this.calibrationConfig = {\n      tasteZoneRatio: 0.20,\n      weights: { x: 0.4, y: 0.4, intensity: 0.2 },\n      targetPerceptiveDistance: 10,\n      similarityWindows: [\n        { min: 8, max: 12 },\n        { min: 6, max: 14 },\n        { min: 5, max: 15 }\n      ],\n      axisDistanceFallbackWeight: 1,\n      // Lab similarity between the 3 bottles, in perceptive-distance units\n      // (similarity % = 100 - distance). Target 80-90%; 93% is the hard ceiling\n      // and 80% the hard floor, so the pack is never off-band in either direction.\n      labSimilarityBands: [\n        { min: 10, max: 20 },\n        { min: 7, max: 20 }\n      ],\n      labAnchorAttempts: 10,\n      labPoolLimit: 20\n    };\n    this.forceRetakeStorageKey = 'vinpop_force_retake';\n    \n    this.popup = document.getElementById('taste-profile-popup');\n    this.triggerBtn = document.getElementById('taste-profile-trigger-btn');\n    this.closeBtn = this.popup ? this.popup.querySelector('.taste-profile-close') : null;\n    this.overlay = this.popup ? this.popup.querySelector('.taste-profile-popup-overlay') : null;\n    this.progressFill = this.popup ? this.popup.querySelector('.taste-profile-progress-fill') : null;\n    this.progressText = this.popup ? this.popup.querySelector('.current-question') : null;\n    this.loadingEl = this.popup ? this.popup.querySelector('.taste-profile-loading') : null;\n    this.backBtn = this.popup ? this.popup.querySelector('.taste-profile-back-btn') : null;\n    // Question-set chooser (test-kit only). 'expert' shows the .tpq-expert wording of Q2-Q6;\n    // both sets carry identical x/y/z per answer, so scoring and the API never see the path.\n    this.pathChooser = this.popup ? this.popup.querySelector('.tpq-path') : null;\n    // Survives the results-page reload, so the add-to-cart can still tag the kit.\n    // null = unknown (e.g. a profile restored from the API), and then no tag is written.\n    this.quizPath = null;\n    try {\n      const storedPath = sessionStorage.getItem('vinpop_quiz_path');\n      if (storedPath === 'expert' || storedPath === 'guided') this.quizPath = storedPath;\n    } catch (e) {}\n    this.recommendedWines = null;\n    \n    console.log('\uD83C\uDF77 Elements found:', {\n      popup: !!this.popup,\n      triggerBtn: !!this.triggerBtn,\n      closeBtn: !!this.closeBtn,\n      backBtn: !!this.backBtn\n    });\n    \n    this.init();\n  }\n\n  // All paid ad traffic gets the free bottle, not just Meta: Google Ads sends the same\n  // cold visitors and used to be silently excluded from the offer.\n  _detectPaidAdTraffic() {\n    try {\n      const params = new URLSearchParams(window.location.search);\n      if (params.has('fbclid') || params.has('gclid') || params.has('gbraid')\n        || params.has('wbraid') || params.has('gad_source')) {\n        sessionStorage.setItem('wr_meta_traffic', '1');\n        return true;\n      }\n      const src = (params.get('utm_source') || '').toLowerCase();\n      if (['facebook', 'instagram', 'meta', 'fb', 'google', 'googleads', 'adwords'].includes(src)) {\n        sessionStorage.setItem('wr_meta_traffic', '1');\n        return true;\n      }\n      const medium = (params.get('utm_medium') || '').toLowerCase();\n      if (['cpc', 'ppc', 'paid', 'paidsocial', 'paid_social'].includes(medium)) {\n        sessionStorage.setItem('wr_meta_traffic', '1');\n        return true;\n      }\n      return sessionStorage.getItem('wr_meta_traffic') === '1';\n    } catch (e) {\n      return false;\n    }\n  }\n\n  init() {\n    if (!this.popup || !this.triggerBtn) {\n      console.error('\u274C TasteProfileQuiz: Required elements not found!');\n      return;\n    }\n    \n    console.log('\u2705 TasteProfileQuiz: All elements found, setting up events...');\n    // Inline mode: quiz is always visible on page\n    const isInline = this.popup.classList.contains('taste-profile-popup--inline');\n\n    if (this.canUseVinpopAuth()) {\n      const savedState = sessionStorage.getItem('vinpop_quiz_state');\n      if (savedState && !sessionStorage.getItem('vinpop_last_results')) {\n        console.log('TasteProfileQuiz: restoring in-progress quiz state');\n        this.restoreQuizState(JSON.parse(savedState));\n      } else if (this.isForceRetakeEnabled()) {\n        console.log('TasteProfileQuiz: force retake enabled, skipping API auto-restore');\n        this.openPopup();\n      } else {\n        this.restoreQuizFromApiOrStart(isInline);\n      }\n    } else {\n    // Check if there's an in-progress quiz to restore (e.g. language switch)\n    const savedState = sessionStorage.getItem('vinpop_quiz_state');\n    if (savedState && !sessionStorage.getItem('vinpop_last_results')) {\n      console.log('\uD83D\uDD04 Restoring quiz state (language switch)');\n      this.restoreQuizState(JSON.parse(savedState));\n    } else if (!sessionStorage.getItem('vinpop_last_results')) {\n      console.log('\uD83D\uDE80 Auto-starting quiz on page load');\n      this.openPopup();\n    } else {\n      console.log('\uD83D\uDCCB Quiz results already exist, skipping auto-open');\n      const isCalibrationKit = window.VinPopRoute && typeof window.VinPopRoute.isCalibrationKitPath === 'function'\n        ? window.VinPopRoute.isCalibrationKitPath(window.location.pathname)\n        : /\\/collections\\/(?:test-kit|tasting-kit)(?:\\/|$)/i.test(window.location.pathname);\n\n      if (isCalibrationKit) {\n        const savedResults = sessionStorage.getItem('vinpop_last_results');\n        if (savedResults) {\n          try {\n            const wines = JSON.parse(savedResults);\n            this.recommendedWines = wines;\n            this.reset();\n            // On page-load restore, keep the visitor at the top (the hero). Do not\n            // scroll down to the results or replay the reveal animation.\n            this.startWineRevealSequence(wines, { preserveScroll: true });\n          } catch (e) {\n            console.warn('\u26A0\uFE0F Could not restore calibration results:', e);\n          }\n        }\n        return;\n      }\n\n      // In inline mode, show a retake banner instead of hiding everything\n      if (isInline) {\n        // Hide quiz questions but show a retake prompt\n        const questionsEl = this.popup.querySelector('.taste-profile-questions');\n        const progressEl = this.popup.querySelector('.taste-profile-progress');\n        if (questionsEl) questionsEl.style.display = 'none';\n        if (progressEl) progressEl.style.display = 'none';\n\n        const isEN = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n        const retakeBanner = document.createElement('div');\n        retakeBanner.className = 'quiz-retake-banner';\n        retakeBanner.innerHTML = `\n          <p>${isEN ? 'You already completed the Test Kit quiz!' : 'Je hebt de Test Kit-quiz al ingevuld!'}</p>\n          <p class=\"quiz-retake-sub\">${isEN ? 'Your personalised results are shown below.' : 'Je persoonlijke resultaten staan hieronder voor je klaar.'}</p>\n          <div class=\"relative z-10 pb-button-shadow\" style=\"display:inline-block;margin:0 auto;\">\n            <button class=\"quiz-retake-btn push-btn\" type=\"button\">\n              <span class=\"push-btn__surface\">\n                ${isEN ? '\uD83D\uDD04 Retake the quiz' : '\uD83D\uDD04 Quiz opnieuw doen'}\n              </span>\n            </button>\n          </div>\n        `;\n        const contentEl = this.popup.querySelector('.taste-profile-popup-content');\n        if (contentEl) contentEl.appendChild(retakeBanner);\n\n        retakeBanner.querySelector('.quiz-retake-btn').addEventListener('click', () => {\n          this.setForceRetake(true);\n          localStorage.removeItem('vinpop-quiz-completed');\n          sessionStorage.removeItem('vinpop-quiz-completed');\n          sessionStorage.removeItem('vinpop_last_results');\n          sessionStorage.removeItem('vinpop_taste_profile');\n          sessionStorage.removeItem('tasteProfileResults');\n          sessionStorage.removeItem('vinpop_quiz_state');\n          this.quizSavePromise = null;\n          this.lastQuizSaveSignature = '';\n          this._reloadToQuizTop();\n        });\n      }\n    }\n    }\n    \n    // Event listeners\n    this.triggerBtn.addEventListener('click', () => this.openPopup());\n    if (this.closeBtn) this.closeBtn.addEventListener('click', () => this.closePopup());\n    if (this.overlay) this.overlay.addEventListener('click', () => this.closePopup());\n    if (this.backBtn) {\n      this.backBtn.addEventListener('click', () => this.goBack());\n    }\n    // Intro start button\n    const introStartBtn = this.popup.querySelector('.quiz-intro-start-btn');\n    if (introStartBtn) {\n      introStartBtn.addEventListener('click', () => {\n        this.setForceRetake(false);\n        this.isProcessing = false;\n        if (this.loadingEl) this.loadingEl.style.display = 'none';\n        if (this.pathChooser) {\n          this.showPathChooser();\n          return;\n        }\n        this.startQuestions();\n      });\n\n      // Elements that look clickable and must behave like the intro CTA (test-kit step 1).\n      this.popup.querySelectorAll('[data-quiz-start]').forEach(el => {\n        el.addEventListener('click', () => introStartBtn.click());\n      });\n    }\n\n    if (this.pathChooser) {\n      this.pathChooser.querySelectorAll('[data-quiz-path]').forEach(btn => {\n        btn.addEventListener('click', () => {\n          this.setQuizPath(btn.dataset.quizPath);\n          this.startQuestions();\n        });\n      });\n    }\n\n    // Answer buttons\n    const answerButtons = this.popup.querySelectorAll('.taste-profile-answer');\n    answerButtons.forEach(btn => {\n      if (btn.classList.contains('taste-profile-answer--mix')) return;\n      btn.addEventListener('click', (e) => this.handleAnswer(e));\n      if (btn.getAttribute('role') === 'button') {\n        btn.addEventListener('keydown', (e) => {\n          if (e.key !== 'Enter' && e.key !== ' ') return;\n          if (e.target && e.target.closest('select')) return;\n          e.preventDefault();\n          this.handleAnswer(e);\n        });\n      }\n    });\n    this.initColorMixControls();\n    \n    // Keyboard navigation (only for popup mode)\n    if (!isInline) {\n      document.addEventListener('keydown', (e) => {\n        if (this.popup.style.display === 'block') {\n          if (e.key === 'Escape') this.closePopup();\n        }\n      });\n    }\n  }\n\n  canUseVinpopAuth() {\n    return !!(\n      this.getMetaContent('customer-id') &&\n      this.getMetaContent('friends-auth-timestamp') &&\n      this.getMetaContent('friends-auth-signature')\n    );\n  }\n\n  getPaidDisplayPrice(product) {\n    if (!product) return 0;\n    const variants = Array.isArray(product.variants) ? product.variants : [];\n    const availablePaid = variants.find(v => v && v.available && Number(v.price) > 0);\n    if (availablePaid) return Number(availablePaid.price) || 0;\n    const anyPaid = variants.find(v => v && Number(v.price) > 0);\n    if (anyPaid) return Number(anyPaid.price) || 0;\n    return Number(product.price) || 0;\n  }\n\n  getDesiredColorMix() {\n    if (this.colorPreference === 'red') return { red: 3, white: 0 };\n    if (this.colorPreference === 'white') return { red: 0, white: 3 };\n    if ((this.colorPreference === 'mix' || this.colorPreference === 'both') && this.colorMixPreference) {\n      const rawRed = parseInt(this.colorMixPreference.red, 10);\n      const red = rawRed >= 2 ? 2 : 1;\n      const white = 3 - red;\n      if (red + white === 3) return { red, white };\n    }\n    // 'both' with no explicit mix yet: honour the 2 red + 1 white the panel shows.\n    if (this.colorPreference === 'both') return { red: 2, white: 1 };\n    return null;\n  }\n\n  normalizeColorMixPreference(redCount) {\n    const parsedRed = parseInt(redCount, 10);\n    const red = Math.max(0, Math.min(3, Number.isFinite(parsedRed) ? parsedRed : 1));\n    return {\n      red,\n      white: 3 - red\n    };\n  }\n\n  initColorMixControls() {\n    const mixButtons = this.popup.querySelectorAll('.taste-profile-answer--mix');\n    mixButtons.forEach(button => {\n      const dropdown = button.querySelector('[data-mix-dropdown]');\n      const trigger = button.querySelector('[data-mix-trigger]');\n      const options = Array.from(button.querySelectorAll('[data-mix-red-count]'));\n      const totalEl = button.querySelector('[data-mix-total]');\n      if (!dropdown || !trigger || !options.length) return;\n      let selectedRed = 0;\n\n      const syncMix = () => {\n        const red = Math.max(0, Math.min(3, parseInt(selectedRed, 10) || 0));\n        const white = 3 - red;\n        button.dataset.redCount = String(red);\n        button.dataset.whiteCount = String(white);\n        const total = red + white;\n        if (totalEl) totalEl.textContent = `${total} / 3`;\n        return total;\n      };\n\n      trigger.addEventListener('click', event => {\n        event.preventDefault();\n        event.stopPropagation();\n        dropdown.classList.toggle('is-open');\n      });\n\n      options.forEach(option => {\n        option.addEventListener('click', event => {\n          event.preventDefault();\n          event.stopPropagation();\n          selectedRed = parseInt(option.dataset.mixRedCount, 10) || 0;\n          trigger.textContent = option.textContent.trim();\n          dropdown.classList.remove('is-open');\n          if (syncMix() === 3) this.handleAnswer({ currentTarget: button });\n        });\n      });\n\n      button.addEventListener('click', event => {\n        event.stopPropagation();\n        if (!event.target.closest('[data-mix-dropdown]')) {\n          dropdown.classList.remove('is-open');\n        }\n      });\n      syncMix();\n    });\n  }\n\n  getWineColorCode(wine, catalog = window.shopCatalog || {}) {\n    const wineColor = String(wine?.color || '').toUpperCase();\n    if (wineColor === 'R' || wineColor === 'W') return wineColor;\n    const wineId = String(wine?.wine_id || wine?.id || '');\n    if (wineId === '15513213370755') return 'W';\n    if (wineId === '15510612148611') return 'R';\n    const product = catalog[wineId];\n    const tags = Array.isArray(product?.tags) ? product.tags.join(' ') : '';\n    const text = [\n      product?.type,\n      product?.wineColor,\n      product?.productType,\n      product?.title,\n      product?.handle,\n      tags\n    ].filter(Boolean).join(' ').toLowerCase();\n    if (/\\b(w|white|wit|blanc|bianco|wei\u00DF|weiss)\\b/.test(text) || text.includes('white wine') || text.includes('witte wijn')) return 'W';\n    if (/\\b(r|red|rood|rouge|rosso|rot)\\b/.test(text) || text.includes('red wine') || text.includes('rode wijn')) return 'R';\n    return '';\n  }\n\n  isWineAllowedForColorPreference(wine, catalog = window.shopCatalog || {}) {\n    if (this.colorPreference !== 'red' && this.colorPreference !== 'white') return true;\n    const code = this.getWineColorCode(wine, catalog);\n    if (this.colorPreference === 'red') return code === 'R';\n    if (this.colorPreference === 'white') return code === 'W';\n    return true;\n  }\n\n  getWineProductKey(wine) {\n    return String(wine?.wine_id || wine?.product_id || wine?.id || '');\n  }\n\n  uniqueWinesByProduct(wines) {\n    const seen = new Set();\n    return (Array.isArray(wines) ? wines : []).filter(wine => {\n      const key = this.getWineProductKey(wine);\n      if (!key || seen.has(key)) return false;\n      seen.add(key);\n      return true;\n    });\n  }\n\n  // Used by the calibration bonus bottle and by the paid-ad free bottle: both ship as a\n  // dedicated 0\u20AC variant, so the discount can never leak onto a normally sold bottle.\n  getFreeBonusVariantIdByProductId(productId) {\n    const pid = String(productId || '');\n    if (pid === '15513213370755') return 58080491012483; // white bonus product -> 0\u20AC variant\n    if (pid === '15510612148611') return 58080438649219; // red bonus product -> 0\u20AC variant\n    return null;\n  }\n\n  // A visitor who left their email to save the offer comes back tagged, so the same colour\n  // of bottle is honoured on the next visit even outside a paid-ad click.\n  getGiftProductIdFromCustomerTags(catalog = window.shopCatalog || {}) {\n    const tags = (window.VinpopCustomerData && Array.isArray(window.VinpopCustomerData.tags))\n      ? window.VinpopCustomerData.tags.map(tag => String(tag || '').trim().toLowerCase())\n      : [];\n    try {\n      const savedGiftTag = String(localStorage.getItem('vinpop_saved_gift_tag') || '').trim().toLowerCase();\n      if (savedGiftTag) tags.push(savedGiftTag);\n    } catch(e) {}\n    const taggedId = tags.includes('white gift')\n      ? '15513213370755'\n      : tags.includes('red gift')\n        ? '15510612148611'\n        : '';\n    if (!taggedId || !catalog[taggedId]) return '';\n    // The 0\u20AC variant requirement is gone with the variant itself: the gift is a line item\n    // property now, so any bottle can be given and the tag only has to name a real product.\n    if (!this.isWineAllowedForColorPreference({ wine_id: taggedId }, catalog)) return '';\n    return taggedId;\n  }\n\n  // Only the two bonus products can be gifted, because only they carry a 0\u20AC variant.\n  // Cheapest first, and never a bottle already in the kit.\n  // The one place the pack price band is decided, so the two callers of\n  // `buildCalibrationBottleSelection()` can no longer drift apart.\n  //\n  // Two rules from the offer, 2026-07-30:\n  //   - the PAID bottles alone must always reach \u20AC32,00, so the box reads as a deal before the\n  //     gift is even counted;\n  //   - the paid bottles PLUS the gift must never exceed \u20AC40,00.\n  // The gift is a real bottle with a real price and it already counts in the retail value, so\n  // the ceiling on the paid pack is \u20AC40,00 minus whatever the gift actually costs. That price\n  // is knowable up front: `reserveForFreeGift` keeps both bonus bottles out of the kit, so the\n  // gift never depends on which wines the pack ended up with.\n  //\n  // Consequence to know: with the \u20AC6,49 gift the paid band is \u20AC32,00 to \u20AC33,51, only \u20AC1,51\n  // wide but still 1233 valid trios. With the \u20AC7,99 gift it collapses to \u20AC0,01 and the\n  // selection will fall back to its closest-match pack, which CAN exceed \u20AC40,00. The pair of\n  // numbers therefore holds only while the \u20AC6,49 bottle is in stock.\n  getPackPriceBand(catalog = window.shopCatalog || {}, isCatalogProductInStock = () => true) {\n    const PAID_FLOOR = 3200;\n    const PACK_TOLERANCE = 50;\n    return {\n      min: PAID_FLOOR,\n      max: PAID_FLOOR + PACK_TOLERANCE,\n      totalCeiling: PAID_FLOOR + PACK_TOLERANCE,\n      giftBudget: 0,\n      reserveId: '',\n      reserveCents: 0\n    };\n  }\n\n  // The bottle held back so there is ALWAYS a gift to give.\n  //\n  // Without it the guarantee breaks in a way that is easy to miss: the pack is free to take the\n  // cheapest wine, and the leftover budget (ceiling minus pack) can then be smaller than every\n  // remaining bottle, leaving nothing giftable. Reserving the cheapest one closes that hole,\n  // because the band already reserves exactly its price, so it always fits the leftover budget.\n  // This is the same job the old `reserveForFreeGift` did for the two 0\u20AC products, generalised.\n  getGiftReserve(catalog = window.shopCatalog || {}, isCatalogProductInStock = () => true) {\n    let reserve = { id: '', cents: 0 };\n    let cheapest = Infinity;\n    Object.keys(catalog).forEach(id => {\n      const product = catalog[id];\n      if (!product || !isCatalogProductInStock(product)) return;\n      if (!this.isWineAllowedForColorPreference({ wine_id: id }, catalog)) return;\n      const price = this.getPaidDisplayPrice(product);\n      if (price > 0 && price < cheapest) {\n        cheapest = price;\n        reserve = { id: String(id), cents: price };\n      }\n    });\n    return reserve;\n  }\n\n  // The gifted bottle, chosen from the WHOLE catalogue.\n  //\n  // 2026-07-30 : the gift no longer needs a dedicated 0\u20AC variant, because it is now a line item\n  // PROPERTY on the Test Kit exactly like the paid bottles, not a cart line of its own. The\n  // customer buys one product at \u20AC29,90 and the properties tell the warehouse what to pack, so\n  // nothing is ever charged for a wine and the page can never disagree with the receipt.\n  //\n  // Consequence, accepted: the gift stops decrementing Shopify inventory. It was the only\n  // bottle in the box that did; the three paid ones never have, since they are properties too.\n  // Stock is updated by hand after each order, so the box is now consistent instead of having\n  // three untracked bottles and one tracked.\n  //\n  // There must ALWAYS be a gift, so this can only return '' when the catalogue has nothing at\n  // all in stock. `budgetCents` caps its retail value so the box respects the \u20AC40,00 total, and\n  // `getPackPriceBand()` has already reserved room for the cheapest bottle, which guarantees at\n  // least one candidate fits. Within the budget the closest taste match wins, so the gift stops\n  // being \"whatever was cheapest\" as soon as the ceiling leaves any choice.\n  chooseGiftProductId({ excludedIds, budgetCents, catalog = window.shopCatalog || {}, isCatalogProductInStock = () => true, rankedCandidates = [] } = {}) {\n    const excluded = excludedIds instanceof Set ? excludedIds : new Set(excludedIds || []);\n    const budget = Number(budgetCents) > 0 ? Number(budgetCents) : Infinity;\n    const isEligible = (id, cap) => {\n      const product = catalog[id];\n      if (!product || excluded.has(String(id))) return false;\n      const price = this.getPaidDisplayPrice(product);\n      if (!(price > 0) || price > cap) return false;\n      if (!isCatalogProductInStock(product)) return false;\n      return this.isWineAllowedForColorPreference({ wine_id: id }, catalog);\n    };\n\n    const pick = (cap) => {\n      // `rankedCandidates` is the scored, taste-distance-sorted list the kit was chosen from, so\n      // the first eligible entry is the closest match the cap allows.\n      for (const wine of rankedCandidates) {\n        const id = String(wine?.wine_id || '');\n        if (id && isEligible(id, cap)) return id;\n      }\n      // Nothing scored fits, so take the cheapest bottle the cap allows.\n      return Object.keys(catalog)\n        .filter(id => isEligible(id, cap))\n        .sort((a, b) => this.getPaidDisplayPrice(catalog[a]) - this.getPaidDisplayPrice(catalog[b]))[0] || '';\n    };\n\n    // The gift is a promise, the \u20AC40,00 total is a target. If the budget covers nothing, give\n    // the cheapest bottle anyway and let the total run over rather than ship a box with no gift.\n    // This only fires when the pack itself already broke its band through `findPricedPack`'s\n    // closest-match fallback, so it is rare, but \"always a free bottle\" has to mean always.\n    return pick(budget) || pick(Infinity);\n  }\n\n  // 2026-07-30 : PLUS APPEL\u00C9, remplac\u00E9 par `chooseGiftProductId()`. Il ne savait offrir que les\n  // deux produits portant une variante \u00E0 0 \u20AC, contrainte disparue avec la variante elle-m\u00EAme.\n  // Conserv\u00E9 le temps de v\u00E9rifier une commande r\u00E9elle, \u00E0 supprimer ensuite avec\n  // `getFreeBonusVariantIdByProductId()` si la bouteille de calibration passe aussi en propri\u00E9t\u00E9.\n  chooseCheapestFreeGiftProductId(excludedIds, catalog = window.shopCatalog || {}, isCatalogProductInStock = () => true) {\n    const excluded = excludedIds instanceof Set ? excludedIds : new Set(excludedIds || []);\n    const bonusIds = ['15513213370755', '15510612148611'];\n    return bonusIds\n      .map(id => {\n        const product = catalog[id];\n        return {\n          id,\n          product,\n          price: product ? this.getPaidDisplayPrice(product) : Infinity\n        };\n      })\n      .filter(item =>\n        item.product &&\n        !excluded.has(item.id) &&\n        this.getFreeBonusVariantIdByProductId(item.id) &&\n        this.isWineAllowedForColorPreference({ wine_id: item.id }, catalog) &&\n        isCatalogProductInStock(item.product)\n      )\n      .sort((a, b) => a.price - b.price || a.id.localeCompare(b.id))[0]?.id || '';\n  }\n\n  getCalibrationAxisContext(wines) {\n    const values = (key) => wines.map(w => parseFloat(w[key])).filter(Number.isFinite);\n    const xs = values('map_x');\n    const ys = values('map_y');\n    const is = values('map_intensity');\n    const minMax = (arr) => {\n      const min = Math.min.apply(null, arr);\n      const max = Math.max.apply(null, arr);\n      return { min, max, range: (max - min) || 1, center: (min + max) / 2 };\n    };\n    const x = minMax(xs.length ? xs : [0, 1]);\n    const y = minMax(ys.length ? ys : [0, 1]);\n    const intensity = minMax(is.length ? is : [0, 1]);\n    const quizRange = 1.2;\n    const customer = {\n      x: x.center + (this.targetX / quizRange) * (x.range / 2),\n      y: y.center + (this.targetY / quizRange) * (y.range / 2),\n      intensity: intensity.center + (this.targetZ / quizRange) * (intensity.range / 2)\n    };\n    const cfg = this.calibrationConfig;\n    return {\n      ranges: { x, y, intensity },\n      customer,\n      tasteZone: {\n        xMin: customer.x - cfg.tasteZoneRatio * x.range,\n        xMax: customer.x + cfg.tasteZoneRatio * x.range,\n        yMin: customer.y - cfg.tasteZoneRatio * y.range,\n        yMax: customer.y + cfg.tasteZoneRatio * y.range,\n        intensityMin: customer.intensity - cfg.tasteZoneRatio * intensity.range,\n        intensityMax: customer.intensity + cfg.tasteZoneRatio * intensity.range\n      }\n    };\n  }\n\n  decorateWineForCalibration(wine, context) {\n    const cfg = this.calibrationConfig;\n    const wx = parseFloat(wine.map_x) || 0;\n    const wy = parseFloat(wine.map_y) || 0;\n    const wi = parseFloat(wine.map_intensity) || 0;\n    const dx = (wx - context.customer.x) / context.ranges.x.range;\n    const dy = (wy - context.customer.y) / context.ranges.y.range;\n    const di = (wi - context.customer.intensity) / context.ranges.intensity.range;\n    const axisDistance = Math.sqrt(\n      cfg.weights.x * Math.pow(dx, 2) +\n      cfg.weights.y * Math.pow(dy, 2) +\n      cfg.weights.intensity * Math.pow(di, 2)\n    );\n    const insideTasteZone =\n      Math.abs(wx - context.customer.x) <= cfg.tasteZoneRatio * context.ranges.x.range &&\n      Math.abs(wy - context.customer.y) <= cfg.tasteZoneRatio * context.ranges.y.range &&\n      Math.abs(wi - context.customer.intensity) <= cfg.tasteZoneRatio * context.ranges.intensity.range;\n    return { ...wine, _axisDistance: axisDistance, _insideTasteZone: insideTasteZone };\n  }\n\n  async getPerceptiveDistanceMap(productId) {\n    const id = String(productId || '');\n    if (!id) return new Map();\n    if (this.distanceLookupCache.has(id)) return this.distanceLookupCache.get(id);\n    try {\n      const response = await fetch('https://api.vinpop.fr/api/wines/getDistance', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ id })\n      });\n      if (!response.ok) throw new Error(`Distance lookup failed for ${id}`);\n      const rows = await response.json();\n      const map = new Map();\n      (Array.isArray(rows) ? rows : []).forEach(row => {\n        const id1 = String(row.productid1 || '');\n        const id2 = String(row.productid2 || '');\n        const otherId = id1 === id ? id2 : id1;\n        const distance = Number(row.perceptive_distance ?? row.distance);\n        if (otherId && Number.isFinite(distance)) map.set(otherId, distance);\n      });\n      this.distanceLookupCache.set(id, map);\n      return map;\n    } catch (error) {\n      console.warn('\u26A0\uFE0F Could not fetch perceptive distances:', error);\n      const empty = new Map();\n      this.distanceLookupCache.set(id, empty);\n      return empty;\n    }\n  }\n\n  getDistanceBetweenSelected(distanceMap, wine) {\n    return distanceMap.get(String(wine?.wine_id || ''));\n  }\n\n  similarityPctFromDistance(distance) {\n    return Number.isFinite(Number(distance)) ? Math.max(0, Math.min(100, Math.round(100 - Number(distance)))) : null;\n  }\n\n  // On the taste map the customer must sit INSIDE the triangle of the 3 bottles,\n  // not off to one side. Mathematically that holds when the three bearings seen\n  // from the customer leave no gap wider than 180 degrees; a gap near 120 gives\n  // the balanced triangle. Returns a penalty (0 = perfect) to add to the score.\n  buildTasteMapGeometryScorer(context) {\n    const customer = context?.customer;\n    const xRange = context?.ranges?.x?.range || 0;\n    const yRange = context?.ranges?.y?.range || 0;\n    if (!customer || !xRange || !yRange) return () => 0;\n\n    const bearingOf = (wine) => {\n      const dx = ((parseFloat(wine?.map_x) || 0) - customer.x) / xRange;\n      const dy = ((parseFloat(wine?.map_y) || 0) - customer.y) / yRange;\n      if (!dx && !dy) return null;\n      return { angle: Math.atan2(dy, dx) * (180 / Math.PI), dx, dy };\n    };\n\n    return (pack) => {\n      const vectors = pack.map(bearingOf);\n      if (vectors.some(vector => !vector)) return 1;\n\n      const angles = vectors.map(vector => (vector.angle + 360) % 360).sort((a, b) => a - b);\n      const gaps = [\n        angles[1] - angles[0],\n        angles[2] - angles[1],\n        360 - (angles[2] - angles[0])\n      ];\n      const widestGap = Math.max(...gaps);\n\n      // Customer outside the triangle: every wine sits on the same side.\n      const enclosesCustomer = widestGap < 180;\n      const spreadPenalty = Math.min(1, Math.abs(widestGap - 120) / 120);\n\n      // How far the triangle's centre of mass drifts from the customer.\n      const centroidOffset = Math.hypot(\n        vectors.reduce((sum, vector) => sum + vector.dx, 0) / 3,\n        vectors.reduce((sum, vector) => sum + vector.dy, 0) / 3\n      );\n\n      return (enclosesCustomer ? 0 : 4) + (spreadPenalty * 2) + (centroidOffset * 4);\n    };\n  }\n\n  // Picks the 3 bottles so that every pair stays inside the lab similarity band.\n  // Returns null when the lab data cannot back a pack, so the caller can fall back.\n  async selectCalibrationTrioByLabSimilarity(candidates, options = {}) {\n    const cfg = this.calibrationConfig;\n    const bands = cfg.labSimilarityBands || [];\n    if (candidates.length < 3 || !bands.length) return null;\n\n    const requiredMix = options.requiredMix || null;\n    const matchesRequiredMix = options.matchesRequiredMix || (() => true);\n    const packTotal = options.packTotal || (() => 0);\n    const minPackPrice = Number.isFinite(options.minPackPrice) ? options.minPackPrice : 0;\n    const maxPackPrice = Number.isFinite(options.maxPackPrice) ? options.maxPackPrice : Infinity;\n    const targetTotal = (minPackPrice + maxPackPrice) / 2;\n    const idOf = (wine) => String(wine?.wine_id || '');\n    const anchors = candidates.slice(0, cfg.labAnchorAttempts || 3);\n    const geometryScore = this.buildTasteMapGeometryScorer(options.context);\n\n    for (const band of bands) {\n      let bandBest = null;\n      const inBand = (distance) => Number.isFinite(distance) && distance >= band.min && distance <= band.max;\n      // The lab never compares a red to a white, so a red/white pair has no\n      // distance at all. Enforce the band on the pairs it does measure, and let\n      // a cross-colour pair through untested rather than rejecting the pack.\n      const pairHolds = (wineA, wineB, distance) => (\n        Number.isFinite(distance)\n          ? inBand(distance)\n          : this.getWineColorCode(wineA) !== this.getWineColorCode(wineB)\n      );\n\n      for (const anchor of anchors) {\n        const distanceFromAnchor = await this.getPerceptiveDistanceMap(idOf(anchor));\n        if (!distanceFromAnchor.size) continue;\n\n        const pool = candidates\n          .filter(wine => idOf(wine) !== idOf(anchor) && pairHolds(anchor, wine, distanceFromAnchor.get(idOf(wine))))\n          .slice(0, cfg.labPoolLimit || 14);\n        if (pool.length < 2) continue;\n\n        // One call per wine returns its distance to every other wine, so this\n        // gives the full pairwise matrix inside the pool.\n        const poolMaps = new Map();\n        await Promise.all(pool.map(async (wine) => {\n          poolMaps.set(idOf(wine), await this.getPerceptiveDistanceMap(idOf(wine)));\n        }));\n\n        let best = null;\n        for (let i = 0; i < pool.length; i += 1) {\n          for (let j = i + 1; j < pool.length; j += 1) {\n            const bottleB = pool[i];\n            const bottleC = pool[j];\n            if (!pairHolds(bottleB, bottleC, poolMaps.get(idOf(bottleB))?.get(idOf(bottleC)))) continue;\n\n            const pack = [anchor, bottleB, bottleC];\n            if (!matchesRequiredMix(pack, requiredMix)) continue;\n\n            const total = packTotal(pack);\n            const pricePenalty = total < minPackPrice\n              ? (minPackPrice - total) / 100\n              : total > maxPackPrice\n                ? (total - maxPackPrice) / 100\n                : 0;\n            const score =\n              (anchor._axisDistance * 3) +\n              bottleB._axisDistance +\n              bottleC._axisDistance +\n              (Math.abs(total - targetTotal) / 1000) +\n              (pricePenalty * 10) +\n              geometryScore(pack);\n\n            if (!best || score < best.score) best = { pack, score };\n          }\n        }\n\n        // Keep searching the other anchors: an anchor further from the customer\n        // often wraps the trio around them, which is what the map must show.\n        if (best && (!bandBest || best.score < bandBest.score)) bandBest = best;\n      }\n\n      // Bottle 1 must still read as the closest match, so order by taste distance.\n      if (bandBest) return [...bandBest.pack].sort((a, b) => a._axisDistance - b._axisDistance);\n    }\n\n    return null;\n  }\n\n  addCalibrationMetadata(wine, role, index, isEN, extras = {}) {\n    const copy = {\n      closest_match: {\n        title: isEN ? 'Your safest bet' : 'Je veiligste keuze',\n        text: isEN\n          ? 'Of all the wines in our lab, this one sits closest to the answers you gave.'\n          : 'Van alle wijnen in ons lab ligt deze het dichtst bij de antwoorden die je gaf.'\n      },\n      same_direction_variation: {\n        title: isEN ? 'One small step sideways' : 'E\u00E9n stapje opzij',\n        text: isEN\n          ? 'Same taste family, slightly different. If you love this one too, we know your taste is wide.'\n          : 'Dezelfde smaakfamilie, net iets anders. Vind je deze ook lekker, dan weten we dat je smaak breed is.'\n      },\n      // 2026-07-30 : RETIR\u00C9 du parcours. Trois clients n'ont pas aim\u00E9 cette bouteille, celle\n      // qui \u00E9tait choisie pour \u00EAtre la plus \u00E9loign\u00E9e de leurs r\u00E9ponses. La copie reste ici\n      // parce qu'un `vinpop_calibration_recommendation` d\u00E9j\u00E0 en sessionStorage peut encore\n      // porter ce r\u00F4le, et `copy[role]` l\u00E8verait une exception s'il disparaissait.\n      // Cons\u00E9quence assum\u00E9e : la box n'apprend plus o\u00F9 est la limite de go\u00FBt du client. Cette\n      // fonction de calibration doit se d\u00E9placer vers les notes Love / Like / Dislike apr\u00E8s\n      // livraison, o\u00F9 une d\u00E9ception ne co\u00FBte pas une bouteille sur quatre.\n      controlled_exploration: {\n        title: isEN ? 'One safe step further' : 'E\u00E9n veilige stap verder',\n        text: isEN\n          ? 'Still inside your taste zone, but the furthest one in your box. It tells us where your limit is.'\n          : 'Nog steeds binnen jouw smaakzone, maar de verste in je box. Die vertelt ons waar jouw grens ligt.'\n      },\n      second_safe_match: {\n        title: isEN ? 'Another safe choice' : 'Nog een veilige keuze',\n        text: isEN\n          ? 'Also very close to your answers. There is no risky bottle in this box.'\n          : 'Ook heel dicht bij je antwoorden. Er zit geen risicofles in deze box.'\n      },\n      third_safe_match: {\n        title: isEN ? 'One more safe choice' : 'Nog een veilige keuze erbij',\n        text: isEN\n          ? 'A fourth bottle, picked the same way as the others: inside your taste zone, never outside it.'\n          : 'Een vierde fles, op dezelfde manier gekozen als de rest: binnen je smaakzone, nooit erbuiten.'\n      }\n    };\n    return {\n      ...wine,\n      _calibrationRole: role,\n      _calibrationIndex: index,\n      reason_title: copy[role].title,\n      reason_text: copy[role].text,\n      axis_distance_to_customer: wine._axisDistance,\n      ...extras\n    };\n  }\n\n  // Demo override, for comparing the two outcomes on a preview theme without having to guess\n  // which quiz answers produce which box: `?wr_pack=3` forces the three-bottle pass and\n  // `?wr_pack=4` forces the four-bottle one. Anything else, including no parameter at all,\n  // leaves the normal behaviour untouched, which is why this is safe to ship.\n  getForcedPackSize() {\n    try {\n      const raw = new URLSearchParams(window.location.search).get('wr_pack');\n      const size = parseInt(raw, 10);\n      return size === 3 || size === 4 ? size : null;\n    } catch (error) {\n      return null;\n    }\n  }\n\n  // `?wr_debug=1` turns on the offer breakdown in the console. Read-only, prints nothing when\n  // absent, so it is safe to ship next to the real page.\n  isOfferDebugEnabled() {\n    try {\n      return new URLSearchParams(window.location.search).get('wr_debug') === '1';\n    } catch (error) {\n      return false;\n    }\n  }\n\n  // Companion override for the total ceiling, in euros (`?wr_cap=42`) or cents (`?wr_cap=4200`).\n  // Exists so a box the live \u20AC40,00 ceiling makes impossible can still be previewed and judged\n  // before the ceiling is actually moved. Clamped to 3200-6000 and null when absent.\n  getForcedTotalCeiling() {\n    try {\n      const raw = new URLSearchParams(window.location.search).get('wr_cap');\n      if (!raw) return null;\n      const value = Number(String(raw).replace(',', '.'));\n      if (!Number.isFinite(value) || value <= 0) return null;\n      const cents = value < 200 ? Math.round(value * 100) : Math.round(value);\n      return cents >= 3200 && cents <= 6000 ? cents : null;\n    } catch (error) {\n      return null;\n    }\n  }\n\n  // The optional fourth paid bottle. Two hard rules, both from the offer rather than the\n  // maths: it must sit in the taste zone (`_insideTasteZone`) and be closer to the customer\n  // than the exploration bottle. A fourth bottle only ever fits when the wines matching the\n  // customer's answers are cheap, so filling this slot with exploration would ship a wine\n  // that is both cheap and far from those answers: the single bottle most likely to sour a\n  // first order and kill the repeat purchase.\n  //\n  // 2026-07-30 : ENCORE DORMANTE, et il manque UNE seule chose, document\u00E9e ici.\n  //\n  // Les deux vins les moins chers du catalogue (6,49 et 7,99) sont exactement les deux\n  // bouteilles cadeaux, que `reserveForFreeGift` exclut des candidats du kit. Le vin\n  // \u00E9ligible le moins cher est donc \u00E0 8,25. Avec MAX_PACK_PRICE = 3800, loger une 4e exige\n  // donc un trio \u00E0 29,75 au plus, alors que le trio est s\u00E9lectionn\u00E9 dans la bande\n  // 3000-3800. Les fen\u00EAtres ne se croisent pas, la fonction renvoie toujours null.\n  //\n  // Ce qui manque n'est PAS une constante : c'est une s\u00E9lection en deux passes. La bande du\n  // trio doit \u00EAtre abaiss\u00E9e \u00E0 1990-2975 quand on VISE 4 bouteilles, puis on retombe sur la\n  // bande normale si aucune 4e ne convient. Le plancher est une contrainte sur le pack\n  // FINI, pas sur le trio : c'est ce qui autorise la 4e bouteille \u00E0 \u00EAtre ce qui atteint le\n  // plancher, au lieu d'obliger le trio \u00E0 monter en prix.\n  //\n  // Cons\u00E9quence \u00E0 conna\u00EEtre avant de l'activer : un pack de 4 sous 3800 fait forc\u00E9ment\n  // 8,15 \u00E0 8,90 de retail par bouteille, contre 11,12 \u00E0 3 bouteilles. Le badge est\n  // identique dans les deux cas (-49%) puisque le plafond est le m\u00EAme. Le gain r\u00E9el n'est\n  // donc pas la remise, c'est la fid\u00E9lit\u00E9 au go\u00FBt : aujourd'hui le plancher force\n  // l'algorithme \u00E0 monter en gamme pour les clients dont les r\u00E9ponses pointent vers des\n  // vins abordables, donc \u00E0 choisir en partie sur le prix plut\u00F4t que sur le go\u00FBt.\n  // \u00C0 3800, 2007 combinaisons de 4 vins sont valides, dont 861 en mix \u00E9quilibr\u00E9 2R+2B.\n  //\n  // Called AFTER the trio is settled and it can never alter it. The taste maths choose the\n  // wines and the bottle count is only a consequence of what they cost, never the reverse.\n  // Returns null when nothing qualifies or the ceiling leaves no room, and the box stays at\n  // three paid bottles.\n  pickSafeFourthBottle({ trio, candidates, packTotal, minPackPrice, maxPackPrice, getColorCode }) {\n    if (!Array.isArray(trio) || trio.length < 3 || !Array.isArray(candidates)) return null;\n    const spent = packTotal(trio);\n    // The floor exists so the retail value always beats the \u20AC29.90 we charge. It is a\n    // constraint on the FINISHED pack, not on the trio, so the fourth bottle can be the\n    // thing that satisfies it rather than an upgrade forced onto the trio's prices.\n    const floorPrice = Math.max(0, (minPackPrice || 0) - spent);\n    const room = maxPackPrice - spent;\n    if (room <= 0 || floorPrice > room) return null;\n\n    const takenIds = new Set(trio.map(wine => String(wine?.wine_id || '')));\n    // No distance ceiling relative to the trio any more. It existed to keep the fourth bottle\n    // safer than the exploration bottle, and that bottle is gone: all three trio slots are now\n    // picked for closeness, so `_insideTasteZone` alone already carries the \"never a risky\n    // bottle\" guarantee. Keeping the old ceiling on top of it would suppress the fourth bottle\n    // almost always, since the trio has already taken the closest wines.\n\n    const eligible = candidates.filter(wine => {\n      if (!wine || takenIds.has(String(wine.wine_id))) return false;\n      if (!wine._insideTasteZone) return false;\n      const price = Number(wine._displayPrice) || 0;\n      return price >= Math.max(1, floorPrice) && price <= room;\n    });\n    if (!eligible.length) return null;\n\n    // Prefer the colour the trio has least of, so a 2+1 trio becomes a balanced 2+2 instead\n    // of a lopsided 3+1. Preference only: a colour-locked visitor already had the other\n    // colour filtered out of `candidates`, so there is nothing to force here.\n    const counts = trio.reduce((acc, wine) => {\n      const code = getColorCode(wine);\n      if (code === 'R') acc.R += 1;\n      if (code === 'W') acc.W += 1;\n      return acc;\n    }, { R: 0, W: 0 });\n    const wanted = counts.R === counts.W ? null : (counts.R < counts.W ? 'R' : 'W');\n    const preferred = wanted ? eligible.filter(wine => getColorCode(wine) === wanted) : [];\n\n    return (preferred.length ? preferred : eligible)\n      .slice()\n      .sort((a, b) => a._axisDistance - b._axisDistance)[0] || null;\n  }\n\n  buildCalibrationVisualization(selectedWines, context, allWines = [], catalog = window.shopCatalog || {}) {\n    const selectedIds = new Set(selectedWines.map(wine => String(wine.wine_id)));\n    const selectedById = selectedWines.reduce((map, wine, index) => {\n      map[String(wine.wine_id)] = { wine, index };\n      return map;\n    }, {});\n    const allMapWines = allWines\n      .filter(wine => Number.isFinite(parseFloat(wine.map_x)) && Number.isFinite(parseFloat(wine.map_y)))\n      .map(wine => {\n        const id = String(wine.wine_id || wine.product_id || wine.id || '');\n        const selected = selectedById[id];\n        return {\n          product_id: id,\n          title: catalog[id]?.title || wine.title || '',\n          color: this.getWineColorCode(wine, catalog),\n          x: parseFloat(wine.map_x) || 0,\n          y: parseFloat(wine.map_y) || 0,\n          intensity: parseFloat(wine.map_intensity) || 0,\n          selected: selectedIds.has(id),\n          role: selected?.wine?._calibrationRole || '',\n          reason_title: selected?.wine?.reason_title || '',\n          label: selected ? String(selected.index + 1) : ''\n        };\n      });\n    return {\n      customer_point: {\n        x: context.customer.x,\n        y: context.customer.y,\n        intensity: context.customer.intensity\n      },\n      taste_zone: {\n        x_min: context.tasteZone.xMin,\n        x_max: context.tasteZone.xMax,\n        y_min: context.tasteZone.yMin,\n        y_max: context.tasteZone.yMax,\n        intensity_min: context.tasteZone.intensityMin,\n        intensity_max: context.tasteZone.intensityMax\n      },\n      map_bounds: {\n        x_min: context.ranges.x.min,\n        x_max: context.ranges.x.max,\n        y_min: context.ranges.y.min,\n        y_max: context.ranges.y.max,\n        intensity_min: context.ranges.intensity.min,\n        intensity_max: context.ranges.intensity.max\n      },\n      all_wines: allMapWines,\n      selected_wines: selectedWines.map((wine, index) => ({\n        product_id: String(wine.wine_id),\n        role: wine._calibrationRole,\n        reason_title: wine.reason_title,\n        reason_text: wine.reason_text,\n        title: catalog[String(wine.wine_id)]?.title || '',\n        color: this.getWineColorCode(wine, catalog),\n        x: parseFloat(wine.map_x) || 0,\n        y: parseFloat(wine.map_y) || 0,\n        intensity: parseFloat(wine.map_intensity) || 0,\n        distance_to_bottle_1: wine.distance_to_bottle_1,\n        distance_to_bottle_2: wine.distance_to_bottle_2,\n        distance_to_bottle_3: wine.distance_to_bottle_3,\n        similarity_to_bottle_1_pct: wine.similarity_to_bottle_1_pct,\n        similarity_to_bottle_2_pct: wine.similarity_to_bottle_2_pct,\n        similarity_to_bottle_3_pct: wine.similarity_to_bottle_3_pct,\n        label: String(index + 1)\n      }))\n    };\n  }\n\n  async buildCalibrationBottleSelection(wines, options = {}) {\n    const isEN = options.isEN ?? (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n    const catalog = options.catalog || window.shopCatalog || {};\n    const excludedCustomerProductIds = new Set(['15513192694147']);\n    const minPackPrice = options.minPackPrice || 3000;\n    const maxPackPrice = options.maxPackPrice || 3500;\n    // Band the TRIO is selected in. It is not the pack band: pass 1 lowers it so a fourth\n    // safe bottle still fits under `maxPackPrice`, pass 2 restores the pack band for a\n    // three-bottle box. Everything that prices a trio reads these two, never the pack band.\n    let trioMin = minPackPrice;\n    let trioMax = maxPackPrice;\n    const maxPrice = options.maxPrice || maxPackPrice;\n    const isCatalogProductInStock = options.isCatalogProductInStock || (() => true);\n    const getPaidDisplayPrice = options.getPaidDisplayPrice || ((product) => this.getPaidDisplayPrice(product));\n    // Search the whole scored catalogue, not just the handful of top matches the\n    // cards display: an 80-90% trio often needs a wine ranked below the top 10.\n    const searchPool = this.uniqueWinesByProduct([\n      ...(Array.isArray(wines) ? wines : []),\n      ...(Array.isArray(this.calibrationSearchPool) ? this.calibrationSearchPool : [])\n    ]);\n    const context = this.getCalibrationAxisContext(searchPool);\n    const giftReserveId = '';\n    const candidates = this.uniqueWinesByProduct(searchPool\n      .filter(wine => {\n        const p = catalog[String(wine.wine_id)];\n        const wineId = String(wine.wine_id);\n        // Exactly ONE bottle is held out of the kit, the cheapest in stock, so there is always\n        // something left to give. The band reserves precisely its price, so it always fits the\n        // leftover budget. Without this the pack could take the cheapest wine and leave a budget\n        // too small for any remaining bottle, and the box would silently ship with no gift.\n        // Only one is reserved now, against two before, so the kit pool is wider than it was.\n        const reservedForGift = giftReserveId && wineId === giftReserveId;\n        return p &&\n          !excludedCustomerProductIds.has(wineId) &&\n          !reservedForGift &&\n          getPaidDisplayPrice(p) <= maxPrice &&\n          isCatalogProductInStock(p) &&\n          this.isWineAllowedForColorPreference(wine, catalog);\n      })\n      .map(wine => {\n        const decorated = this.decorateWineForCalibration(wine, context);\n        const product = catalog[String(wine.wine_id)];\n        return { ...decorated, _displayPrice: product ? getPaidDisplayPrice(product) : 0 };\n      })\n      .sort((a, b) => a._axisDistance - b._axisDistance));\n\n    if (!candidates.length) return { selectedWines: [], visualization: this.buildCalibrationVisualization([], context, searchPool, catalog), candidates: [] };\n\n    const cfg = this.calibrationConfig;\n    const desiredMix = this.getDesiredColorMix();\n    const enforceColorMix = (this.colorPreference === 'mix' || this.colorPreference === 'both') && !!desiredMix;\n    const getColorCode = (wine) => this.getWineColorCode(wine, catalog);\n    const countColors = (pack) => {\n      const counts = { red: 0, white: 0 };\n      (pack || []).forEach(wine => {\n        const code = getColorCode(wine);\n        if (code === 'R') counts.red += 1;\n        if (code === 'W') counts.white += 1;\n      });\n      return counts;\n    };\n    const matchesRequiredMix = (pack, requiredMix) => {\n      if (!requiredMix) return true;\n      const counts = countColors(pack);\n      return counts.red === requiredMix.red && counts.white === requiredMix.white;\n    };\n    const packTotal = (pack) => pack.reduce((sum, wine) => sum + (Number(wine?._displayPrice) || 0), 0);\n    const inPackPriceRange = (pack) => {\n      const total = packTotal(pack);\n      return total >= trioMin && total <= trioMax;\n    };\n    const findPricedPack = (preferredPack, requiredMix = null) => {\n      if (preferredPack.length === 3 && inPackPriceRange(preferredPack) && matchesRequiredMix(preferredPack, requiredMix)) return preferredPack;\n\n      const currentIds = new Set(preferredPack.map(wine => String(wine?.wine_id || '')));\n      const targetTotal = (trioMin + trioMax) / 2;\n      const pool = candidates;\n      let best = null;\n      let closestFallback = null;\n\n      for (let i = 0; i < pool.length; i += 1) {\n        const a = pool[i];\n        for (let j = 0; j < pool.length; j += 1) {\n          if (j === i) continue;\n          const b = pool[j];\n          for (let k = j + 1; k < pool.length; k += 1) {\n            if (k === i || k === j) continue;\n            const c = pool[k];\n            const ids = new Set([String(a.wine_id), String(b.wine_id), String(c.wine_id)]);\n            if (ids.size !== 3) continue;\n            const pack = [a, b, c];\n            const colorCounts = countColors(pack);\n            const total = packTotal(pack);\n            const preserved = pack.reduce((count, wine) => count + (currentIds.has(String(wine.wine_id)) ? 1 : 0), 0);\n            const score =\n              (a._axisDistance * 3) +\n              b._axisDistance +\n              c._axisDistance +\n              (Math.abs(total - targetTotal) / 1000) -\n              (preserved * 0.08);\n            const colorGap = requiredMix\n              ? Math.abs(colorCounts.red - requiredMix.red) + Math.abs(colorCounts.white - requiredMix.white)\n              : 0;\n            const fallbackPenalty = total < trioMin\n              ? (trioMin - total) / 100\n              : total > trioMax\n                ? (total - trioMax) / 100\n                : 0;\n            const fallbackScore = score + (fallbackPenalty * 10) + (colorGap * 10);\n            if (!closestFallback || fallbackScore < closestFallback.score) {\n              closestFallback = { pack, score: fallbackScore, total };\n            }\n            if (total < trioMin || total > trioMax) continue;\n            if (!matchesRequiredMix(pack, requiredMix)) continue;\n            if (!best || score < best.score) {\n              best = { pack, score, total };\n            }\n          }\n        }\n      }\n\n      return best?.pack || closestFallback?.pack || preferredPack;\n    };\n    let bottleA = null;\n    let bottleB = null;\n    let bottleC = null;\n    let distA = new Map();\n    let distB = new Map();\n    let distanceFromA = () => null;\n    let distanceFromB = () => null;\n\n    // Replayable so the two passes can run it against different trio bands. Everything it\n    // touches is local: the distance maps the caller needs are rebuilt below from whichever\n    // trio finally wins, so a discarded pass leaves nothing behind.\n    const selectTrio = async () => {\n    const selectedIds = new Set();\n    const pickClosest = (pool) => pool.find(wine => !selectedIds.has(String(wine.wine_id))) || null;\n\n    // Lab similarity decides the pack. Axis distance and price only rank the\n    // packs that already hold up, so the 3 bottles can never drift apart.\n    const labTrio = await this.selectCalibrationTrioByLabSimilarity(candidates, {\n      requiredMix: enforceColorMix ? desiredMix : null,\n      matchesRequiredMix,\n      packTotal,\n      minPackPrice: trioMin,\n      maxPackPrice: trioMax,\n      context\n    });\n\n    if (labTrio) {\n      return labTrio;\n    } else if (enforceColorMix) {\n      const usedIds = new Set();\n      const pickFromPool = (pool, count) => {\n        const selected = [];\n        for (const wine of pool) {\n          const id = String(wine.wine_id || '');\n          if (!id || usedIds.has(id)) continue;\n          selected.push(wine);\n          usedIds.add(id);\n          if (selected.length >= count) break;\n        }\n        return selected;\n      };\n      const redPool = candidates.filter(wine => getColorCode(wine) === 'R');\n      const whitePool = candidates.filter(wine => getColorCode(wine) === 'W');\n      const seedSelection = [\n        ...pickFromPool(redPool, desiredMix.red),\n        ...pickFromPool(whitePool, desiredMix.white)\n      ];\n      if (seedSelection.length < 3) {\n        seedSelection.push(...pickFromPool(candidates, 3 - seedSelection.length));\n      }\n      seedSelection.sort((a, b) => a._axisDistance - b._axisDistance);\n      return findPricedPack(this.uniqueWinesByProduct(seedSelection).slice(0, 3), desiredMix);\n    } else {\n      let localA = pickClosest(candidates);\n      let localB = null;\n      let localC = null;\n      if (!localA) return [];\n      selectedIds.add(String(localA.wine_id));\n\n      const zonePoolBase = candidates.filter(wine => wine._insideTasteZone && !selectedIds.has(String(wine.wine_id)));\n      const poolBase = zonePoolBase.length ? zonePoolBase : candidates.filter(wine => !selectedIds.has(String(wine.wine_id)));\n      const localDistA = await this.getPerceptiveDistanceMap(localA.wine_id);\n      const distanceFromA = (wine) => this.getDistanceBetweenSelected(localDistA, wine);\n      let localDistB = new Map();\n      const withinWindow = (distance, window) => Number.isFinite(distance) && distance >= window.min && distance <= window.max;\n      const closestToTarget = (pool, scorer) => pool\n        .map(wine => ({ wine, score: scorer(wine) }))\n        .filter(item => Number.isFinite(item.score))\n        .sort((a, b) => a.score - b.score || a.wine._axisDistance - b.wine._axisDistance)[0]?.wine || null;\n      const pickBInWindow = (pool, window) => pool.find(wine => {\n        const distance = distanceFromA(wine);\n        return withinWindow(distance, window);\n      });\n\n      for (const window of cfg.similarityWindows) {\n        localB = pickBInWindow(poolBase, window);\n        if (localB) break;\n      }\n      if (!localB) {\n        localB = closestToTarget(poolBase, wine => Math.abs((distanceFromA(wine) ?? Infinity) - cfg.targetPerceptiveDistance));\n      }\n      if (!localB) localB = pickClosest(poolBase);\n\n      if (localB) selectedIds.add(String(localB.wine_id));\n\n      const poolForC = (zonePoolBase.length ? zonePoolBase : candidates).filter(wine => !selectedIds.has(String(wine.wine_id)));\n      if (localB) localDistB = await this.getPerceptiveDistanceMap(localB.wine_id);\n      const distanceFromB = (wine) => this.getDistanceBetweenSelected(localDistB, wine);\n      const pickCInWindow = (window) => poolForC.find(wine => {\n        const a = distanceFromA(wine);\n        const b = distanceFromB(wine);\n        return withinWindow(a, window) && withinWindow(b, window);\n      });\n\n      // Bottle 3 used to be picked AT a target perceptive distance from both others, which is\n      // what made it the furthest bottle in the box and what three customers disliked. It is\n      // now picked for closeness to the customer inside the taste zone, like bottle 1, and the\n      // similarity windows only cap how far it may drift, never how far it must go.\n      localC = poolForC\n        .filter(wine => {\n          const a = distanceFromA(wine);\n          const b = distanceFromB(wine);\n          const widest = cfg.similarityWindows[cfg.similarityWindows.length - 1];\n          return (!Number.isFinite(a) || a <= widest.max) && (!Number.isFinite(b) || b <= widest.max);\n        })\n        .sort((a, b) => a._axisDistance - b._axisDistance)[0] || null;\n      if (!localC) localC = pickClosest(poolForC);\n\n      return findPricedPack(this.uniqueWinesByProduct([localA, localB, localC].filter(Boolean)).slice(0, 3));\n    }\n    };\n\n    // Two passes. Pass 1 aims for four bottles by lowering the trio band so the cheapest\n    // eligible wine still fits under `maxPackPrice`; the pack floor is then satisfied by the\n    // fourth bottle instead of being forced onto the trio's prices, which is what used to\n    // push a cheap-tasting customer up into wines chosen for their price and not their match.\n    // Pass 2 is the historical three-bottle band and always runs if pass 1 finds no fourth.\n    const cheapestCandidate = candidates.reduce((min, wine) => {\n      const price = Number(wine?._displayPrice) || 0;\n      return price > 0 && price < min ? price : min;\n    }, Infinity);\n    const forcedPack = this.getForcedPackSize();\n    const passes = [];\n    if (forcedPack !== 3 && Number.isFinite(cheapestCandidate) && maxPackPrice - cheapestCandidate > 0) {\n      const wantMax = maxPackPrice - cheapestCandidate;\n      passes.push({ wantFourth: true, min: Math.max(0, wantMax - 1000), max: wantMax });\n    }\n    if (forcedPack !== 4) passes.push({ wantFourth: false, min: minPackPrice, max: maxPackPrice });\n    if (!passes.length) passes.push({ wantFourth: false, min: minPackPrice, max: maxPackPrice });\n\n    let bottleD = null;\n    for (const pass of passes) {\n      trioMin = pass.min;\n      trioMax = pass.max;\n      const trio = (await selectTrio()) || [];\n      if (trio.length < 3) continue;\n      if (!pass.wantFourth) {\n        [bottleA, bottleB, bottleC] = trio;\n        bottleD = null;\n        break;\n      }\n      const fourth = this.pickSafeFourthBottle({\n        trio,\n        candidates,\n        packTotal,\n        minPackPrice,\n        maxPackPrice,\n        getColorCode\n      });\n      if (fourth) {\n        [bottleA, bottleB, bottleC] = trio;\n        bottleD = fourth;\n        break;\n      }\n    }\n    // Restore the pack band so anything reading it after the loop sees the real one.\n    trioMin = minPackPrice;\n    trioMax = maxPackPrice;\n\n    if (bottleA) distA = await this.getPerceptiveDistanceMap(bottleA.wine_id);\n    if (bottleB) distB = await this.getPerceptiveDistanceMap(bottleB.wine_id);\n    distanceFromA = (wine) => this.getDistanceBetweenSelected(distA, wine);\n    distanceFromB = (wine) => this.getDistanceBetweenSelected(distB, wine);\n\n    // Only fetched when a fourth bottle exists, so a three-bottle box makes no extra\n    // request and its distance figures keep the exact expression they had before.\n    let distCMap = new Map();\n    if (bottleD && bottleC) distCMap = await this.getPerceptiveDistanceMap(bottleC.wine_id);\n    const distanceFromC = (wine) => this.getDistanceBetweenSelected(distCMap, wine);\n\n    const selected = this.uniqueWinesByProduct([bottleA, bottleB, bottleC, bottleD].filter(Boolean)).slice(0, 4);\n    const annotated = selected.map((wine, index) => {\n      // Bottle 3 is no longer `controlled_exploration`: that bottle was chosen for being the\n      // furthest from the customer's answers and three customers disliked exactly it.\n      const role = index === 0\n        ? 'closest_match'\n        : index === 1\n          ? 'same_direction_variation'\n          : index === 2\n            ? 'second_safe_match'\n            : 'third_safe_match';\n      const distanceToA = index === 0 ? 0 : distanceFromA(wine) ?? null;\n      const distanceToB = index === 1 ? 0 : (index === 0 && bottleB ? distanceFromA(bottleB) : distanceFromB(wine)) ?? null;\n      const distanceToC = index === 2\n        ? 0\n        : index === 3\n          ? (bottleC ? distanceFromC(wine) ?? null : null)\n          : (bottleC ? (index === 0 ? distanceFromA(bottleC) : distanceFromB(bottleC)) : null);\n      return this.addCalibrationMetadata(wine, role, index + 1, isEN, {\n        distance_to_bottle_1: distanceToA,\n        distance_to_bottle_2: distanceToB,\n        distance_to_bottle_3: distanceToC,\n        similarity_to_bottle_1_pct: this.similarityPctFromDistance(distanceToA),\n        similarity_to_bottle_2_pct: this.similarityPctFromDistance(distanceToB),\n        similarity_to_bottle_3_pct: this.similarityPctFromDistance(distanceToC),\n        perceptive_distance_from_bottle_A: index === 0 ? null : distanceToA,\n        perceptive_distance_from_previous_selected_bottles: index === 2\n          ? { from_bottle_A: distanceToA, from_bottle_B: distanceToB }\n          : index === 3\n            ? { from_bottle_A: distanceToA, from_bottle_B: distanceToB, from_bottle_C: distanceToC }\n            : null\n      });\n    });\n\n    const visualization = this.buildCalibrationVisualization(annotated, context, searchPool, catalog);\n    sessionStorage.setItem('vinpop_calibration_recommendation', JSON.stringify({\n      selected_wines: annotated.map(wine => ({\n        product_id: String(wine.wine_id),\n        title: catalog[String(wine.wine_id)]?.title || '',\n        color: this.getWineColorCode(wine, catalog),\n        map_x: parseFloat(wine.map_x) || 0,\n        map_y: parseFloat(wine.map_y) || 0,\n        map_intensity: parseFloat(wine.map_intensity) || 0,\n        axis_distance_to_customer: wine.axis_distance_to_customer,\n        role: wine._calibrationRole,\n        reason_title: wine.reason_title,\n        reason_text: wine.reason_text,\n        distance_to_bottle_1: wine.distance_to_bottle_1,\n        distance_to_bottle_2: wine.distance_to_bottle_2,\n        distance_to_bottle_3: wine.distance_to_bottle_3,\n        similarity_to_bottle_1_pct: wine.similarity_to_bottle_1_pct,\n        similarity_to_bottle_2_pct: wine.similarity_to_bottle_2_pct,\n        similarity_to_bottle_3_pct: wine.similarity_to_bottle_3_pct,\n        perceptive_distance_from_bottle_A: wine.perceptive_distance_from_bottle_A,\n        perceptive_distance_from_previous_selected_bottles: wine.perceptive_distance_from_previous_selected_bottles\n      })),\n      visualization\n    }));\n    // `candidates` is the full catalogue already scored and sorted by taste distance.\n    // The +1 bottle upsell reads it so the extra bottle is ranked by the same maths\n    // as the kit itself, instead of being a random cross-sell.\n    return { selectedWines: annotated, visualization, candidates };\n  }\n\n  buildCalibrationMapHTML(visualization, isEN) {\n    if (!visualization || !visualization.map_bounds || !Array.isArray(visualization.selected_wines)) return '';\n    const bottle1 = visualization.selected_wines[0] || {};\n    const bottle2 = visualization.selected_wines[1] || {};\n    const bottle3 = visualization.selected_wines[2] || {};\n    const similarityLabel = (value) => Number.isFinite(Number(value)) ? `<strong>${Math.round(Number(value))}%</strong>` : '<strong>--</strong>';\n    const sim12 = similarityLabel(bottle1.similarity_to_bottle_2_pct ?? bottle2.similarity_to_bottle_1_pct);\n    const sim13 = similarityLabel(bottle1.similarity_to_bottle_3_pct ?? bottle3.similarity_to_bottle_1_pct);\n    const sim23 = similarityLabel(bottle2.similarity_to_bottle_3_pct ?? bottle3.similarity_to_bottle_2_pct);\n    const triangleHTML = `\n      <div class=\"wr-calibration-triangle\" aria-label=\"${isEN ? 'Taste similarity between wines' : 'Smaakovereenkomst tussen wijnen'}\">\n        <em class=\"wr-calibration-triangle__caption\">${isEN ? 'Taste similarity between wines' : 'Smaakovereenkomst tussen wijnen'}</em>\n        <div class=\"wr-calibration-triangle__node wr-calibration-triangle__node--one\"><strong>1</strong><span>${isEN ? 'Best match to your taste' : 'Beste match met jouw smaak'}</span></div>\n        <div class=\"wr-calibration-triangle__node wr-calibration-triangle__node--two\"><strong>2</strong><span>${isEN ? 'Close variation' : 'Nauwe variatie'}</span></div>\n        <div class=\"wr-calibration-triangle__node wr-calibration-triangle__node--three\"><strong>3</strong><span>${isEN ? 'Safe exploration' : 'Veilig ontdekken'}</span></div>\n        <span class=\"wr-calibration-triangle__line wr-calibration-triangle__line--12\">${sim12}</span>\n        <span class=\"wr-calibration-triangle__line wr-calibration-triangle__line--13\">${sim13}</span>\n        <span class=\"wr-calibration-triangle__line wr-calibration-triangle__line--23\">${sim23}</span>\n      </div>\n    `;\n    return `\n      <div class=\"wr-calibration-map\" aria-label=\"${isEN ? 'Your calibration wines' : 'Jouw kalibratiewijnen'}\">\n        <div class=\"wr-calibration-map__head\">\n          <strong>${isEN ? 'Your calibration wines' : 'Jouw kalibratiewijnen'}</strong>\n          <span>${isEN ? 'Three wines chosen to confirm your taste, reduce the risk of guessing, and guide you to a better match.' : 'Drie wijnen gekozen om jouw smaak te bevestigen, gokwerk te verminderen en je naar een betere match te begeleiden.'}</span>\n        </div>\n        ${triangleHTML}\n      </div>\n    `;\n  }\n\n  // Full-screen wine-by-wine reveal played over the (already built) bundle page.\n  // Auto-advances so cold mobile traffic never has to click to see their result.\n  buildWineRevealHTML(packWines, isEN, catalog, giftProductId) {\n    const wines = (packWines || []).filter(w => w && catalog[String(w.wine_id)]);\n    if (wines.length < 2) return '';\n    const escHTML = (s) => String(s == null ? '' : s)\n      .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n    // No \"Discovery\" label any more: bottle 3 stopped being the far bottle when three\n    // customers reported disliking exactly that one. Every slot now reads as a match.\n    const roleLabels = isEN\n      ? { 1: 'Best match', 2: 'Close match', 3: 'Safe match', 4: 'Safe match' }\n      : { 1: 'Beste match', 2: 'Dichte match', 3: 'Veilige match', 4: 'Veilige match' };\n\n    const items = wines.map((wine, i) => {\n      const p = catalog[String(wine.wine_id)];\n      const idx = wine._calibrationIndex || (i + 1);\n      let label = roleLabels[idx] || (isEN ? 'Your match' : 'Jouw match');\n      if (wine._isBonus) label = isEN ? 'Bonus bottle' : 'Bonusfles';\n      else if (wine._isSimilar) label = isEN ? 'Surprise match' : 'Verrassingsmatch';\n      let reason = wine.reason_text || wine.reason_title || '';\n      if (reason.length > 150) reason = reason.slice(0, 147).trim() + '\u2026';\n      return {\n        badge: `#${idx} \u00B7 ${label}`,\n        badgeClass: '',\n        img: p.image && p.image.url ? p.image.url : '',\n        title: p.title,\n        reason\n      };\n    });\n\n    // Free gift bottle as a distinct FINAL card: a gift, never a taste match, no % shown.\n    const gift = giftProductId ? catalog[String(giftProductId)] : null;\n    if (gift) {\n      items.push({\n        badge: isEN ? '\uD83C\uDF81 Free gift' : '\uD83C\uDF81 Gratis cadeau',\n        badgeClass: 'wr-reveal__badge--gift',\n        img: gift.image && gift.image.url ? gift.image.url : '',\n        title: gift.title,\n        reason: isEN\n          ? 'A bottle on us. Our gift for trying VinPop, with no strings attached.'\n          : 'Een fles van ons. Ons cadeau omdat je VinPop probeert, zonder verplichtingen.'\n      });\n    }\n\n    const cards = items.map((it, i) => (\n      `<article class=\"wr-reveal__card${i === 0 ? ' is-active' : ''}\" data-idx=\"${i}\">` +\n      `<div class=\"wr-reveal__badge ${it.badgeClass}\">${escHTML(it.badge)}</div>` +\n      `<div class=\"wr-reveal__imgwrap\">${it.img ? `<img src=\"${escHTML(it.img)}\" alt=\"${escHTML(it.title)}\">` : ''}</div>` +\n      `<h3 class=\"wr-reveal__name\">${escHTML(it.title)}</h3>` +\n      `<p class=\"wr-reveal__reason\">${escHTML(it.reason)}</p>` +\n      `</article>`\n    )).join('');\n    const segments = items.map(() => `<span class=\"wr-reveal__seg\"><span class=\"wr-reveal__seg-fill\"></span></span>`).join('');\n    const eyebrow = isEN\n      ? 'Your matches, chosen from 10,000 lab-analyzed wines:'\n      : 'Jouw matches, gekozen uit 10.000 lab-geanalyseerde wijnen:';\n    const hint = isEN ? 'Tap to go faster' : 'Tik om sneller te gaan';\n    const introTitle = isEN ? 'Analysing your taste\u2026' : 'Je smaak analyseren\u2026';\n    return `\n      <div class=\"wr-reveal\" id=\"wr-reveal\" role=\"dialog\" aria-label=\"${isEN ? 'Your wine matches' : 'Jouw wijnmatches'}\">\n        <button class=\"wr-reveal__skip\" type=\"button\">${isEN ? 'Skip' : 'Overslaan'}</button>\n        <div class=\"wr-reveal__intro\">\n          <canvas class=\"wr-reveal__intro-canvas\"></canvas>\n          <div class=\"wr-reveal__intro-text\"><p class=\"wr-reveal__intro-title\">${introTitle}</p></div>\n        </div>\n        <div class=\"wr-reveal__inner\">\n          <div class=\"wr-reveal__progress\">${segments}</div>\n          <div class=\"wr-reveal__eyebrow\">${eyebrow}</div>\n          <div class=\"wr-reveal__stage\">${cards}</div>\n          <button class=\"wr-reveal__next\" type=\"button\" data-last-label=\"${isEN ? 'See my box \u2192' : 'Bekijk mijn box \u2192'}\" data-next-label=\"${isEN ? 'Next \u2192' : 'Volgende \u2192'}\">${isEN ? 'Next \u2192' : 'Volgende \u2192'}</button>\n          <button class=\"wr-reveal__hint\" type=\"button\" style=\"background:none;border:0;padding:10px 16px;margin-top:0;cursor:pointer;font:inherit;font-size:12px;text-decoration:underline;text-underline-offset:3px;\">${hint}</button>\n        </div>\n      </div>\n    `;\n  }\n\n  playWineReveal(overlay, onDone) {\n    const cards = Array.from(overlay.querySelectorAll('.wr-reveal__card'));\n    const segFills = Array.from(overlay.querySelectorAll('.wr-reveal__seg-fill'));\n    const nextBtn = overlay.querySelector('.wr-reveal__next');\n    const skipBtn = overlay.querySelector('.wr-reveal__skip');\n    if (!cards.length || !nextBtn) { overlay.remove(); if (onDone) onDone(); return; }\n    const DURATION = 1800; // ms per card (stories-style, snappier than before)\n    const last = cards.length - 1;\n    let idx = 0, timer = null;\n    const prevOverflow = document.body.style.overflow;\n    document.body.style.overflow = 'hidden';\n\n    const clearTimer = () => { if (timer) { clearTimeout(timer); timer = null; } };\n    let finished = false;\n    const finish = () => {\n      if (finished) return;\n      finished = true;\n      clearTimer();\n      overlay.classList.add('is-closing');\n      document.body.style.overflow = prevOverflow;\n      setTimeout(() => { overlay.remove(); if (onDone) onDone(); }, 360);\n    };\n    const paintProgress = (n) => {\n      segFills.forEach((f, i) => {\n        f.style.transition = 'none';\n        f.style.transform = i < n ? 'scaleX(1)' : 'scaleX(0)';\n      });\n      const active = segFills[n];\n      if (active) {\n        void active.offsetWidth; // reflow so the fill transition actually runs\n        active.style.transition = `transform ${DURATION}ms linear`;\n        active.style.transform = 'scaleX(1)';\n      }\n    };\n    const show = (n) => {\n      cards.forEach((c, i) => c.classList.toggle('is-active', i === n));\n      nextBtn.textContent = n >= last ? nextBtn.dataset.lastLabel : nextBtn.dataset.nextLabel;\n      nextBtn.classList.toggle('is-final', n >= last);\n      paintProgress(n);\n      clearTimer();\n      // Auto-advance every card, including the last \u2192 then go straight to the results page (no click needed).\n      timer = setTimeout(() => advance(), DURATION);\n    };\n    const advance = () => { if (idx < last) { idx += 1; show(idx); } else { finish(); } };\n\n    nextBtn.addEventListener('click', advance);\n    // 2026-09-28 : \u00AB Tap to go faster \u00BB est un vrai bouton (il n'etait qu'un texte : le toucher\n    // ne faisait rien). Il avance au vin suivant, comme \u00AB Volgende \u00BB, puis ouvre la box.\n    const hintBtn = overlay.querySelector('.wr-reveal__hint');\n    if (hintBtn) hintBtn.addEventListener('click', advance);\n    if (skipBtn) skipBtn.addEventListener('click', finish);\n    overlay.querySelector('.wr-reveal__stage').addEventListener('click', advance);\n\n    // 3D analysis intro: wine points appear in depth, then hand off to the cards.\n    const intro = overlay.querySelector('.wr-reveal__intro');\n    const canvas = intro ? intro.querySelector('canvas') : null;\n    if (intro && canvas) {\n      const isEN = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n      overlay.classList.add('wr-reveal--analyzing');\n      let intoCards = false, stopIntro = null;\n      const startCards = () => {\n        if (intoCards) return; intoCards = true;\n        if (stopIntro) stopIntro();\n        overlay.classList.remove('wr-reveal--analyzing');\n        show(0);\n      };\n      stopIntro = this.runAnalysisIntro(canvas, intro.querySelector('.wr-reveal__intro-title'), isEN, startCards);\n      intro.addEventListener('click', startCards);\n    } else {\n      show(0);\n    }\n  }\n\n  _roundRect(ctx, x, y, w, h, r) {\n    ctx.beginPath();\n    ctx.moveTo(x + r, y);\n    ctx.arcTo(x + w, y, x + w, y + h, r);\n    ctx.arcTo(x + w, y + h, x, y + h, r);\n    ctx.arcTo(x, y + h, x, y, r);\n    ctx.arcTo(x, y, x + w, y, r);\n    ctx.closePath();\n  }\n\n  // Living taste-map intro: a drifting cloud of analyzed wines, the real wines settling into\n  // place, then \"you\" and the 3 matched wines lit up with their names. Then hands off to cards.\n  runAnalysisIntro(canvas, titleEl, isEN, onDone) {\n    const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;\n    let done = false, raf = 0, nameTimer = 0;\n    const finishIntro = () => { if (done) return; done = true; if (raf) cancelAnimationFrame(raf); if (nameTimer) clearInterval(nameTimer); onDone(); };\n    const ctx = canvas.getContext('2d');\n    if (!ctx) { finishIntro(); return finishIntro; }\n    const dpr = Math.min(2, window.devicePixelRatio || 1);\n    canvas.width = (canvas.clientWidth || window.innerWidth) * dpr;\n    canvas.height = (canvas.clientHeight || window.innerHeight) * dpr;\n    const W = canvas.width, H = canvas.height, cxc = W / 2, cyc = H / 2;\n\n    const cleanName = (s) => String(s || '')\n      .replace(/[\\u{1F1E6}-\\u{1F1FF}]/gu, '')\n      .replace(/[\\u{1F000}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{2190}-\\u{21FF}]/gu, '')\n      .replace(/\\s+/g, ' ').trim();\n    const truncate = (s, n) => (s.length > n ? s.slice(0, n - 1).trim() + '\u2026' : s);\n\n    // Real wine positions from the taste-map visualization.\n    const viz = this._lastVisualization;\n    const dots = []; let you = null; const matches = []; const names = [];\n    const b = viz && viz.map_bounds;\n    if (b && Array.isArray(viz.all_wines) && viz.all_wines.length) {\n      const xR = (b.x_max - b.x_min) || 1, yR = (b.y_max - b.y_min) || 1;\n      const pad = Math.min(W, H) * 0.17;\n      const fx = (x) => pad + ((x - b.x_min) / xR) * (W - 2 * pad);\n      const fy = (y) => pad + (1 - (y - b.y_min) / yR) * (H - 2 * pad);\n      const selIds = new Set((viz.selected_wines || []).map(w => String(w.product_id)));\n      viz.all_wines.forEach(w => {\n        dots.push({ x: fx(w.x), y: fy(w.y), col: String(w.color).toUpperCase() === 'W' ? '#E7C662' : '#D45D4A', sel: selIds.has(String(w.product_id)) });\n        const nm = cleanName(w.title); if (nm) names.push(truncate(nm, 30));\n      });\n      if (viz.customer_point) you = { x: fx(viz.customer_point.x), y: fy(viz.customer_point.y) };\n      (viz.selected_wines || []).forEach((w, i) => matches.push({ x: fx(w.x), y: fy(w.y), label: String(w.label || i + 1), title: truncate(cleanName(w.title) || (isEN ? 'Your match' : 'Jouw match'), 22) }));\n    } else {\n      let s = 1234567; const rnd = () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;\n      for (let i = 0; i < 70; i++) dots.push({ x: W * (0.12 + 0.76 * rnd()), y: H * (0.12 + 0.76 * rnd()), col: rnd() > 0.5 ? '#E7C662' : '#D45D4A', sel: false });\n      you = { x: cxc, y: cyc };\n    }\n    const APPEAR = reduce ? 0 : 1500;\n    dots.forEach((d, i) => { d.appear = reduce ? 0 : (i / Math.max(1, dots.length - 1)) * APPEAR; });\n\n    // Decorative \"all analyzed wines\" cloud, drifting in parallax for a living map.\n    const deco = [];\n    let s2 = 90210; const rnd2 = () => (s2 = (s2 * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;\n    const DECO = reduce ? 50 : 150;\n    for (let i = 0; i < DECO; i++) deco.push({ bx: rnd2(), by: rnd2(), depth: 0.3 + rnd2() * 0.7, vx: rnd2() - 0.5, vy: rnd2() - 0.5, tw: rnd2() * Math.PI * 2, col: rnd2() > 0.6 ? '#E7C662' : '#D45D4A' });\n\n    const ease = (p) => 1 - Math.pow(1 - p, 3);\n    const cf = (v) => Math.max(0, Math.min(1, v));\n    const DURATION = reduce ? 800 : 3300;\n    const t0 = performance.now();\n    const rot2 = (x, y, a) => { const dx = x - cxc, dy = y - cyc, c = Math.cos(a), sn = Math.sin(a); return [cxc + dx * c - dy * sn, cyc + dx * sn + dy * c]; };\n\n    const draw = (now) => {\n      if (done) return;\n      if (!document.body.contains(canvas)) { finishIntro(); return; }\n      const t = now - t0;\n      const rot = reduce ? 0 : 0.10 * Math.sin(t * 0.00045);\n      ctx.clearRect(0, 0, W, H);\n\n      // drifting analyzed-wines cloud\n      for (const p of deco) {\n        let bx = (p.bx + p.vx * t * 0.000018) % 1; if (bx < 0) bx += 1;\n        let by = (p.by + p.vy * t * 0.000018) % 1; if (by < 0) by += 1;\n        const [x, y] = rot2(bx * W, by * H, rot * 0.5 * p.depth);\n        ctx.globalAlpha = (0.08 + 0.16 * (0.5 + 0.5 * Math.sin(t * 0.002 + p.tw))) * p.depth;\n        ctx.fillStyle = p.col;\n        ctx.beginPath(); ctx.arc(x, y, (0.6 + 1.3 * p.depth) * dpr, 0, Math.PI * 2); ctx.fill();\n      }\n\n      // real wines settle into their map positions\n      for (const d of dots) {\n        const p = reduce ? 1 : cf((t - d.appear) / 480);\n        if (p <= 0) continue;\n        const e = ease(p);\n        const [x, y] = rot2(cxc + (d.x - cxc) * e, cyc + (d.y - cyc) * e, rot);\n        const r = (d.sel ? 3.4 : 2.2) * dpr * (0.45 + 0.55 * e);\n        ctx.globalAlpha = p * (d.sel ? 1 : 0.82);\n        ctx.fillStyle = d.col;\n        ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill();\n        if (d.sel && p > 0.85) { ctx.globalAlpha = p; ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.2 * dpr; ctx.stroke(); }\n      }\n\n      // you + lines + match name labels\n      const yp = reduce ? 1 : cf((t - 1500) / 500);\n      if (you && yp > 0) {\n        const [yx, yy] = rot2(you.x, you.y, rot);\n        const lp = reduce ? 1 : cf((t - 1850) / 550);\n        if (lp > 0) {\n          ctx.globalAlpha = 0.6 * lp; ctx.strokeStyle = '#8FE3D0'; ctx.lineWidth = 1 * dpr; ctx.setLineDash([3 * dpr, 3 * dpr]);\n          for (const m of matches) { const [mx, my] = rot2(m.x, m.y, rot); ctx.beginPath(); ctx.moveTo(yx, yy); ctx.lineTo(yx + (mx - yx) * lp, yy + (my - yy) * lp); ctx.stroke(); }\n          ctx.setLineDash([]);\n        }\n        const np = reduce ? 1 : cf((t - 2150) / 450);\n        if (np > 0) {\n          ctx.font = `700 ${11 * dpr}px system-ui, -apple-system, sans-serif`;\n          ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic';\n          for (const m of matches) {\n            const [mx, my] = rot2(m.x, m.y, rot);\n            const label = `${m.label}. ${m.title}`;\n            const tw = ctx.measureText(label).width, padx = 6 * dpr, h = 17 * dpr;\n            const lx = Math.max(tw / 2 + 8 * dpr, Math.min(W - tw / 2 - 8 * dpr, mx));\n            const ly = Math.max(24 * dpr, my - 10 * dpr);\n            ctx.globalAlpha = np;\n            ctx.fillStyle = 'rgba(12,20,37,0.85)';\n            this._roundRect(ctx, lx - tw / 2 - padx, ly - h, tw + padx * 2, h, 4 * dpr); ctx.fill();\n            ctx.fillStyle = '#eafff9';\n            ctx.fillText(label, lx, ly - 5 * dpr);\n          }\n          ctx.textAlign = 'start';\n        }\n        const pulse = 1 + 0.16 * Math.sin(t * 0.006);\n        const g = ctx.createRadialGradient(yx, yy, 0, yx, yy, 28 * dpr * pulse);\n        g.addColorStop(0, `rgba(111,224,200,${0.55 * yp})`); g.addColorStop(1, 'rgba(111,224,200,0)');\n        ctx.globalAlpha = 1; ctx.fillStyle = g;\n        ctx.beginPath(); ctx.arc(yx, yy, 28 * dpr * pulse, 0, Math.PI * 2); ctx.fill();\n        ctx.fillStyle = '#c7f9ee'; ctx.globalAlpha = yp;\n        ctx.beginPath(); ctx.arc(yx, yy, 4.5 * dpr, 0, Math.PI * 2); ctx.fill();\n      }\n\n      ctx.globalAlpha = 1;\n      if (t >= DURATION) { finishIntro(); return; }\n      raf = requestAnimationFrame(draw);\n    };\n\n    // Title: scan through real wine names while analysing, then announce the matches.\n    if (titleEl) {\n      const analysing = isEN ? 'Analysing' : 'Analyseren';\n      const finalTitle = isEN ? 'Your 3 closest wines' : 'Jouw 3 dichtste wijnen';\n      if (reduce || !names.length) {\n        titleEl.textContent = finalTitle;\n      } else {\n        let k = 0;\n        titleEl.textContent = `${analysing}: ${names[0]}`;\n        nameTimer = setInterval(() => {\n          if (done) return;\n          k = (k + 1) % names.length;\n          titleEl.textContent = `${analysing}: ${names[k]}`;\n        }, 110);\n        setTimeout(() => { if (nameTimer) { clearInterval(nameTimer); nameTimer = 0; } if (!done && titleEl) titleEl.textContent = finalTitle; }, 2050);\n      }\n    }\n    raf = requestAnimationFrame(draw);\n    return finishIntro;\n  }\n\n  logCalibrationMapDebug(payload) {\n    const isDevelopmentTheme = /preview_theme_id=196622516611/.test(window.location.search) || window.Shopify?.designMode;\n    if (!isDevelopmentTheme) return;\n    console.log('Vinpop calibration map debug', {\n      all_wines_shown: payload.allWines.length,\n      customer_point: payload.customer,\n      selected_wines: payload.selectedWines.map(wine => ({ product_id: wine.product_id, x: wine.x, y: wine.y, intensity: wine.intensity })),\n      map_bounds: payload.bounds,\n      taste_zone_bounds: payload.zone\n    });\n  }\n\n  async restoreQuizFromApiOrStart(isInline) {\n    if (this.isForceRetakeEnabled()) {\n      this.openPopup();\n      return;\n    }\n\n    try {\n      this.showQuizLoading();\n      const payload = await this.fetchSavedQuizPayloadFromApi();\n      if (this.isForceRetakeEnabled()) {\n        this.openPopup();\n        return;\n      }\n      if (payload && this.applySavedQuizPayload(payload)) {\n        console.log('TasteProfileQuiz: restored quiz answers from API', payload);\n        if (this.isForceRetakeEnabled()) {\n          this.openPopup();\n          return;\n        }\n        await this.fetchResults({ skipQuizSave: true, preserveScroll: true });\n        return;\n      }\n      console.log('TasteProfileQuiz: no saved quiz answers found in API');\n    } catch (error) {\n      console.warn('TasteProfileQuiz: could not restore quiz answers from API:', error);\n    }\n\n    this.openPopup();\n  }\n\n  isForceRetakeEnabled() {\n    try {\n      return sessionStorage.getItem(this.forceRetakeStorageKey) === '1';\n    } catch (error) {\n      return false;\n    }\n  }\n\n  setForceRetake(enabled) {\n    try {\n      if (enabled) {\n        sessionStorage.setItem(this.forceRetakeStorageKey, '1');\n      } else {\n        sessionStorage.removeItem(this.forceRetakeStorageKey);\n      }\n    } catch (error) {\n      console.warn('TasteProfileQuiz: could not update retake flag', error);\n    }\n  }\n\n  showQuizLoading() {\n    this.popup.querySelectorAll('.taste-profile-question').forEach(q => {\n      q.style.display = 'none';\n    });\n    const progressEl = this.popup.querySelector('.taste-profile-progress');\n    if (progressEl) progressEl.style.display = 'none';\n    if (this.loadingEl) this.loadingEl.style.display = 'block';\n  }\n\n  async fetchSavedQuizPayloadFromApi() {\n    const apiBase = (this.getMetaContent('api-url') || 'https://api.vinpop.fr').replace(/\\/+$/, '');\n    const urls = [\n      `${apiBase}/api/quizz/get?quizz_type=test-kit`,\n      `${apiBase}/api/quizz/get`\n    ];\n    let lastError = null;\n\n    for (const url of urls) {\n      try {\n        const response = await fetch(url, {\n          method: 'GET',\n          headers: this.buildVinpopAuthHeaders(),\n          credentials: 'omit',\n          mode: 'cors'\n        });\n\n        if (response.status === 204 || response.status === 404) {\n          lastError = null;\n          continue;\n        }\n\n        if (!response.ok) {\n          lastError = new Error('quiz_get_failed_' + response.status);\n          continue;\n        }\n\n        const data = await response.json().catch(() => null);\n        return this.normalizeSavedQuizPayload(data);\n      } catch (error) {\n        lastError = error;\n      }\n    }\n\n    if (lastError) {\n      throw lastError;\n    }\n    return null;\n  }\n\n  normalizeSavedQuizPayload(data) {\n    const unwrap = value => {\n      if (!value) return null;\n      if (Array.isArray(value)) {\n        return value.find(item => {\n          const type = String(item?.quizz_type || item?.quiz_type || '').toLowerCase();\n          return type === 'test-kit';\n        }) || value[0] || null;\n      }\n      return value;\n    };\n\n    const candidates = [\n      unwrap(data),\n      unwrap(data?.data),\n      unwrap(data?.result),\n      unwrap(data?.quiz),\n      unwrap(data?.quizz),\n      unwrap(data?.answers)\n    ];\n\n    for (const candidate of candidates) {\n      if (!candidate || typeof candidate !== 'object') {\n        continue;\n      }\n\n      const payload = { quizz_type: 'test-kit' };\n      ['color', 'coffee', 'chocolate', 'salad', 'cheese', 'mood'].forEach(field => {\n        const value = Number(candidate[field] ?? candidate[field.toUpperCase()]);\n        if (value >= 1 && value <= 3) {\n          payload[field] = value;\n        }\n      });\n\n      if (['color', 'coffee', 'chocolate', 'salad', 'cheese', 'mood'].some(field => payload[field])) {\n        return payload;\n      }\n    }\n\n    return null;\n  }\n\n  applySavedQuizPayload(payload) {\n    const fieldByQuestion = {\n      1: 'color',\n      2: 'coffee',\n      3: 'chocolate',\n      4: 'salad',\n      5: 'cheese',\n      6: 'mood'\n    };\n\n    this.currentQuestion = this.totalQuestions;\n    this.targetX = 0;\n    this.targetY = 0;\n    this.targetZ = 0;\n    this.colorPreference = 'both';\n    this.colorMixPreference = null;\n    this.answers = [];\n    this.isProcessing = false;\n\n    Object.keys(fieldByQuestion).forEach(question => {\n      const questionNumber = Number(question);\n      const apiField = fieldByQuestion[questionNumber];\n      const choice = Number(payload[apiField]) - 1;\n      const qEl = this.popup.querySelector(`.taste-profile-question[data-question=\"${questionNumber}\"]`);\n      const button = qEl ? qEl.querySelectorAll('.taste-profile-answer')[choice] : null;\n      if (!button || choice < 0) {\n        return;\n      }\n\n      const color = button.dataset.color;\n      if (color) {\n        this.colorPreference = color;\n        this.colorMixPreference = null;\n        this.answers.push({ question: questionNumber, apiField, color, choice, apiChoice: color === 'mix' ? 2 : choice, label: this.getAnswerButtonLabel(button) });\n        return;\n      }\n\n      const x = parseFloat(button.dataset.x);\n      const y = parseFloat(button.dataset.y);\n      const z = parseFloat(button.dataset.z) || 0;\n      if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {\n        return;\n      }\n\n      this.targetX += x;\n      this.targetY += y;\n      this.targetZ += z;\n      this.answers.push({ question: questionNumber, apiField, x, y, z, choice, label: this.getAnswerButtonLabel(button) });\n    });\n\n    const rebuiltPayload = this.buildQuizSavePayload('test-kit');\n    const requiredFields = ['color', 'coffee', 'chocolate', 'salad', 'cheese', 'mood'];\n    return requiredFields.every(field => rebuiltPayload[field] >= 1 && rebuiltPayload[field] <= 3);\n  }\n\n  openPopup() {\n    const isInline = this.popup.classList.contains('taste-profile-popup--inline');\n    if (!isInline) {\n      this.popup.style.display = 'block';\n      document.body.style.overflow = 'hidden';\n    }\n    this.reset();\n  }\n\n  closePopup() {\n    const isInline = this.popup.classList.contains('taste-profile-popup--inline');\n    if (!isInline) {\n      this.popup.style.display = 'none';\n      document.body.style.overflow = '';\n    }\n\n    // If quiz results exist, refresh the page so the product grid updates\n    if (sessionStorage.getItem('vinpop_last_results')) {\n      window.location.href = window.location.pathname + '?taste_profile=true';\n    }\n  }\n\n  reset() {\n    const hasIntro = !!this.popup.querySelector('[data-question=\"0\"]');\n    this.currentQuestion = hasIntro ? 0 : 1;\n    this.targetX = 0;\n    this.targetY = 0;\n    this.targetZ = 0;\n    this.colorPreference = 'both';\n    this.colorMixPreference = null;\n    this.answers = [];\n    this.isProcessing = false;\n    sessionStorage.removeItem('vinpop_quiz_state');\n    \n    // Hide loading (critical for quiz re-open)\n    if (this.loadingEl) this.loadingEl.style.display = 'none';\n    \n    // Hide old wine reveal sequence\n    const seqEl = document.querySelector('.wine-reveal-sequence');\n    if (seqEl) seqEl.style.display = 'none';\n    const cardsEl = document.querySelector('.wine-reveal-cards');\n    if (cardsEl) cardsEl.innerHTML = '';\n\n    // Show/hide progress bar depending on whether intro screen exists\n    const progressEl = this.popup.querySelector('.taste-profile-progress');\n    if (progressEl) progressEl.style.display = hasIntro ? 'none' : '';\n    \n    this.showQuestion(this.currentQuestion);\n  }\n\n  // Retake: reload the page so the intro (and its Start button) are freshly bound and the\n  // page opens at the top. The force-retake flag persists in sessionStorage across reload.\n  _reloadToQuizTop() {\n    try { if ('scrollRestoration' in history) history.scrollRestoration = 'manual'; } catch (e) {}\n    try { window.scrollTo(0, 0); } catch (e) {}\n    window.location.reload();\n  }\n\n  handleAnswer(e) {\n    if (this.isProcessing) {\n      return;\n    }\n\n    const button = e.currentTarget;\n    const questionEl = button.closest('.taste-profile-question');\n    const clickedQuestion = questionEl ? Number(questionEl.dataset.question) : this.currentQuestion;\n    if (!clickedQuestion || clickedQuestion !== this.currentQuestion) {\n      console.warn('TasteProfileQuiz: ignored answer for inactive question', {\n        clickedQuestion,\n        currentQuestion: this.currentQuestion\n      });\n      return;\n    }\n\n    const apiFieldByQuestion = {\n      1: 'color',\n      2: 'coffee',\n      3: 'chocolate',\n      4: 'salad',\n      5: 'cheese',\n      6: 'mood'\n    };\n    const apiField = apiFieldByQuestion[clickedQuestion] || '';\n    if (!apiField) {\n      console.warn('TasteProfileQuiz: ignored answer without API field', { clickedQuestion });\n      return;\n    }\n\n    if (clickedQuestion >= 1) {\n      this.setForceRetake(false);\n    }\n\n    window.VinPopTracking?.trackQuizStarted({ quiz_type: 'test-kit' });\n    \n    // Determine answer index (A=0, B=1, C=2)\n    const allButtons = Array.from(button.parentElement.querySelectorAll('.taste-profile-answer'));\n    const answerIndex = allButtons.indexOf(button);\n    if (answerIndex < 0) {\n      console.warn('TasteProfileQuiz: ignored answer with invalid choice', { clickedQuestion });\n      return;\n    }\n\n    this.isProcessing = true;\n    \n    // Q1 uses data-color instead of x/y/z\n    const color = button.dataset.color;\n    if (color) {\n      this.colorPreference = color; // 'red', 'white', 'both', or 'mix'\n      const redCount = parseInt(button.dataset.redCount || '', 10);\n      const whiteCount = parseInt(button.dataset.whiteCount || '', 10);\n      this.colorMixPreference = color === 'mix' && Number.isFinite(redCount) && Number.isFinite(whiteCount)\n        ? { red: redCount, white: whiteCount }\n        : null;\n      this.answers = this.answers.filter(answer => Number(answer.question) !== clickedQuestion);\n      this.answers.push({\n        question: clickedQuestion,\n        apiField,\n        color,\n        redCount: this.colorMixPreference ? this.colorMixPreference.red : null,\n        whiteCount: this.colorMixPreference ? this.colorMixPreference.white : null,\n        choice: answerIndex,\n        apiChoice: color === 'mix' ? 2 : answerIndex,\n        label: this.getAnswerButtonLabel(button)\n      });\n      this.adaptQuestionsToColor(color === 'mix' ? 'both' : color);\n    } else {\n      const x = parseFloat(button.dataset.x);\n      const y = parseFloat(button.dataset.y);\n      const z = parseFloat(button.dataset.z) || 0;\n      if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {\n        this.isProcessing = false;\n        console.warn('TasteProfileQuiz: ignored answer with invalid coordinates', {\n          clickedQuestion,\n          x: button.dataset.x,\n          y: button.dataset.y,\n          z: button.dataset.z\n        });\n        return;\n      }\n      \n      // Accumulate coordinates (X, Y, Z)\n      this.targetX += x;\n      this.targetY += y;\n      this.targetZ += z;\n      \n      // Store answer with choice index\n      this.answers = this.answers.filter(answer => Number(answer.question) !== clickedQuestion);\n      this.answers.push({ question: clickedQuestion, apiField, x, y, z, choice: answerIndex, label: this.getAnswerButtonLabel(button) });\n    }\n    \n    // Animate button selection\n    button.classList.add('selected');\n    \n    setTimeout(() => {\n      if (!this.hasAnswerForQuestion(clickedQuestion)) {\n        this.isProcessing = false;\n        button.classList.remove('selected');\n        console.warn('TasteProfileQuiz: answer was not stored, staying on current question', {\n          clickedQuestion,\n          currentQuestion: this.currentQuestion\n        });\n        return;\n      }\n\n      if (this.currentQuestion < this.totalQuestions) {\n        // Move to next question\n        this.currentQuestion++;\n        this.updateProgress();\n        this.showQuestion(this.currentQuestion);\n        button.classList.remove('selected');\n        // Save state so language switch doesn't lose progress\n        this.saveQuizState();\n        this.isProcessing = false;\n      } else {\n        // All questions answered. Clear in-progress state, fetch results\n        window.VinPopTracking?.trackQuizCompleted({ quiz_type: 'test-kit' });\n        sessionStorage.removeItem('vinpop_quiz_state');\n        this.saveCompletedQuizToApi('final_answer')\n          .catch(error => {\n            console.warn('TasteProfileQuiz: could not save quiz answers:', error);\n          })\n          .finally(() => this.fetchResults());\n      }\n    }, 300);\n  }\n\n  // options.skipScroll: page-load restores pass this so the visitor stays on the\n  // hero instead of being yanked down to the resumed question. User-driven\n  // navigation (answering, back/next) omits it and keeps the scroll-into-view.\n  showQuestion(questionNumber, options = {}) {\n    const questions = this.popup.querySelectorAll('.taste-profile-question');\n    let visibleEl = null;\n    questions.forEach(q => {\n      const qNum = parseInt(q.dataset.question);\n      const show = qNum === questionNumber;\n      q.style.display = show ? 'block' : 'none';\n      if (show) visibleEl = q;\n      // Remove selected class from all answers\n      q.querySelectorAll('.taste-profile-answer').forEach(btn => {\n        btn.classList.remove('selected');\n      });\n    });\n\n    // In inline mode, scroll the question into view\n    const isInline = this.popup && this.popup.classList.contains('taste-profile-popup--inline');\n    if (isInline && visibleEl && questionNumber > 0 && !options.skipScroll) {\n      requestAnimationFrame(() => {\n        const headerH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-group-height')) || 120;\n        const top = visibleEl.getBoundingClientRect().top + window.pageYOffset - headerH - 16;\n        window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });\n      });\n    }\n  }\n\n  startQuestions() {\n    this.currentQuestion = 1;\n    const progressEl = this.popup.querySelector('.taste-profile-progress');\n    if (progressEl) progressEl.style.display = '';\n    this.updateProgress();\n    this.showQuestion(1);\n    this.saveQuizState();\n  }\n\n  // Not a question: currentQuestion stays 0 so no answer can register, and the\n  // progress bar stays hidden. showQuestion() never matches data-question=\"path\".\n  showPathChooser() {\n    this.currentQuestion = 0;\n    this.showQuestion(-1, { skipScroll: true });\n    const progressEl = this.popup.querySelector('.taste-profile-progress');\n    if (progressEl) progressEl.style.display = 'none';\n    this.pathChooser.style.display = 'block';\n    requestAnimationFrame(() => {\n      const headerH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-group-height')) || 120;\n      const top = this.pathChooser.getBoundingClientRect().top + window.pageYOffset - headerH - 16;\n      window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });\n    });\n  }\n\n  setQuizPath(path) {\n    this.quizPath = path === 'expert' ? 'expert' : 'guided';\n    if (this.popup) this.popup.classList.toggle('is-expert-path', this.quizPath === 'expert');\n    try { sessionStorage.setItem('vinpop_quiz_path', this.quizPath); } catch (e) {}\n  }\n\n  hasAnswerForQuestion(questionNumber) {\n    return this.answers.some(answer => Number(answer.question) === Number(questionNumber));\n  }\n\n  /**\n   * After Q1 (color), adapt the explanation text of Q2-Q6\n   * to mention the selected wine color for a more personalized feel\n   */\n  adaptQuestionsToColor(color) {\n    if (color === 'mix') color = 'both';\n    const isEN = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n    const explanations = {};\n\n    if (color === 'red') {\n      explanations[2] = isEN\n        ? '\u2615 This reveals your sensitivity to bitterness and tannins. Key elements in red wines.'\n        : '\u2615 Dit onthult je gevoeligheid voor bitterheid en tannines. Sleutelelementen in rode wijnen.';\n      explanations[3] = isEN\n        ? '\uD83C\uDF6B Your chocolate preference shows your taste for sweetness vs. structure in red wines.'\n        : '\uD83C\uDF6B Je chocoladevoorkeur onthult je smaak voor zoetheid vs. structuur in rode wijnen.';\n      explanations[4] = isEN\n        ? '\uD83E\uDD57 This measures your acidity tolerance. Important for fresh, balanced reds.'\n        : '\uD83E\uDD57 Dit meet je zuurtolerantie. Belangrijk voor frisse, evenwichtige rode wijnen.';\n      explanations[5] = isEN\n        ? '\uD83E\uDDC0 Cheese maturity mirrors wine complexity. Aged cheese lovers often enjoy aged reds.'\n        : '\uD83E\uDDC0 Kaasrijping weerspiegelt wijn complexiteit. Liefhebbers van oude kaas genieten vaak van gerijpte rode wijnen.';\n      explanations[6] = isEN\n        ? '\uD83C\uDFAF Final calibration: this helps us find your perfect red wine style.'\n        : '\uD83C\uDFAF Laatste kalibratie: dit helpt ons je perfecte rode wijnstijl te vinden.';\n    } else if (color === 'white') {\n      explanations[2] = isEN\n        ? '\u2615 This reveals your sensitivity to bitterness. Helping us match the right white wine style.'\n        : '\u2615 Dit onthult je gevoeligheid voor bitterheid. Zo vinden we de juiste witte wijnstijl.';\n      explanations[3] = isEN\n        ? '\uD83C\uDF6B Your chocolate preference reveals your taste for sweetness. Essential for your ideal white wine.'\n        : '\uD83C\uDF6B Je chocoladevoorkeur onthult je smaak voor zoetheid. Essentieel voor jouw ideale witte wijn.';\n      explanations[4] = isEN\n        ? '\uD83E\uDD57 This measures your acidity tolerance. The heart of crisp, refreshing whites.'\n        : '\uD83E\uDD57 Dit meet je zuurtolerantie. Het hart van heldere, verfrissende witte wijnen.';\n      explanations[5] = isEN\n        ? '\uD83E\uDDC0 Cheese pairing reveals your complexity preference in white wines.'\n        : '\uD83E\uDDC0 Kaasvoorkeur onthult je complexiteitsvoorkeur in witte wijnen.';\n      explanations[6] = isEN\n        ? '\uD83C\uDFAF Final calibration: this helps us find your perfect white wine style.'\n        : '\uD83C\uDFAF Laatste kalibratie: dit helpt ons je perfecte witte wijnstijl te vinden.';\n    }\n    // 'both' = restore original neutral explanations\n    if (color === 'both') {\n      explanations[2] = isEN\n        ? '\u2615 This reveals your sensitivity to bitterness and tannins. Key elements in red wines.'\n        : '\u2615 Dit onthult je gevoeligheid voor bitterheid en tannines. Sleutelelementen in rode wijnen.';\n      explanations[3] = isEN\n        ? '\uD83C\uDF6B Chocolate preference correlates with your taste for sweetness vs. complex, structured wines.'\n        : '\uD83C\uDF6B Chocoladevoorkeur correleert met je smaak voor zoetheid vs. complexe, gestructureerde wijnen.';\n      explanations[4] = isEN\n        ? '\uD83E\uDD57 This measures your acidity tolerance. The backbone of fresh, crisp wines.'\n        : '\uD83E\uDD57 Dit meet je zuurtolerantie. De ruggengraat van frisse, heldere wijnen.';\n      explanations[5] = isEN\n        ? '\uD83E\uDDC0 Cheese maturity mirrors wine complexity. Aged cheese lovers often enjoy aged wines.'\n        : '\uD83E\uDDC0 Kaasrijping weerspiegelt wijncomplexiteit. Liefhebbers van oude kaas genieten vaak van gerijpte wijnen.';\n      explanations[6] = isEN\n        ? '\uD83C\uDFAF Final calibration: this helps us fine-tune your perfect wine style.'\n        : '\uD83C\uDFAF Laatste kalibratie: dit helpt ons je perfecte wijnstijl te bepalen.';\n    }\n\n    Object.entries(explanations).forEach(([qNum, text]) => {\n      const qEl = document.querySelector(`.taste-profile-question[data-question=\"${qNum}\"]`);\n      if (qEl) {\n        const expEl = qEl.querySelector('.taste-profile-explanation');\n        if (expEl) expEl.textContent = text;\n      }\n    });\n\n    // Q6 (mood) images follow the chosen color. White uses the white set;\n    // red and both keep the red set (the Liquid default).\n    // Expert answers name grapes and styles, which only make sense per colour.\n    const subAttr = color === 'red' ? 'data-sub-red' : color === 'white' ? 'data-sub-white' : 'data-sub-both';\n    this.popup.querySelectorAll('.tpq-expert .answer-sub[data-sub-both]').forEach(el => {\n      const text = el.getAttribute(subAttr);\n      if (text) el.textContent = text;\n    });\n\n    const moodAttr = color === 'white' ? 'data-img-white' : 'data-img-red';\n    const q6 = document.querySelector('.taste-profile-question[data-question=\"6\"]');\n    if (q6) {\n      q6.querySelectorAll('.answer-img[data-img-red]').forEach(img => {\n        const url = img.getAttribute(moodAttr);\n        if (url) img.src = url;\n      });\n    }\n  }\n\n  updateProgress() {\n    const progress = (this.currentQuestion / this.totalQuestions) * 100;\n    this.progressFill.style.width = progress + '%';\n    this.progressText.textContent = this.currentQuestion;\n    \n    // Show/hide back button\n    if (this.backBtn) {\n      const canGoBack = this.currentQuestion > 1 || (this.currentQuestion === 1 && !!this.pathChooser);\n      this.backBtn.style.display = canGoBack ? 'flex' : 'none';\n    }\n  }\n\n  goBack() {\n    if (this.isProcessing) {\n      return;\n    }\n\n    if (this.currentQuestion === 1 && this.pathChooser) {\n      this.showPathChooser();\n      return;\n    }\n\n    if (this.currentQuestion > 1) {\n      // Remove last answer\n      const lastAnswer = this.answers.pop();\n      if (lastAnswer) {\n        if (lastAnswer.color) {\n          // Q1: reset color preference and restore original explanations\n          this.colorPreference = 'both';\n          this.colorMixPreference = null;\n          const mixAnswer = this.popup.querySelector('.taste-profile-answer--mix');\n          const mixTrigger = mixAnswer ? mixAnswer.querySelector('[data-mix-trigger]') : null;\n          const mixDropdown = mixAnswer ? mixAnswer.querySelector('[data-mix-dropdown]') : null;\n          const mixTotal = mixAnswer ? mixAnswer.querySelector('[data-mix-total]') : null;\n          if (mixTrigger) mixTrigger.textContent = mixDropdown?.dataset?.mixPlaceholder || 'Choose';\n          if (mixDropdown) mixDropdown.classList.remove('is-open');\n          if (mixAnswer) {\n            mixAnswer.dataset.redCount = '0';\n            mixAnswer.dataset.whiteCount = '0';\n          }\n          if (mixTotal) mixTotal.textContent = '0 / 3';\n          this.adaptQuestionsToColor('both');\n        } else {\n          // Q2-Q6: subtract the coordinates\n          this.targetX -= lastAnswer.x;\n          this.targetY -= lastAnswer.y;\n          this.targetZ -= lastAnswer.z || 0;\n        }\n      }\n      \n      // Go to previous question\n      this.currentQuestion--;\n      this.updateProgress();\n      this.showQuestion(this.currentQuestion);\n      this.saveQuizState();\n    }\n  }\n\n  saveQuizState() {\n    const state = {\n      currentQuestion: this.currentQuestion,\n      targetX: this.targetX,\n      targetY: this.targetY,\n      targetZ: this.targetZ,\n      colorPreference: this.colorPreference,\n      colorMixPreference: this.colorMixPreference,\n      answers: this.answers,\n      quizPath: this.quizPath\n    };\n    sessionStorage.setItem('vinpop_quiz_state', JSON.stringify(state));\n  }\n\n  // Texte visible d'un bouton reponse (titre + sous-titre), pour la trace dans la commande.\n  getAnswerButtonLabel(button) {\n    if (!button) return '';\n    const clean = (text) => String(text || '').replace(/\\s+/g, ' ').trim();\n    const parts = [...button.querySelectorAll('*')]\n      .filter(el => !el.children.length)\n      .map(el => clean(el.textContent))\n      .filter(Boolean);\n    return (parts.length ? parts.join(' / ') : clean(button.textContent)).slice(0, 100);\n  }\n\n  // TRACE DU KIT DANS LA COMMANDE (2026-09-27). Les reponses ne sont sauvees par l'API que pour\n  // un client connecte, et `/api/kits/create` n'existe pas cote API : sans ces proprietes, les\n  // reponses d'un kit achete par le trafic pub sont perdues (cas de la commande #1034).\n  // Prefixe `_` : invisible dans le panier et le checkout, mais stocke sur la commande et lu\n  // par la base du dashboard (`public.orders.line_items[].properties`).\n  // `_Kit algo` dit quelles regles de selection ont produit le kit : voir la section\n  // \u00AB Versions de l'algorithme du kit \u00BB de CHANGELOG.md pour les rejouer.\n  buildKitTraceProperties() {\n    const answers = [...(this.answers || [])]\n      .sort((a, b) => Number(a.question) - Number(b.question))\n      .map(answer => {\n        const label = answer.label || `choix ${Number(answer.choice) + 1}`;\n        if (answer.color) return `Q${answer.question}=${label}`;\n        return `Q${answer.question}=${label} (${answer.x},${answer.y},${answer.z})`;\n      });\n    const fmt = (v) => (Number(v) || 0).toFixed(2);\n    return {\n      '_Quiz answers': answers.join(' | '),\n      '_Quiz axes': `x=${fmt(this.targetX)} y=${fmt(this.targetY)} z=${fmt(this.targetZ)} couleur=${this.colorPreference || '?'} parcours=${this.quizPath || 'standard'}`,\n      '_Kit algo': KIT_ALGO_VERSION\n    };\n  }\n\n  getMetaContent(name) {\n    const meta = document.querySelector(`meta[name=\"${name}\"]`);\n    return meta ? String(meta.getAttribute('content') || '').trim() : '';\n  }\n\n  buildVinpopAuthHeaders() {\n    const customerId = this.getMetaContent('customer-id');\n    const timestamp = this.getMetaContent('friends-auth-timestamp');\n    const signature = this.getMetaContent('friends-auth-signature');\n    if (!customerId || !timestamp || !signature) {\n      throw new Error('vinpop_auth_unavailable');\n    }\n\n    return {\n      'Content-Type': 'application/json',\n      'X-Customer-Id': customerId,\n      'X-Timestamp': timestamp,\n      'X-Signature': signature\n    };\n  }\n\n  buildQuizSavePayload(quizzType) {\n    const fieldByQuestion = {\n      1: 'color',\n      2: 'coffee',\n      3: 'chocolate',\n      4: 'salad',\n      5: 'cheese',\n      6: 'mood'\n    };\n    const payload = { quizz_type: quizzType };\n\n    this.answers.forEach(answer => {\n      const field = answer.apiField || fieldByQuestion[Number(answer.question)];\n      const value = Number.isFinite(Number(answer.apiChoice)) ? Number(answer.apiChoice) + 1 : Number(answer.choice) + 1;\n      if (field && value >= 1 && value <= 3) {\n        payload[field] = value;\n      }\n    });\n\n    if (!payload.color) {\n      const colorChoiceByPreference = {\n        red: 1,\n        white: 2,\n        both: 3,\n        mix: 3\n      };\n      const colorValue = colorChoiceByPreference[this.colorPreference];\n      if (colorValue) {\n        payload.color = colorValue;\n      }\n    }\n\n    Object.keys(fieldByQuestion).forEach(question => {\n      if (payload[fieldByQuestion[question]]) {\n        return;\n      }\n      const answer = this.answers.find(item => Number(item.question) === Number(question));\n      const value = answer ? Number(answer.choice) + 1 : NaN;\n      if (value >= 1 && value <= 3) {\n        payload[fieldByQuestion[question]] = value;\n      }\n    });\n\n    return payload;\n  }\n\n  async saveQuizToApi() {\n    const payload = this.buildQuizSavePayload('test-kit');\n    console.log('TasteProfileQuiz: saving quiz answers', {\n      quizz_type: payload.quizz_type,\n      hasCompletePayload: ['color', 'coffee', 'chocolate', 'salad', 'cheese', 'mood'].every(field => payload[field] >= 1 && payload[field] <= 3),\n      payloadFields: Object.keys(payload).filter(field => field !== 'quizz_type'),\n      authAvailable: {\n        customerId: !!this.getMetaContent('customer-id'),\n        timestamp: !!this.getMetaContent('friends-auth-timestamp'),\n        signature: !!this.getMetaContent('friends-auth-signature')\n      }\n    });\n    const requiredFields = ['color', 'coffee', 'chocolate', 'salad', 'cheese', 'mood'];\n    if (!requiredFields.every(field => payload[field] >= 1 && payload[field] <= 3)) {\n      throw new Error('quiz_answers_incomplete');\n    }\n\n    const apiBase = (this.getMetaContent('api-url') || 'https://api.vinpop.fr').replace(/\\/+$/, '');\n    const response = await fetch(`${apiBase}/api/quizz/save`, {\n      method: 'POST',\n      headers: this.buildVinpopAuthHeaders(),\n      credentials: 'omit',\n      mode: 'cors',\n      body: JSON.stringify(payload)\n    });\n\n    if (!response.ok) {\n      throw new Error('quiz_save_failed_' + response.status);\n    }\n\n    return response;\n  }\n\n  ensureQuizSavedToApi() {\n    const payload = this.buildQuizSavePayload('test-kit');\n    const signature = JSON.stringify(payload);\n    if (this.quizSavePromise && this.lastQuizSaveSignature === signature) {\n      return this.quizSavePromise;\n    }\n\n    this.lastQuizSaveSignature = signature;\n    this.quizSavePromise = this.saveQuizToApi();\n    return this.quizSavePromise;\n  }\n\n  saveCompletedQuizToApi(source) {\n    const payload = this.buildQuizSavePayload('test-kit');\n    const requiredFields = ['color', 'coffee', 'chocolate', 'salad', 'cheese', 'mood'];\n    const missingFields = requiredFields.filter(field => !(payload[field] >= 1 && payload[field] <= 3));\n\n    if (missingFields.length) {\n      console.warn('TasteProfileQuiz: not saving incomplete quiz payload', {\n        source,\n        answersCount: this.answers.length,\n        expectedAnswers: this.totalQuestions,\n        payloadFields: Object.keys(payload).filter(field => field !== 'quizz_type'),\n        missingFields\n      });\n      return Promise.reject(new Error('quiz_answers_incomplete'));\n    }\n\n    console.log('TasteProfileQuiz: completed quiz, queueing API save', {\n      source,\n      answers: this.answers.map(answer => ({\n        question: answer.question,\n        apiField: answer.apiField,\n        choice: Number(answer.choice) + 1\n      }))\n    });\n\n    return this.ensureQuizSavedToApi();\n  }\n\n  /**\n   * KIT SNAPSHOT \u2014 everything the printed recap sheet and the guest rating page need,\n   * frozen at the moment the customer adds the kit to the cart. See `KIT_RECAP_REFERRAL.md`.\n   *\n   * Why this exists even though the API already stores quizzes and Shopify stores orders:\n   *\n   * 1. **The quiz is only saved for logged-in visitors.** `saveQuizToApi()` goes through\n   *    `buildVinpopAuthHeaders()`, which throws `vinpop_auth_unavailable` when the\n   *    `customer-id` / `friends-auth-*` meta tags are absent \u2014 and `layout/theme.liquid`\n   *    only prints those inside `{%- if customer -%}`. Both call sites swallow that error\n   *    with a `console.warn`. On paid Meta traffic, where most buyers check out without an\n   *    account, `/api/quizz/get` therefore has nothing for the order that just came in.\n   * 2. **The order only stores wine titles**, not ids (`Quiz Match #n` = `product.title`),\n   *    so rebuilding the four fiches from the order means fuzzy title matching.\n   * 3. **The fingerprint axes are not reproducible.** `_barWineX/Y/Z` are min-max\n   *    normalised against the candidate set of one quiz run; recomputing them later draws\n   *    a different triangle than the customer saw.\n   *\n   * The DOM is the source for role label and axes rather than `_packWines` alone, because\n   * `.wr-bundle-item` is literally what the customer looked at and what `openProductModal`\n   * reads \u2014 the sheet and the popup then cannot disagree.\n   */\n  buildKitSnapshotPayload(properties) {\n    const isEN = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n    const catalog = window.shopCatalog || {};\n    const cardById = {};\n    document.querySelectorAll('.wr-bundle-item[data-wine-id]').forEach(el => {\n      cardById[String(el.dataset.wineId)] = el;\n    });\n\n    // Same mapping `openProductModal()` uses for the \"your taste\" triangle. Note it is a\n    // different normalisation from the wine axes (raw target clamped to \u00B11.2, not min-max);\n    // that asymmetry is pre-existing and is reproduced here on purpose so the printed chart\n    // matches the on-screen one exactly.\n    const targetToPct = (v) => Math.round(((Math.max(-1.2, Math.min(1.2, Number(v) || 0)) + 1.2) / 2.4) * 100);\n\n    const wines = (this._packWines || []).map((wine, index) => {\n      const id = String(wine && wine.wine_id ? wine.wine_id : '');\n      const product = catalog[id] || {};\n      const card = cardById[id];\n      // null rather than 0 when an axis was never measured: `wine-fiche.js` omits the whole\n      // chart on null, but would draw a fully collapsed triangle on 0, which reads as \"this\n      // wine scores zero on everything\". `Number(null)` is 0, hence the explicit guard.\n      const axisFrom = (key, fallback) => {\n        const fromCard = card ? parseInt(card.dataset[key], 10) : NaN;\n        if (Number.isFinite(fromCard)) return fromCard;\n        if (fallback === null || fallback === undefined || fallback === '') return null;\n        return Number.isFinite(Number(fallback)) ? Math.round(Number(fallback)) : null;\n      };\n\n      return {\n        wine_id: id,\n        title: product.title || '',\n        handle: product.handle || '',\n        image: (product.image && product.image.url) || '',\n        role_label: (card && card.dataset.typeLabel) || '',\n        why: (card && card.dataset.molecularText) || '',\n        price_cents: card ? parseInt(card.dataset.displayPrice || '0', 10) : null,\n        is_gift: Boolean(wine && wine._isBonus),\n        position: index + 1,\n        axes: {\n          wx: axisFrom('axisX', wine && wine._barWineX),\n          wy: axisFrom('axisY', wine && wine._barWineY),\n          wz: axisFrom('axisZ', wine && wine._barWineZ)\n        }\n      };\n    }).filter(entry => entry.wine_id);\n\n    // The gifted fourth bottle is a line item property, not a `_packWines` entry, so it is\n    // appended here or the sheet would print three fiches for a four-bottle box.\n    const giftId = String(this._fastBonusProductId || '');\n    if (giftId && !wines.some(entry => entry.wine_id === giftId)) {\n      const giftProduct = catalog[giftId] || {};\n      wines.push({\n        wine_id: giftId,\n        title: giftProduct.title || '',\n        handle: giftProduct.handle || '',\n        image: (giftProduct.image && giftProduct.image.url) || '',\n        role_label: isEN ? '\uD83C\uDF81 Free bottle' : '\uD83C\uDF81 Gratis fles',\n        why: '',\n        price_cents: null,\n        is_gift: true,\n        position: wines.length + 1,\n        axes: { wx: null, wy: null, wz: null }\n      });\n    }\n\n    return {\n      locale: isEN ? 'en' : 'nl',\n      quizz_type: 'test-kit',\n      // Same 1-3 encoding `/api/quizz/save` uses, so a snapshot and an API-stored quiz can\n      // be read by one renderer.\n      quiz: this.buildQuizSavePayload('test-kit'),\n      color_preference: this.colorPreference || null,\n      taste: {\n        ux: targetToPct(this.targetX),\n        uy: targetToPct(this.targetY),\n        uz: targetToPct(this.targetZ)\n      },\n      wines,\n      // Echoed back so the warehouse sheet and the recap sheet can be reconciled if the\n      // property names ever change.\n      line_item_properties: properties || {},\n      customer_id: this.getMetaContent('customer-id') || null,\n      created_at: new Date().toISOString()\n    };\n  }\n\n  /**\n   * POSTs the snapshot and returns its short token, or null.\n   *\n   * Never throws and never blocks for long: this runs on the add-to-cart path of the\n   * highest-intent button on the site, so an API outage must cost the recap sheet, not the\n   * sale. A local copy is kept so a snapshot lost to a timeout can still be replayed.\n   */\n  async persistKitSnapshot(properties) {\n    let payload;\n    try {\n      payload = this.buildKitSnapshotPayload(properties);\n    } catch (error) {\n      console.warn('TasteProfileQuiz: could not build kit snapshot', error);\n      return null;\n    }\n\n    if (!payload.wines.length) return null;\n\n    try {\n      localStorage.setItem('vinpop_kit_snapshot', JSON.stringify(payload));\n    } catch (error) {\n      // Private mode or a full quota. The POST below is what matters.\n    }\n\n    const apiBase = (this.getMetaContent('api-url') || 'https://api.vinpop.fr').replace(/\\/+$/, '');\n    const controller = new AbortController();\n    const timeout = setTimeout(() => controller.abort(), 4000);\n\n    try {\n      const response = await fetch(`${apiBase}/api/kits/create`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'omit',\n        mode: 'cors',\n        signal: controller.signal,\n        body: JSON.stringify(payload)\n      });\n      if (!response.ok) throw new Error('kit_snapshot_failed_' + response.status);\n\n      const data = await response.json();\n      const token = String((data && (data.token || data.kit_token)) || '').trim();\n      if (!token) throw new Error('kit_snapshot_no_token');\n\n      try {\n        localStorage.setItem('vinpop_kit_token', token);\n      } catch (error) { /* see above */ }\n\n      return token;\n    } catch (error) {\n      console.warn('TasteProfileQuiz: kit snapshot not stored, order will ship without a recap sheet', error);\n      return null;\n    } finally {\n      clearTimeout(timeout);\n    }\n  }\n\n  restoreQuizState(state) {\n    this.currentQuestion = state.currentQuestion;\n    this.targetX = state.targetX;\n    this.targetY = state.targetY;\n    this.targetZ = state.targetZ;\n    this.colorPreference = state.colorPreference;\n    this.colorMixPreference = state.colorMixPreference || null;\n    this.answers = state.answers;\n    this.setQuizPath(state.quizPath);\n\n    // Open popup and show the correct question\n    const isInline = this.popup.classList.contains('taste-profile-popup--inline');\n    this.popup.style.display = 'block';\n    if (!isInline) {\n      document.body.style.overflow = 'hidden';\n    }\n\n    // Hide loading screen\n    if (this.loadingEl) this.loadingEl.style.display = 'none';\n\n    // Show progress bar and update it\n    const progressEl = this.popup.querySelector('.taste-profile-progress');\n    if (progressEl) progressEl.style.display = '';\n    this.updateProgress();\n    // Page-load restore: render the resumed question but leave the scroll position\n    // alone, so the visitor still lands on the hero. This is the root cause the\n    // hero's pin-to-top guard was compensating for.\n    this.showQuestion(this.currentQuestion, { skipScroll: true });\n\n    // Restore adapted explanations if color was already answered\n    if (this.colorPreference !== 'both') {\n      this.adaptQuestionsToColor(this.colorPreference);\n    }\n\n    // Re-highlight previously selected answer for each answered question\n    this.answers.forEach(a => {\n      const qEl = this.popup.querySelector(`.taste-profile-question[data-question=\"${a.question}\"]`);\n      if (qEl) {\n        const scope = (this.quizPath === 'expert' && qEl.querySelector('.tpq-expert')) || qEl;\n        const btns = scope.querySelectorAll('.taste-profile-answer');\n        if (btns[a.choice]) btns[a.choice].classList.add('selected');\n      }\n    });\n  }\n\n  markQuizCompleted() {\n    // Store completion in localStorage (permanent) and sessionStorage (current session)\n    localStorage.setItem('vinpop-quiz-completed', 'true');\n    sessionStorage.setItem('vinpop-quiz-completed', 'true');\n    console.log('\u2705 Quiz marked as completed');\n  }\n\n  isQuizCompleted() {\n    // Check if quiz has been completed (in either storage)\n    return localStorage.getItem('vinpop-quiz-completed') === 'true' || \n           sessionStorage.getItem('vinpop-quiz-completed') === 'true';\n  }\n\n  async fetchResults(options = {}) {\n    const silentUpdate = !!options.silentUpdate;\n    const preserveScrollOpt = !!options.preserveScroll;\n    const axisUpdateRequestId = silentUpdate ? ++this.axisUpdateRequestId : 0;\n    // Show loading state\n    if (!silentUpdate) {\n      this.popup.querySelectorAll('.taste-profile-question').forEach(q => q.style.display = 'none');\n      // Calibration: skip the old spinner/DNA screens \u2014 show a dark analysis bridge that flows\n      // straight into the reveal's 3D map intro.\n      if (/\\/collections\\/(?:test-kit|tasting-kit)(?:\\/|$)/.test(window.location.pathname)) {\n        const _en = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n        this.loadingEl.classList.add('taste-profile-loading--calib');\n        this.loadingEl.innerHTML = `<div class=\"wr-analysis-bridge\"><span class=\"wr-analysis-bridge__pulse\"></span><p>${_en ? 'Analysing your taste\u2026' : 'Je smaak analyseren\u2026'}</p></div>`;\n      }\n      this.loadingEl.style.display = 'block';\n    }\n\n    try {\n      if (!options.skipQuizSave && !silentUpdate) {\n        await this.saveCompletedQuizToApi('fetch_results').catch(error => {\n          console.warn('TasteProfileQuiz: could not save quiz answers before results:', error);\n        });\n      }\n\n      // Get API URL from meta tag or default\n      const apiUrl = document.querySelector('meta[name=\"api-url\"]')?.content || 'https://api.vinpop.fr';\n      \n      // Fetch all wines with coordinates\n      const response = await fetch(`${apiUrl}/api/wines/getMapAll`);\n      \n      if (!response.ok) {\n        throw new Error('Failed to fetch wine data');\n      }\n      \n      const wines = await response.json();\n      \n      // Exclude wines that are too extreme for the quiz's Overton window\n      const excludedIds = ['15510626763139'];\n      const eligibleWines = wines.filter(w => !excludedIds.includes(String(w.wine_id)));\n      \n      // Filter by color preference (Q6) BEFORE scoring\n      // API field: wine.color = 'R' (red) or 'W' (white)\n      let filteredWines = eligibleWines;\n      if (this.colorPreference === 'red') {\n        const redWines = eligibleWines.filter(w => (w.color || '').toUpperCase() === 'R');\n        filteredWines = redWines;\n        if (redWines.length < 3) console.warn('\u26A0\uFE0F Only', redWines.length, 'red wines found; keeping strict red filter');\n      } else if (this.colorPreference === 'white') {\n        const whiteWines = eligibleWines.filter(w => (w.color || '').toUpperCase() === 'W');\n        filteredWines = whiteWines;\n        if (whiteWines.length < 3) console.warn('\u26A0\uFE0F Only', whiteWines.length, 'white wines found; keeping strict white filter');\n      }\n      // 'both' and 'mix' = no hard filter; final ordering respects the requested mix.\n      console.log(`\uD83C\uDF77 Color filter: ${this.colorPreference} \u2192 ${filteredWines.length}/${eligibleWines.length} eligible wines`);\n\n      // Filter by price: only wines under \u20AC14\n      const MAX_PRICE_CENTS = 1400;\n      const catalog = window.shopCatalog || {};\n      const beforePriceCount = filteredWines.length;\n      const priceFiltered = filteredWines.filter(w => {\n        const p = catalog[String(w.wine_id)];\n        return p && this.getPaidDisplayPrice(p) <= MAX_PRICE_CENTS;\n      });\n      filteredWines = priceFiltered.length >= 3 ? priceFiltered : filteredWines;\n      console.log(`\uD83D\uDCB0 Price filter (<\u20AC14): ${priceFiltered.length}/${beforePriceCount} wines`);\n\n      // S\u00C9CURIT\u00C9: si 0 vins apr\u00E8s tous les filtres, utiliser TOUS les vins bruts de l'API\n      if (filteredWines.length === 0 && wines.length > 0) {\n        const strictFallback = this.colorPreference === 'red'\n          ? wines.filter(w => (w.color || '').toUpperCase() === 'R')\n          : this.colorPreference === 'white'\n            ? wines.filter(w => (w.color || '').toUpperCase() === 'W')\n            : wines;\n        console.warn('\u26A0\uFE0F 0 wines after all filters, using strict color fallback:', strictFallback.length);\n        filteredWines = strictFallback;\n      }\n\n      if (filteredWines.length === 0) {\n        throw new Error(`No wines available for color preference: ${this.colorPreference}`);\n      }\n      \n      // Calculate scores\n      const winesWithScores = this.calculateScores(filteredWines);\n\n      // The cards only show the top matches, but the calibration pack must be\n      // free to reach further down the list to hold the lab similarity band.\n      this.calibrationSearchPool = winesWithScores;\n      \n      // Normalize map_intensity (Z axis) to same [-1,1] range as X/Y\n      const rawZs = filteredWines.map(w => parseFloat(w.map_intensity) || 0);\n      const zMin = Math.min.apply(null, rawZs);\n      const zMax = Math.max.apply(null, rawZs);\n      const zCenter = (zMin + zMax) / 2;\n      const zHalfRange = (zMax - zMin) / 2 || 1;\n      console.log('\uD83D\uDDFA\uFE0F Z normalization: raw range', zMin.toFixed(2), '\u2192', zMax.toFixed(2), ', center:', zCenter.toFixed(2), ', halfRange:', zHalfRange.toFixed(2));\n\n      // \u2500\u2500 Calibrate user XYZ to wine data range \u2500\u2500\n      // The quiz accumulates small deltas (\u00B10.1 to \u00B10.4 per question, 5 questions)\n      // Wine map_x/map_y come from the API with their own range\n      // We must map the user's accumulated XYZ into the same coordinate space as the wines\n      const rawXs = filteredWines.map(w => parseFloat(w.map_x) || 0);\n      const rawYs = filteredWines.map(w => parseFloat(w.map_y) || 0);\n      const xWineMin = Math.min.apply(null, rawXs), xWineMax = Math.max.apply(null, rawXs);\n      const yWineMin = Math.min.apply(null, rawYs), yWineMax = Math.max.apply(null, rawYs);\n      const xWineCenter = (xWineMin + xWineMax) / 2, xWineHalf = (xWineMax - xWineMin) / 2 || 1;\n      const yWineCenter = (yWineMin + yWineMax) / 2, yWineHalf = (yWineMax - yWineMin) / 2 || 1;\n\n      // Quiz theoretical range for each axis (sum of max possible deltas across 5 questions)\n      // X: [-0.8, 0.8], Y: [-0.9, 1.0], Z: [-1.0, 1.1] approximately\n      // We use a safe \u00B11.2 range for the quiz output\n      const quizRange = 1.2;\n\n      // Map user's quiz coordinates into wine data space\n      const calibratedX = xWineCenter + (this.targetX / quizRange) * xWineHalf;\n      const calibratedY = yWineCenter + (this.targetY / quizRange) * yWineHalf;\n      // Z: user targetZ is already accumulated from quiz, map to normalized [-1,1] wine Z space\n      const calibratedZ = this.targetZ / quizRange;\n\n      console.log('\uD83D\uDDFA\uFE0F User calibration: raw(' + this.targetX.toFixed(2) + ',' + this.targetY.toFixed(2) + ',' + this.targetZ.toFixed(2) + ') \u2192 calibrated(' + calibratedX.toFixed(2) + ',' + calibratedY.toFixed(2) + ',' + calibratedZ.toFixed(2) + ')');\n      console.log('\uD83D\uDDFA\uFE0F Wine X range: [' + xWineMin.toFixed(2) + ', ' + xWineMax.toFixed(2) + '], Y range: [' + yWineMin.toFixed(2) + ', ' + yWineMax.toFixed(2) + ']');\n\n      // Store data for 3D taste map visualization\n      sessionStorage.setItem('vinpop_user_xyz', JSON.stringify({\n        x: calibratedX, y: calibratedY, z: calibratedZ\n      }));\n      const allWinesXYZ = filteredWines.map(w => {\n        const rawZ = parseFloat(w.map_intensity) || 0;\n        const normZ = (rawZ - zCenter) / zHalfRange;\n        return {\n          id: String(w.wine_id),\n          x: parseFloat(w.map_x) || 0,\n          y: parseFloat(w.map_y) || 0,\n          z: normZ,\n          color: (w.color || '').toUpperCase()\n        };\n      });\n      sessionStorage.setItem('vinpop_all_wines_xyz', JSON.stringify(allWinesXYZ));\n      \n      // Debug: show Z distribution\n      const zVals = allWinesXYZ.map(w => w.z);\n      console.log('\uD83D\uDDFA\uFE0F Stored', allWinesXYZ.length, 'wines for 3D viz. Z range:', Math.min.apply(null, zVals).toFixed(2), '\u2192', Math.max.apply(null, zVals).toFixed(2));\n      console.log('\uD83D\uDDFA\uFE0F Sample wines:', allWinesXYZ.slice(0, 3).map(w => '(' + w.x.toFixed(2) + ',' + w.y.toFixed(2) + ',' + w.z.toFixed(2) + ')').join(' '));\n      \n      // Max products: 8 for single color, buffer of 10+10 for both (manager does final 8+8 from Shopify-matched)\n      let topWines;\n      const desiredMix = this.getDesiredColorMix();\n      if (desiredMix && this.colorPreference === 'mix') {\n        const sorted = winesWithScores.sort((a, b) => b.score - a.score);\n        const reds = sorted.filter(w => (w.color || '').toUpperCase() === 'R');\n        const whites = sorted.filter(w => (w.color || '').toUpperCase() === 'W');\n        const mixed = [];\n        for (let i = 0; i < Math.max(desiredMix.red, desiredMix.white, 1); i++) {\n          if (i < desiredMix.red && reds[i]) mixed.push(reds[i]);\n          if (i < desiredMix.white && whites[i]) mixed.push(whites[i]);\n        }\n        const fallback = sorted.filter(w => !mixed.some(item => String(item.wine_id) === String(w.wine_id))).slice(0, 12);\n        topWines = [...mixed, ...fallback].slice(0, 20);\n        console.log(`\uD83C\uDF77 Mixed color buffer: ${desiredMix.red} red + ${desiredMix.white} white preference, ${topWines.length} candidates`);\n      } else if (this.colorPreference === 'both') {\n        // Send larger buffer: top 10 red + top 10 white\n        // Manager will pick final 8+8 from products that exist in Shopify catalog\n        const sorted = winesWithScores.sort((a, b) => b.score - a.score);\n        const topRed = sorted.filter(w => (w.color || '').toUpperCase() === 'R').slice(0, 10);\n        const topWhite = sorted.filter(w => (w.color || '').toUpperCase() === 'W').slice(0, 10);\n        // Combine and re-sort by score\n        topWines = [...topRed, ...topWhite].sort((a, b) => b.score - a.score);\n        console.log(`\uD83C\uDF77 Balanced both (buffer): ${topRed.length} red + ${topWhite.length} white = ${topWines.length} candidates`);\n      } else {\n        topWines = winesWithScores\n          .sort((a, b) => b.score - a.score)\n          .slice(0, 10); // Buffer of 10, manager caps to 8\n        console.log(`\uD83C\uDF77 Showing top wines (color: ${this.colorPreference}), ${topWines.length} candidates`);\n      }\n      \n      // Store wines for reveal\n      this.recommendedWines = topWines;\n\n      if (silentUpdate && axisUpdateRequestId !== this.axisUpdateRequestId) {\n        return;\n      }\n\n      // DNA loading animation (rotating steps, ~3s auto-transition)\n      const _isEN_dna = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n      const _dnaSteps = _isEN_dna ? [\n        { title: 'Analysing your Wine DNA\u2026', sub: 'Reading your answers against our lab-analyzed database.' },\n        { title: 'Calculating taste similarity\u2026', sub: 'No opinions, no scores. Just measurable matches.' },\n        { title: 'Finalizing your matches\u2026', sub: 'Selecting the wines closest to what you already love.' }\n      ] : [\n        { title: 'Analyse van je Wijn DNA\u2026', sub: 'Je antwoorden worden vergeleken met onze laboratoriumdata.' },\n        { title: 'Smaakgelijkenis berekenen\u2026', sub: 'Geen meningen, geen scores. Enkel meetbare matches.' },\n        { title: 'Jouw matches worden afgerond\u2026', sub: 'We selecteren de wijnen die het dichtst bij jouw smaak liggen.' }\n      ];\n      const _isCalib_dna = /\\/collections\\/(?:test-kit|tasting-kit)(?:\\/|$)/.test(window.location.pathname);\n      let _dnaStepTimer = null;\n      // Calibration: no orbs screen \u2014 the reveal's 3D map intro is the analysis. Other flows keep the DNA loader.\n      if (!silentUpdate && this.loadingEl && !_isCalib_dna) {\n        let _dnaStepIndex = 0;\n        const _renderDnaStep = () => {\n          const step = _dnaSteps[_dnaStepIndex];\n          this.loadingEl.innerHTML = `\n            <div class=\"wr-dna-loading\">\n              <div class=\"wr-dna-orbs\">\n                <span class=\"wr-dna-orb wr-dna-orb--1\"></span>\n                <span class=\"wr-dna-orb wr-dna-orb--2\"></span>\n                <span class=\"wr-dna-orb wr-dna-orb--3\"></span>\n              </div>\n              <p class=\"wr-dna-loading__title\">${step.title}</p>\n              <p class=\"wr-dna-loading__sub\">${step.sub}</p>\n            </div>\n          `;\n        };\n        _renderDnaStep();\n        this.loadingEl.style.display = 'block';\n        _dnaStepTimer = setInterval(() => {\n          _dnaStepIndex = (_dnaStepIndex + 1) % _dnaSteps.length;\n          _renderDnaStep();\n        }, 1000);\n      }\n      if (!silentUpdate && !_isCalib_dna) {\n        await new Promise(resolve => setTimeout(resolve, 3000));\n      }\n      if (_dnaStepTimer) clearInterval(_dnaStepTimer);\n      this.markQuizCompleted();\n      if (!silentUpdate && this.loadingEl) this.loadingEl.style.display = 'none';\n      const _progressEl_dna = this.popup.querySelector('.taste-profile-progress');\n      if (!silentUpdate && _progressEl_dna) _progressEl_dna.style.display = 'none';\n      if (/\\/collections\\/(?:test-kit|tasting-kit)(?:\\/|$)/.test(window.location.pathname)) {\n        if (silentUpdate && typeof this.updateBundleItemsFromRecommendations === 'function') {\n          await this.updateBundleItemsFromRecommendations(this.recommendedWines, { requestId: axisUpdateRequestId });\n        } else {\n          this.startWineRevealSequence(this.recommendedWines, { preserveScroll: silentUpdate || preserveScrollOpt });\n        }\n      } else {\n        this.displayResults(this.recommendedWines);\n      }\n      \n    } catch (error) {\n      console.error('Error fetching wine recommendations:', error);\n      if (!silentUpdate) this.showError();\n    }\n  }\n\n  calculateScores(wines) {\n    // Normalize map_intensity (Z axis) to same scale as X/Y for distance calc\n    const rawZvals = wines.map(w => parseFloat(w.map_intensity) || 0);\n    const zMinS = Math.min.apply(null, rawZvals);\n    const zMaxS = Math.max.apply(null, rawZvals);\n    const zCenterS = (zMinS + zMaxS) / 2;\n    const zHalfS = (zMaxS - zMinS) / 2 || 1;\n\n    // Calibrate user XYZ into wine data coordinate space\n    const rawXvals = wines.map(w => parseFloat(w.map_x) || 0);\n    const rawYvals = wines.map(w => parseFloat(w.map_y) || 0);\n    const xMin = Math.min.apply(null, rawXvals), xMax = Math.max.apply(null, rawXvals);\n    const yMin = Math.min.apply(null, rawYvals), yMax = Math.max.apply(null, rawYvals);\n    const xCenter = (xMin + xMax) / 2, xHalf = (xMax - xMin) / 2 || 1;\n    const yCenter = (yMin + yMax) / 2, yHalf = (yMax - yMin) / 2 || 1;\n    const quizRange = 1.2;\n    const calX = xCenter + (this.targetX / quizRange) * xHalf;\n    const calY = yCenter + (this.targetY / quizRange) * yHalf;\n    const calZ = this.targetZ / quizRange;\n\n    // Step 1: Calculate all distances (using calibrated user coordinates)\n    const withDistances = wines.map(wine => {\n      const wineX = parseFloat(wine.map_x) || 0;\n      const wineY = parseFloat(wine.map_y) || 0;\n      const wineZ = ((parseFloat(wine.map_intensity) || 0) - zCenterS) / zHalfS;\n      \n      const distance = Math.sqrt(\n        Math.pow(calX - wineX, 2) + \n        Math.pow(calY - wineY, 2) +\n        Math.pow(calZ - wineZ, 2)\n      );\n      \n      return { ...wine, distance };\n    });\n    \n    // Step 2: Dynamic multiplier. Guarantee top 16 wines at 80%+\n    // (buffer of 10+10 to account for wines missing from Shopify catalog)\n    const GUARANTEE_COUNT = 16;\n    const MIN_SCORE = 80;\n    let multiplier = 15; // default\n    \n    const sorted = [...withDistances].sort((a, b) => a.distance - b.distance);\n    if (sorted.length >= GUARANTEE_COUNT) {\n      const distanceAtGuarantee = sorted[GUARANTEE_COUNT - 1].distance;\n      if (distanceAtGuarantee > 0) {\n        const maxMultiplier = (100 - MIN_SCORE) / distanceAtGuarantee;\n        if (multiplier > maxMultiplier) {\n          multiplier = Math.max(1, maxMultiplier); // never below 1\n          console.log(`\uD83D\uDCCA Dynamic multiplier adjusted: ${multiplier.toFixed(2)} (6th wine distance: ${distanceAtGuarantee.toFixed(3)})`);\n        }\n      }\n    } else if (sorted.length > 0) {\n      // Fewer than 6 wines total. Adjust to last wine\n      const distanceLast = sorted[sorted.length - 1].distance;\n      if (distanceLast > 0) {\n        const maxMultiplier = (100 - MIN_SCORE) / distanceLast;\n        if (multiplier > maxMultiplier) {\n          multiplier = Math.max(1, maxMultiplier);\n        }\n      }\n    }\n    \n    console.log(`\uD83D\uDCCA Score multiplier: ${multiplier.toFixed(2)}`);\n\n    // Pre-compute taste bar percentages for reveal cards\n    const clampPct = (v) => Math.max(3, Math.min(97, v));\n    const xRange = (xMax - xMin) || 1;\n    const yRange = (yMax - yMin) || 1;\n    const zRangeS = (zMaxS - zMinS) || 1;\n    const userRawZ = calZ * zHalfS + zCenterS; // convert normalized Z back to raw intensity space\n    const _barUserX = clampPct((calX - xMin) / xRange * 100);\n    const _barUserY = clampPct((calY - yMin) / yRange * 100);\n    const _barUserZ = clampPct((userRawZ - zMinS) / zRangeS * 100);\n    console.log(`\uD83D\uDCCA Taste bars. User pcts: X=${_barUserX.toFixed(1)}% Y=${_barUserY.toFixed(1)}% Z=${_barUserZ.toFixed(1)}%`);\n    \n    // Step 3: Apply scores with dynamic multiplier + bar data\n    return withDistances.map(wine => {\n      let score = 100 - (wine.distance * multiplier);\n      score = Math.max(0, Math.min(100, score));\n      const wX = parseFloat(wine.map_x) || 0;\n      const wY = parseFloat(wine.map_y) || 0;\n      const wZ = parseFloat(wine.map_intensity) || 0;\n      return {\n        ...wine,\n        score: Math.round(score),\n        _barUserX, _barUserY, _barUserZ,\n        _barWineX: clampPct((wX - xMin) / xRange * 100),\n        _barWineY: clampPct((wY - yMin) / yRange * 100),\n        _barWineZ: clampPct((wZ - zMinS) / zRangeS * 100),\n      };\n    });\n  }\n\n  /**\n   * Build a human-readable taste profile from quiz answers\n   * Uses X (sweetness/acidity), Y (body/structure), Z (intensity) axes\n   * Used to generate justification text on product cards\n   */\n  buildTasteProfile() {\n    const profile = {\n      tanninTolerance: 'medium',    // Q1: coffee \u2192 Y+Z\n      sweetnessPreference: 'balanced', // Q2: chocolate \u2192 X+Z\n      acidityTolerance: 'medium',   // Q3: dressing \u2192 X+Z\n      complexityPreference: 'medium', // Q4: cheese \u2192 Y+Z\n      moodPreference: 'balanced',   // Q5: mood \u2192 X+Y+Z\n      intensityPreference: 'medium', // Overall Z accumulation\n      colorPreference: this.colorPreference || 'both', // Q6: wine color\n      traits: [],\n      choices: {} // Q1-Q6 answer index (0=A, 1=B, 2=C)\n    };\n\n    // Store raw answer choices for justification\n    this.answers.forEach(a => {\n      if (a.choice !== undefined) profile.choices[`q${a.question}`] = a.choice;\n    });\n\n    // Calculate overall intensity from Z accumulation\n    const totalZ = this.targetZ;\n    if (totalZ > 0.3) {\n      profile.intensityPreference = 'high';\n    } else if (totalZ < -0.3) {\n      profile.intensityPreference = 'low';\n    }\n\n    this.answers.forEach(answer => {\n      const z = answer.z || 0;\n      switch (answer.question) {\n        case 2: // Coffee \u2192 tannin/bitterness (Y=body, Z=intensity)\n          if (answer.y > 0.1 && z > 0.1) {\n            profile.tanninTolerance = 'high';\n            profile.traits.push('bold tannins');\n          } else if (answer.y < -0.1 && z <= 0) {\n            profile.tanninTolerance = 'low';\n            profile.traits.push('smooth & soft');\n          } else if (z < -0.1) {\n            profile.tanninTolerance = 'low';\n            profile.traits.push('light & delicate');\n          }\n          break;\n        case 3: // Chocolate \u2192 sweetness vs structure (X=sweetness, Z=intensity)\n          if (answer.x > 0.2 && z < 0) {\n            profile.sweetnessPreference = 'sweet';\n            profile.traits.push('fruity & sweet');\n          } else if (answer.y > 0.1 && z > 0.1) {\n            profile.sweetnessPreference = 'dry';\n            profile.traits.push('intense & structured');\n          } else if (z < 0) {\n            profile.sweetnessPreference = 'fruity';\n            profile.traits.push('fruity flavors');\n          }\n          break;\n        case 4: // Dressing \u2192 acidity (X=acidity, Z=intensity)\n          if (answer.x < -0.2) {\n            profile.acidityTolerance = 'high';\n            profile.traits.push('crisp acidity');\n          } else if (answer.x > 0.1) {\n            profile.acidityTolerance = 'low';\n            profile.traits.push('rich & round');\n          } else {\n            profile.traits.push('balanced acidity');\n          }\n          break;\n        case 5: // Cheese \u2192 complexity (Y=structure, Z=intensity)\n          if (answer.y > 0.2 && z > 0.2) {\n            profile.complexityPreference = 'complex';\n            profile.traits.push('complex & aged');\n          } else if (answer.y < -0.2 && z < -0.2) {\n            profile.complexityPreference = 'simple';\n            profile.traits.push('fresh & easy');\n          }\n          break;\n        case 6: // Mood \u2192 final calibration (X+Y+Z combined)\n          if (answer.x < -0.1 && z < 0) {\n            profile.moodPreference = 'fresh';\n          } else if (answer.y > 0.1 && z > 0.2) {\n            profile.moodPreference = 'bold';\n          } else {\n            profile.moodPreference = 'smooth';\n          }\n          break;\n      }\n    });\n\n    if (this.axisTweakActive) {\n      profile.traits = [];\n      if (this.targetX < -0.2) {\n        profile.acidityTolerance = 'high';\n        profile.sweetnessPreference = 'fruity';\n        profile.moodPreference = 'fresh';\n        profile.traits.push('crisp acidity', 'fresh & bright');\n      } else if (this.targetX > 0.2) {\n        profile.acidityTolerance = 'low';\n        profile.sweetnessPreference = 'sweet';\n        profile.moodPreference = 'smooth';\n        profile.traits.push('round & smooth', 'soft fruit');\n      }\n      if (this.targetY > 0.2) {\n        profile.tanninTolerance = 'high';\n        profile.complexityPreference = 'complex';\n        profile.traits.push('structured', 'complex & aged');\n      } else if (this.targetY < -0.2) {\n        profile.tanninTolerance = 'low';\n        profile.complexityPreference = 'simple';\n        profile.traits.push('smooth & soft', 'fresh & easy');\n      }\n      if (this.targetZ > 0.3) {\n        profile.intensityPreference = 'high';\n        profile.moodPreference = 'bold';\n        profile.traits.push('bold intensity');\n      } else if (this.targetZ < -0.3) {\n        profile.intensityPreference = 'low';\n        profile.traits.push('light & delicate');\n      }\n    }\n\n    // Deduplicate traits\n    profile.traits = [...new Set(profile.traits)];\n    return profile;\n  }\n\n  displayResults(wines) {\n    // Store results in sessionStorage BEFORE closing popup\n    sessionStorage.setItem('tasteProfileResults', JSON.stringify(wines));\n    \n    // ============ STORE TASTE PROFILE for justification text ============\n    const profile = this.buildTasteProfile();\n    sessionStorage.setItem('vinpop_taste_profile', JSON.stringify(profile));\n    console.log('\uD83C\uDF77 Taste profile saved:', profile);\n    \n    // Save results under the manager's key so checkAndRestoreResults() can render them on reload\n    sessionStorage.setItem('vinpop_last_results', JSON.stringify(wines));\n    \n    // Mark quiz as completed\n    this.markQuizCompleted();\n    \n    // Close the popup\n    this.closePopup();\n    \n    // RELIABLE APPROACH: Reload the page with ?taste_profile=true\n    // On reload, checkAndRestoreResults() in wine-recommendation-manager.js detects\n    // vinpop_last_results in sessionStorage and renders the results cleanly.\n    // This avoids all Alpine.js / Filterable / data-island interference issues\n    // that occur when trying to mutate the DOM after popup close.\n    console.log('\uD83C\uDF77 Reloading page to display quiz results...');\n    window.location.href = window.location.pathname + '?taste_profile=true';\n  }\n\n  /**\n   * Wine-by-wine reveal sequence inside the popup (for starterpack)\n   * Starter Pack: 2 quiz matches + 1 algorithmic discovery (+ optional bonus)\n   */\n  async startWineRevealSequence(wines, options = {}) {\n    const preserveScroll = !!options.preserveScroll;\n    const preservedWindowY = preserveScroll ? window.scrollY : null;\n    const preservedPopupContent = this.popup ? this.popup.querySelector('.taste-profile-popup-content') : null;\n    const preservedPopupY = preserveScroll && preservedPopupContent ? preservedPopupContent.scrollTop : null;\n    const isEN = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n    const catalog = window.shopCatalog || {};\n    const isCatalogProductInStock = (product) => {\n      if (!product || product.available === false) return false;\n      const variants = Array.isArray(product.variants) ? product.variants : [];\n      if (!variants.length) return !!product.available;\n      // `available` stays true for a tracked variant that is set to \"continue\n      // selling when out of stock\" (backorder) even at 0 units. Treat a tracked\n      // variant with 0 or fewer units on hand as out of stock so it is never\n      // recommended, matching the merchant's real inventory. Untracked variants\n      // (inventoryManaged === false) keep relying on `available`.\n      return variants.some(v => {\n        if (!v || v.available === false) return false;\n        if (v.inventoryManaged && Number(v.inventoryQuantity) <= 0) return false;\n        return true;\n      });\n    };\n    const getPaidDisplayPrice = (product) => this.getPaidDisplayPrice(product);\n\n    // Paid bottles always reach \u20AC32,00 on their own, and paid plus gift never passes \u20AC40,00.\n    // Same band for every visitor: the old 2600 floor for paid-ad traffic is gone, since the\n    // gift now ships to everyone and the floor is what makes the box read as a deal.\n    const { min: MIN_PACK_PRICE, max: MAX_PACK_PRICE } = this.getPackPriceBand(catalog, isCatalogProductInStock);\n    const calibrationSelection = await this.buildCalibrationBottleSelection(wines, {\n      isEN,\n      catalog,\n      minPackPrice: MIN_PACK_PRICE,\n      maxPackPrice: MAX_PACK_PRICE,\n      maxPrice: MAX_PACK_PRICE,\n      isCatalogProductInStock,\n      getPaidDisplayPrice\n    });\n    const packWines = [...calibrationSelection.selectedWines];\n    const calibrationVisualization = calibrationSelection.visualization;\n    this._lastVisualization = calibrationVisualization;\n\n    if (packWines.length < 2) {\n      console.warn('\u26A0\uFE0F Not enough catalog matches for starter pack, falling back');\n      this.displayResults(wines);\n      return;\n    }\n\n    // Store results for the page\n    sessionStorage.setItem('tasteProfileResults', JSON.stringify(wines));\n    const profile = this.buildTasteProfile();\n    sessionStorage.setItem('vinpop_taste_profile', JSON.stringify(profile));\n    sessionStorage.setItem('vinpop_last_results', JSON.stringify(wines));\n\n    // Hide progress bar\n    const progressEl = this.popup.querySelector('.taste-profile-progress');\n    if (progressEl) progressEl.style.display = 'none';\n\n    // Hide only quiz question steps while showing the final wine list/bundle\n    this.popup.querySelectorAll('.taste-profile-question').forEach(q => {\n      q.style.display = 'none';\n    });\n\n    // Show wine reveal container\n    const seqEl = this.popup.querySelector('.wine-reveal-sequence');\n    const cardsEl = this.popup.querySelector('.wine-reveal-cards');\n    if (!seqEl || !cardsEl) {\n      this.displayResults(wines);\n      return;\n    }\n    seqEl.style.display = 'block';\n    cardsEl.innerHTML = '';\n\n    // Store pack info for add-to-cart\n    this._packWines = packWines;\n    this._packReplacementCandidates = calibrationSelection.candidates || [];\n\n    // Next best matches after the kit. `added` holds one entry per extra bottle, each\n    // owning a private block of alternatives that Replace cycles through.\n    this._upsell = {\n      pool: this.buildUpsellCandidates(calibrationSelection.candidates, packWines, catalog),\n      added: []\n    };\n\n    // i18n labels\n    const labels = {\n      match:    isEN ? 'match'           : 'match',\n      region:   isEN ? 'Region'          : 'Regio',\n      country:  isEN ? 'Country'         : 'Land',\n      grape:    isEN ? 'Grape'           : 'Druif',\n      color:    isEN ? 'Type'            : 'Type',\n      body:     isEN ? 'Body'            : 'Body',\n      food:     isEN ? 'Food pairing'    : 'Eten & drinken',\n      vintage:  isEN ? 'Vintage'         : 'Jaar',\n      next:     isEN ? 'Next wine \u2192'     : 'Volgende wijn \u2192',\n      prev:     isEN ? '\u2190 Previous'      : '\u2190 Vorige',\n      addCart:  isEN ? 'Add to cart'      : 'In winkelwagen',\n      adding:   isEN ? 'Adding\u2026'         : 'Toevoegen\u2026',\n      added:    isEN ? 'Added!'           : 'Toegevoegd!',\n      seeSel:   isEN ? 'See your Test Kit' : 'Bekijk jouw Test Kit',\n      shipping: isEN ? 'Shipping' : 'Verzending',\n      free:     isEN ? 'FREE'            : 'GRATIS',\n    };\n\n    // Progress dots (wine pages + bundle)\n    const totalDots = packWines.length + 1;\n    const dotsHTML = `<div class=\"wr-dots\">${Array.from({length: totalDots}, (_, i) =>\n      `<span class=\"wr-dot${i === 0 ? ' wr-dot--active' : ''}\" data-dot=\"${i}\"></span>`\n    ).join('')}</div>`;\n    cardsEl.insertAdjacentHTML('beforeend', dotsHTML);\n\n    // Helper: update dots state\n    const updateDots = (activeIdx) => {\n      cardsEl.querySelectorAll('.wr-dot').forEach(dot => {\n        const di = parseInt(dot.dataset.dot);\n        dot.classList.toggle('wr-dot--active', di === activeIdx);\n        dot.classList.toggle('wr-dot--done', di < activeIdx);\n      });\n    };\n\n    // Helper: navigate to a page\n    const goToPage = (pageKey) => {\n      cardsEl.querySelectorAll('.wr-page').forEach(p => p.style.display = 'none');\n      const target = cardsEl.querySelector(`[data-page=\"${pageKey}\"]`);\n      if (target) {\n        target.style.display = 'block';\n        target.classList.add('wr-page--enter');\n        const popupContent = this.popup.querySelector('.taste-profile-popup-content');\n        if (popupContent && !this.popup.classList.contains('taste-profile-popup--inline')) {\n          popupContent.scrollTop = 0;\n        }\n      }\n      const dotIdx = pageKey === 'bundle' ? packWines.length : parseInt(pageKey);\n      updateDots(dotIdx);\n    };\n\n    // Build wine page cards\n    packWines.forEach((wine, idx) => {\n      const p = catalog[String(wine.wine_id)];\n      const imgUrl = p.image && p.image.url ? p.image.url : '';\n      const prodUrl = p.url || `/products/${p.handle}`;\n      const isLast = idx === packWines.length - 1;\n      const isFirst = idx === 0;\n      const nextLabel = isLast ? `\uD83C\uDF81 ${labels.seeSel}` : labels.next;\n\n      // Determine card type\n      let rankLabel, tagHTML;\n      if (wine._isBonus) {\n        rankLabel = isEN ? '\uD83C\uDF81 Bonus' : '\uD83C\uDF81 Bonus';\n        tagHTML = `<span class=\"wr-page__tag wr-page__tag--bonus\">${isEN ? 'Bonus unlocked!' : 'Bonus ontgrendeld!'}</span>`;\n      } else if (wine._calibrationRole) {\n        rankLabel = `#${wine._calibrationIndex || idx + 1}`;\n        tagHTML = `<span class=\"wr-page__tag\">${wine.reason_title || (isEN ? 'Calibration pick' : 'Kalibratiekeuze')}</span>`;\n      } else if (wine._isSimilar) {\n        const similarToTitle = catalog[String(wine._similarTo)] ? catalog[String(wine._similarTo)].title : '';\n        rankLabel = '\uD83D\uDD17 Algorithmic Discovery';\n        tagHTML = `<span class=\"wr-page__tag wr-page__tag--similar\">${isEN\n          ? `Because you love ${similarToTitle || 'your pick'}, our algorithm found ${wine.score}% similarity`\n          : `Omdat jij van ${similarToTitle || 'jouw keuze'} houdt, vond ons algoritme ${wine.score}% gelijkenis`}</span>`;\n      } else {\n        rankLabel = `#${idx + 1}`;\n        tagHTML = '';\n      }\n\n      // Detail rows\n      let detailRows = '';\n      const addRow = (icon, label, value) => {\n        if (value) detailRows += `<div class=\"wr-page__detail\"><span class=\"wr-page__detail-label\">${icon} ${label}</span><span class=\"wr-page__detail-value\">${value}</span></div>`;\n      };\n      addRow('\uD83C\uDF47', labels.grape, p.grapeVariety);\n      addRow('\uD83D\uDCCD', labels.region, p.region);\n      addRow('\uD83C\uDF0D', labels.country, p.country);\n      addRow('\uD83C\uDF77', labels.color, p.wineColor);\n      addRow('\uD83D\uDCC5', labels.vintage, p.vintage);\n      addRow('\uD83D\uDCAA', labels.body, p.body);\n\n      // Food pairing\n      let foodHTML = '';\n      if (p.foodPairing) {\n        const foods = p.foodPairing.split(',').map(f => f.trim()).filter(Boolean);\n        if (foods.length) {\n          foodHTML = `<div class=\"wr-page__food\"><span class=\"wr-page__detail-label\">\uD83C\uDF7D\uFE0F ${labels.food}</span><div class=\"wr-page__food-tags\">${foods.map(f => `<span class=\"wr-page__food-tag\">${f}</span>`).join('')}</div></div>`;\n        }\n      }\n\n      const pageHTML = `\n        <div class=\"wr-page\" data-page=\"${idx}\" style=\"display: none;\">\n          <div class=\"wr-page__hero\">\n            <div class=\"wr-page__rank\">${rankLabel}</div>\n            <span class=\"wr-page__score-badge\">\u2B50 ${wine.score}% ${labels.match}</span>\n            <a href=\"${prodUrl}\" class=\"wr-page__img-wrap\">\n              <img src=\"${imgUrl}\" alt=\"${p.title}\" class=\"wr-page__img\" loading=\"lazy\">\n            </a>\n          </div>\n          ${tagHTML}\n          <h3 class=\"wr-page__title\"><a href=\"${prodUrl}\">${p.title}</a></h3>\n          ${p.vendor ? `<div class=\"wr-page__vendor\">${p.vendor}</div>` : ''}\n          <div class=\"wr-page__details\">${detailRows}</div>\n          ${foodHTML}\n          <div class=\"wr-page__actions\">\n            <div class=\"wr-page__actions-nav\">\n              ${!isFirst ? `<button class=\"wr-page__prev push-btn\" type=\"button\" data-prev=\"${idx - 1}\"><span class=\"push-btn__surface\">${labels.prev}</span></button>` : ''}\n              <button class=\"wr-page__next push-btn\" type=\"button\" data-next=\"${isLast ? 'bundle' : idx + 1}\"><span class=\"push-btn__surface\">${nextLabel}</span></button>\n            </div>\n          </div>\n        </div>\n      `;\n      cardsEl.insertAdjacentHTML('beforeend', pageHTML);\n    });\n\n    // Build Starter Pack bundle page\n\n    // Read taste profile from sessionStorage for molecular match text\n    const savedProfile = (() => { try { return JSON.parse(sessionStorage.getItem('vinpop_taste_profile') || '{}'); } catch(e) { return {}; } })();\n    const getMolecularText = (profile, wineIdx, isEN) => {\n      const tannin = profile.tanninTolerance || 'medium';\n      const acidity = profile.acidityTolerance || 'medium';\n      const sweetness = profile.sweetnessPreference || 'balanced';\n      const complexity = profile.complexityPreference || 'medium';\n      const mood = profile.moodPreference || 'smooth';\n      const en = [\n        tannin === 'high'\n          ? 'You drink it black. Same bold intensity as your espresso.'\n          : tannin === 'low'\n          ? 'You prefer cappuccino or tea. No harsh tannins here.'\n          : 'Your bitterness threshold guided this pick.',\n        acidity === 'high'\n          ? 'You chose lemon vinaigrette. This wine has that same crisp bite.'\n          : acidity === 'low'\n          ? 'You went for creamy dressing. This wine is round, no sharp edges.'\n          : 'You picked olive oil & salt. Clean and balanced.',\n        sweetness === 'dry'\n          ? 'You chose dark chocolate 70%+. Same dry, structured finish.'\n          : sweetness === 'sweet'\n          ? 'You prefer milk chocolate. This wine is soft and fruity.'\n          : 'You picked fruit jellies. Same bright, fruity character.',\n        complexity === 'complex'\n          ? 'You chose aged Gouda. This wine has the same layered depth.'\n          : complexity === 'simple'\n          ? 'You chose Brie. Light, creamy, no rough edges.'\n          : 'Your cheese choice points to a balanced complexity.',\n        mood === 'bold'\n          ? 'You asked for Bold & Powerful. This wine delivers exactly that.'\n          : mood === 'fresh'\n          ? 'You wanted Fresh & Fruity. This is exactly it.'\n          : 'You picked Smooth & Round. That\\'s precisely what this is.',\n      ];\n      const nl = [\n        tannin === 'high'\n          ? 'Jij drinkt het puur zwart. Dezelfde krachtige intensiteit als jouw espresso.'\n          : tannin === 'low'\n          ? 'Jij prefereert cappuccino of thee. Geen zware tannines hier.'\n          : 'Jouw bitterheidsdrempel stuurde deze keuze.',\n        acidity === 'high'\n          ? 'Jij koos citroendressing. Deze wijn heeft dezelfde frisse bite.'\n          : acidity === 'low'\n          ? 'Jij ging voor romige dressing. Ronde wijn, geen scherpe kanten.'\n          : 'Jij koos olijfolie & zout. Clean en uitgebalanceerd.',\n        sweetness === 'dry'\n          ? 'Jij koos pure chocolade 70%+. Dezelfde droge, gestructureerde afdronk.'\n          : sweetness === 'sweet'\n          ? 'Jij houdt van melkchocolade. Deze wijn is zacht en fruitig.'\n          : 'Jij koos fruitgummies. Hetzelfde levendige, fruitige karakter.',\n        complexity === 'complex'\n          ? 'Jij koos oude Gouda. Deze wijn heeft dezelfde gelaagde diepte.'\n          : complexity === 'simple'\n          ? 'Jij koos Brie. Licht, romig, geen ruwe randjes.'\n          : 'Jouw kaaskeuze wijst op een uitgebalanceerde complexiteit.',\n        mood === 'bold'\n          ? 'Jij vroeg om Krachtig & Koel. Deze wijn levert dat precies.'\n          : mood === 'fresh'\n          ? 'Jij wilde Fris & Fruitig. Dit is precies dat.'\n          : 'Jij koos Zacht & Rond. Dat is precies wat dit is.',\n      ];\n      const pool = isEN ? en : nl;\n      return pool[wineIdx % pool.length];\n    };\n\n    const bundleSummaryHTML = this.buildBundleItemCardsHTML(packWines, isEN);\n\n    // Calculate total real retail value of the selected wines for price anchoring.\n    // 2026-08-03 : RAPPEL\u00C9, mais pour une seule chose, la ligne \"tu \u00E9conomises au total\" du\n    // bloc prix. Le port y est nomm\u00E9 \u00E0 part, jamais fondu dans le prix barr\u00E9 ni dans le badge,\n    // qui restent sur le vin seul. Un prix de r\u00E9f\u00E9rence qui m\u00E9lange un produit et un service\n    // est le maillon faible qu'un contr\u00F4le ACM attaque en premier, et il gonflait tout seul :\n    // le port passe de 15,00 \u20AC \u00E0 17,00 \u20AC \u00E0 cinq bouteilles, donc ajouter une bouteille\n    // fabriquait 2,00 \u20AC d'\"\u00E9conomie\" qui \u00E9tait du fret, pas du vin. S\u00E9parer les deux montants\n    // garde le chiffre total (le seul argument que le client additionne) sans cette ancre.\n    const getConfiguredShippingCents = (bottleCount) => {\n      const fallbackCents = 1500;\n      const config = window.VinpopShippingConfig;\n      if (!config || !config.zones) return fallbackCents;\n\n      const customerData = window.VinpopCustomerData || {};\n      const countryCode = customerData.countryCode;\n      const countryName = customerData.country;\n      let zone = null;\n\n      Object.values(config.zones).some(candidate => {\n        const countries = candidate.countries || [];\n        if (countries.includes(countryCode) || countries.includes(countryName)) {\n          zone = candidate;\n          return true;\n        }\n        return false;\n      });\n\n      if (!zone && config.defaultZone && config.zones[config.defaultZone]) {\n        zone = config.zones[config.defaultZone];\n      }\n      if (!zone || !Array.isArray(zone.rates)) return fallbackCents;\n\n      const rate = zone.rates.find(item => bottleCount >= item.minBottles && bottleCount <= item.maxBottles) || zone.rates[zone.rates.length - 1];\n      const price = rate && typeof rate.price === 'number' ? rate.price : null;\n      return Number.isFinite(price) ? Math.round(price * 100) : fallbackCents;\n    };\n\n    const packRetailCents = packWines.reduce(\n      (sum, wine) => sum + getPaidDisplayPrice(catalog[String(wine.wine_id)]),\n      0\n    );\n    const priceBand = this.getPackPriceBand(catalog, isCatalogProductInStock);\n\n    if (this.isOfferDebugEnabled()) {\n      const euros = (cents) => `\u20AC${(Math.round(cents) / 100).toFixed(2).replace('.', ',')}`;\n      console.log(\n        `%c\uD83C\uDF77 OFFRE  3 vins ${euros(packRetailCents)}` +\n        `  ${packRetailCents <= priceBand.max ? '\u2705 dans la bande' : '\u26A0\uFE0F HORS BANDE'} (${euros(priceBand.min)} \u00E0 ${euros(priceBand.max)})`,\n        'font-weight:bold'\n      );\n      console.table(packWines.map((wine, i) => ({\n          role: wine._calibrationRole || (wine._isBonus ? 'calibration_bonus' : wine._isSimilar ? 'similar' : `bottle_${i + 1}`),\n          bouteille: catalog[String(wine.wine_id)]?.title || String(wine.wine_id),\n          prix: euros(getPaidDisplayPrice(catalog[String(wine.wine_id)])),\n          paye: 'oui'\n        })));\n      console.log(\n        `   bande du pack payant : ${euros(priceBand.min)} a ${euros(priceBand.max)}\\n` +\n        `   remise affichee : -${this.discountPercent(packRetailCents, 2990)}%  (sur le vin seul, hors livraison)`\n      );\n    }\n\n    const totalCents = packRetailCents;\n    const baseBottleCount = packWines.length;\n    const displayBottleCount = baseBottleCount;\n    // The header sells the MECHANISM, not the box. \"Taste 4 wines. Buy the box that matches\n    // your taste.\" described the transaction and stopped there, so the box read as a one-off\n    // purchase competing on price with a supermarket shelf.\n    //\n    // These bottles are step one of a loop: the customer rates them, and every later box is\n    // built from those ratings. That is the only thing here a supermarket cannot sell him, so\n    // it belongs in the biggest text on the page. The kicker names the step, the title names\n    // what he stops doing (buying blind), and nothing else: a subtitle spelling out the rating\n    // words was tried here and removed, the header carries more weight without it.\n    //\n    // Count stays interpolated for the dormant optional fourth paid bottle.\n    const packKicker = isEN\n      ? 'Step 1 of your taste profile'\n      : 'Stap 1 van je smaakprofiel';\n    const packTitle = isEN\n      ? `The last ${displayBottleCount} bottles you'll ever buy blind.`\n      : `De laatste ${displayBottleCount} flessen die je blind koopt.`;\n    const totalEuros = (totalCents / 100).toFixed(2).replace('.', ',');\n    // Badge, struck price and per-bottle prices stay computed on WINE ONLY. Delivery is named\n    // once, on its own line, as part of the total saving, and never folded into any of them.\n    //\n    // A struck-through figure that mixes a product price with a service is the weakest kind of\n    // reference price, and it is exactly what the ACM looks at. Worse, it inflated itself with\n    // our own logistics: shipping goes from \u20AC15,00 to \u20AC17,00 at five bottles, so adding a\n    // bottle used to buy \u20AC2,00 of extra \"savings\" that were freight, not wine. Hence the split:\n    // one reference price on the wine, one line that adds the delivery the customer really\n    // does not pay, each with its own amount visible.\n    const savedPercent = this.discountPercent(totalCents, 2990);\n    // 2026-08-03 : the total saving comes back, as an amount. Stripping every figure out of\n    // this block cost a conversion rate that was working: \"-22%\" and \"gratis bezorgd\" are two\n    // soft claims the visitor has to add up himself, and he does not. About \u20AC8,86 off the wine\n    // plus \u20AC15,00 of delivery is one number he cannot reach alone, and it is the largest\n    // honest figure on the page.\n    const shippingCents = getConfiguredShippingCents(displayBottleCount);\n    const wineSavingCents = Math.max(0, totalCents - 2990);\n    // Per-bottle prices are the hero of this block: they are the only figures in the customer's\n    // own currency. He buys at about \u20AC7 in the supermarket, so \u20AC5,98 lands without him having\n    // to trust any total.\n    const normalPerBottleEuros = (Math.round(totalCents / Math.max(1, displayBottleCount)) / 100).toFixed(2).replace('.', ',');\n    const paidPerBottleEuros = (Math.round(2990 / Math.max(1, displayBottleCount)) / 100).toFixed(2).replace('.', ',');\n    const hasCalibrationBonus = packWines.some(wine => wine && wine._isBonus);\n    // The \"your box contains\" list spells out one line per bottle, so it has to know when\n    // the optional fourth paid bottle is in the pack. Read from the role the selection set,\n    // not from a count, so the line can never appear against a three-bottle box.\n    const hasFourthBottle = packWines.some(wine => wine && wine._calibrationRole === 'third_safe_match');\n\n    // Kept so the per-bottle prices, the badge and the saving line can be recomputed when the\n    // pack contents change: showing a 3-bottle price next to a 4-bottle count would misstate\n    // both. The shipping reader is carried along because delivery is tiered by bottle count,\n    // so an added upsell bottle can push the box from \u20AC15,00 to \u20AC17,00 of freight and the\n    // saving line has to follow, or it understates itself the moment the customer adds one.\n    this._bundleMath = {\n      wineCents: totalCents,\n      baseCount: displayBottleCount,\n      getShippingCents: getConfiguredShippingCents\n    };\n\n    const checkoutCardHTML =\n      `<div class=\"wr-bundle__checkout-card\" style=\"background:#FDFBF7;border:1px solid #EAE6DF;padding:20px;border-radius:12px;margin:0;text-align:center;box-shadow:0 4px 12px rgba(0,0,0,0.02);\">` +\n      `<div style=\"margin:0 0 12px;padding:9px 11px;background:#fff;border:1px solid #EAE6DF;border-radius:10px;text-align:left;\">` +\n      // Real, verifiable Trustpilot review. Never invent a customer on a page whose whole\n      // argument is that we do not sell opinions.\n      `<div style=\"display:flex;align-items:center;gap:7px;margin-bottom:3px;\"><span style=\"color:#00b67a;font-size:13px;letter-spacing:1px;\">\u2605\u2605\u2605\u2605\u2605</span><span style=\"font-size:12px;color:#6B7280;font-weight:700;\">Trustpilot</span></div>` +\n      `<div style=\"font-size:12px;color:#374151;line-height:1.4;font-style:italic;\">${isEN ? '\u201CThe quiz worked amazing. Without any expectations, I have become really impressed.\u201D' : '\u201CDe quiz werkte geweldig. Zonder verwachtingen ben ik echt onder de indruk geraakt.\u201D'}</div>` +\n      `<div style=\"font-size:11px;color:#9CA3AF;margin-top:2px;\">${isEN ? 'Maria, Netherlands' : 'Maria, Nederland'}</div>` +\n      `</div>` +\n      `<div class=\"wr-bundle__pricing-storybrand\" style=\"margin-bottom:15px;padding-bottom:14px;border-bottom:1px solid #EAE6DF;text-align:left;\">` +\n      `<div style=\"margin-bottom:12px;\"><div style=\"font-size:11px;font-weight:700;color:#E07A5F;letter-spacing:0.05em;text-transform:uppercase;margin-bottom:2px;\">${isEN ? 'Test Kit' : 'Test Kit'}</div><div style=\"font-size:15px;color:#1F2937;line-height:1.25;font-weight:800;\">${isEN ? 'Your box contains' : 'Je box bevat'}</div></div>` +\n      `<div style=\"display:grid;gap:7px;margin:0 0 12px;\">` +\n      `<div style=\"display:flex;gap:8px;align-items:flex-start;font-size:13px;color:#374151;line-height:1.35;\"><span style=\"color:#E07A5F;font-weight:800;line-height:1.35;\">\u2713</span><span>1 ${isEN ? 'safe match based on your answers' : 'veilige match op basis van je antwoorden'}</span></div>` +\n      `<div style=\"display:flex;gap:8px;align-items:flex-start;font-size:13px;color:#374151;line-height:1.35;\"><span style=\"color:#E07A5F;font-weight:800;line-height:1.35;\">\u2713</span><span>1 ${isEN ? 'close match with a bit more variation' : 'dichte match met iets meer variatie'}</span></div>` +\n      `<div style=\"display:flex;gap:8px;align-items:flex-start;font-size:13px;color:#374151;line-height:1.35;\"><span style=\"color:#E07A5F;font-weight:800;line-height:1.35;\">\u2713</span><span>1 ${isEN ? 'second safe match, also very close to your answers' : 'tweede veilige match, ook heel dicht bij je antwoorden'}</span></div>` +\n      `<div style=\"display:${hasFourthBottle ? 'flex' : 'none'};gap:8px;align-items:flex-start;font-size:13px;color:#374151;line-height:1.35;\"><span style=\"color:#E07A5F;font-weight:800;line-height:1.35;\">\u2713</span><span>1 ${isEN ? 'fourth bottle, picked the same way, never outside your taste zone' : 'vierde fles, op dezelfde manier gekozen, nooit buiten je smaakzone'}</span></div>` +\n      `<div style=\"display:${hasCalibrationBonus ? 'flex' : 'none'};gap:8px;align-items:flex-start;font-size:13px;color:#374151;line-height:1.35;\"><span style=\"color:#E07A5F;font-weight:800;line-height:1.35;\">\u2713</span><span>1 ${isEN ? 'extra calibration bottle to improve future recommendations' : 'extra kalibratiefles om toekomstige aanbevelingen te verbeteren'}</span></div>` +\n      `<div class=\"wr-bundle__math-upsell\" style=\"display:none;gap:8px;align-items:flex-start;font-size:13px;color:#374151;line-height:1.35;\"><span style=\"color:#E07A5F;font-weight:800;line-height:1.35;\">\u2713</span><span><span class=\"wr-bundle__math-upsell-count\">1</span> <span class=\"wr-bundle__math-upsell-noun\">${isEN ? 'extra bottle you picked, at 20% off' : 'extra fles die je koos, met 20% korting'}</span></span></div>` +\n      `</div>` +\n      // One block, two lines, five numbers. The previous version was a two-column grid with\n      // nine figures for three actual facts, and it read as confusing. Everything that made\n      // the customer do arithmetic is gone: the normal total, the duplicated bottle counts,\n      // the separate savings line. What is left is the price per bottle he can compare to his\n      // supermarket habit, and the one sentence that says what he pays for what.\n      `<div style=\"background:linear-gradient(180deg,#FFFFFF 0%,#FDFBF7 100%);border:1px solid #EAE6DF;border-radius:10px;padding:13px 14px;margin-bottom:10px;\">` +\n      `<div style=\"display:flex;align-items:baseline;flex-wrap:wrap;gap:8px;\">` +\n      `<strong class=\"wr-bundle__math-paid-bottle\" style=\"font-size:30px;line-height:1;font-weight:900;color:#1F2937;letter-spacing:-0.02em;\">\u20AC${paidPerBottleEuros}</strong>` +\n      `<span style=\"font-size:12px;color:#4B5563;\">${isEN ? 'per bottle' : 'per fles'}</span>` +\n      `<s class=\"wr-bundle__math-normal-bottle\" style=\"font-size:13px;color:#9CA3AF;font-weight:700;\">\u20AC${normalPerBottleEuros}</s>` +\n      `<span class=\"wr-bundle__math-percent\" style=\"display:${savedPercent > 0 ? 'inline-block' : 'none'};background:#E07A5F;color:#fff;font-size:11px;font-weight:800;line-height:1;padding:4px 7px;border-radius:999px;white-space:nowrap;\">-${savedPercent}%</span>` +\n      `</div>` +\n      `<div style=\"font-size:12px;color:#4B5563;line-height:1.4;margin-top:5px;\">` +\n      `<span class=\"wr-bundle__math-total-count\">${displayBottleCount}</span> ${isEN ? 'bottles for' : 'flessen voor'} <strong class=\"wr-bundle__math-total-price\" style=\"color:#1F2937;font-weight:700;\">\u20AC29,90</strong>, ${isEN ? 'delivered free on your first order' : 'gratis bezorgd bij je eerste bestelling'}` +\n      `</div>` +\n      // The total saving, and the only place delivery is ever given an amount. It is written\n      // as a sum in plain sight, wine + delivery, for two reasons. The customer trusts a\n      // number he can verify far more than one he is handed, and the breakdown is what keeps\n      // the freight out of the \u20AC38,76 struck price above: the two amounts stay separate\n      // things that happen to be added, not one inflated reference price.\n      //\n      // The delivery figure is only safe here because it is the second term of a sum. Printed\n      // alone as \"otherwise \u20AC17,00\" it read as a delivery costing more than half the order,\n      // which is what got it removed. Inside the total it does the opposite job.\n      `<div class=\"wr-bundle__math-saving\" style=\"display:${wineSavingCents + shippingCents > 0 ? 'block' : 'none'};font-size:12.5px;color:#8C5A4A;line-height:1.4;margin-top:6px;font-weight:700;\">` +\n      `${isEN ? 'You save' : 'Je bespaart'} <strong class=\"wr-bundle__math-saving-total\" style=\"font-weight:800;\">\u20AC${this.formatEurosFromCents(wineSavingCents + shippingCents)}</strong> ${isEN ? 'in total' : 'in totaal'}` +\n      `<span style=\"display:block;font-weight:600;color:#6B7280;font-size:11.5px;margin-top:1px;\">` +\n      `<span class=\"wr-bundle__math-saving-wine\">\u20AC${this.formatEurosFromCents(wineSavingCents)}</span> ${isEN ? 'on the wine' : 'op de wijn'} + <span class=\"wr-bundle__math-saving-shipping\">\u20AC${this.formatEurosFromCents(shippingCents)}</span> ${isEN ? 'delivery, on us (first order)' : 'verzendkosten, van ons (eerste bestelling)'}` +\n      `</span>` +\n      `</div>` +\n      // No gift strip here. The free bottle was tried as a highlighted line in this block and\n      // removed: the contents list above already states it, and repeating it next to the price\n      // made the block argue instead of just stating the number.\n      //\n      // What follows is the only line here that sells something other than the price, and it\n      // closes the villain block the visitor read before the quiz (\"critic scores, gold medals,\n      // wine jargon\" against \"your palate does not need their approval\"). Buying is the moment\n      // that argument turns into a side he picks, so it belongs at the price, not earlier.\n      //\n      // Deliberately a hairline, not a filled callout: a boxed strip was tried here for the\n      // gift and read as a second advertisement inside the price. No membership count either,\n      // since there is no real figure in the codebase to cite and inventing one would be the\n      // exact kind of unearned authority this sentence attacks.\n      `<div style=\"margin-top:9px;padding-top:8px;border-top:1px solid #F0EBE3;font-size:11.5px;line-height:1.4;color:#8C5A4A;font-weight:600;\">` +\n      `\uD83C\uDF77 ${isEN ? 'Welcome to the rebellion: a growing community that trusts its own palate over critic scores.' : 'Welkom bij de rebellie: een groeiende community die haar eigen smaak meer vertrouwt dan punten van critici.'}` +\n      `</div>` +\n      `</div>` +\n      `</div>` +\n      `<div class=\"relative z-10 pb-button-shadow\" style=\"width:100%;margin:0 auto 0.6rem;\">` +\n      `<button class=\"wr-bundle__add push-btn\" type=\"button\" style=\"width:100%;display:block !important;\">` +\n      `<span class=\"push-btn__surface push-btn__surface--cta-primary\" style=\"display:block;width:100%;font-weight:bold;\">${isEN ? 'Get my Taste Kit - \u20AC29.90' : 'Ontvang mijn Taste Kit - \u20AC29,90'}</span>` +\n      `</button>` +\n      `</div>` +\n      `<span style=\"font-size:12px;color:#374151;display:block;margin-top:6px;line-height:1.4;text-align:center;font-weight:600;\">${isEN ? '\u2705 Not your taste? Every wine we picked that you don\\'t like is replaced for free in your next order.' : '\u2705 Niet jouw smaak? Elke wijn die wij kozen en die je niet lekker vindt, vervangen we gratis in je volgende bestelling.'}</span>` +\n      `<span style=\"font-size:12px;color:#4B5563;display:block;margin-top:8px;line-height:1.45;text-align:center;\">${isEN ? '\uD83D\uDD12 One-time purchase. No subscription.' : '\uD83D\uDD12 Eenmalige aankoop. Geen abonnement.'}</span>` +\n      // \"Free\" is deliberately NOT repeated here: the math block above already makes that\n      // claim with its real value. This line answers a different question, how fast and from\n      // where, so the two never say the same thing twice.\n      `<span class=\"wr-checkout-delivery\" style=\"font-size:12px;color:#4B5563;display:block;margin-top:6px;line-height:1.45;text-align:center;\">${isEN ? '\uD83D\uDE9A Delivered within 72h from our Enschede warehouse via Bulpe Fulfilment' : '\uD83D\uDE9A Binnen 72u bezorgd vanuit ons warehouse in Enschede via Bulpe Fulfilment'}</span>` +\n      `<span class=\"wr-checkout-security\" aria-label=\"${isEN ? 'Secure checkout with iDEAL, credit card and Apple Pay' : 'Veilig afrekenen met iDEAL, creditcard en Apple Pay'}\" style=\"display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;margin-top:8px;line-height:1.2;text-align:center;\">` +\n      `<span style=\"font-size:12px;color:#4B5563;margin-right:1px;\">\uD83D\uDD12</span>` +\n      `<span class=\"wr-checkout-payment-icons\" aria-hidden=\"true\" style=\"display:inline-flex;align-items:center;justify-content:center;gap:7px;flex-wrap:wrap;\">` +\n      `<span style=\"display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:24px;padding:0 8px;border:1px solid #D9DDE5;border-radius:5px;background:#fff;color:#111827;font-size:12px;font-weight:800;letter-spacing:-0.01em;\">iDEAL</span>` +\n      `<span style=\"display:inline-flex;align-items:center;justify-content:center;min-width:56px;height:24px;padding:0 8px;border:1px solid #D9DDE5;border-radius:5px;background:#fff;color:#1F2937;font-size:10px;font-weight:800;letter-spacing:0.03em;\">VISA&nbsp;/&nbsp;MC</span>` +\n      `<span style=\"display:inline-flex;align-items:center;justify-content:center;min-width:64px;height:24px;padding:0 9px;border:1px solid #111827;border-radius:5px;background:#111827;color:#fff;font-size:11px;font-weight:700;letter-spacing:0;\">\uF8FF Pay</span>` +\n      `</span>` +\n      `</span>` +\n      `</div>`;\n\n    // The free bonus bottle is already added to the cart when the customer clicks the main\n    // CTA (see addBtn.onclick / FAST_BONUS_VARIANT_ID below), so there is no separate\n    // \"enter your email to secure it\" card competing with the buy button.\n    const urgencyCardHTML = '';\n\n    this._fastBonusProductId = '';\n\n    // No email capture under the buy button: it asked for a second decision right where the\n    // customer is making the first one, and the gift is already secured by the CTA itself.\n    const emailCaptureHTML = '';\n\n    const TESTIMONIAL_VIDEO_NL_MOBILE = 'https://cdn.shopify.com/videos/c/o/v/c054d21e56b94c3aa6b3469db8ca4bd2.mp4';\n    const TESTIMONIAL_VIDEO_NL_DESKTOP = 'https://cdn.shopify.com/videos/c/o/v/9c7a2d255c74449a914e56966306f8a6.mp4';\n    const TESTIMONIAL_VIDEO_EN_MOBILE = 'https://cdn.shopify.com/videos/c/o/v/59f1cea513bf4ed7b7dc6bf517a101ee.mp4';\n    const TESTIMONIAL_VIDEO_EN_DESKTOP = 'https://cdn.shopify.com/videos/c/o/v/cf751c31ad014100a0f8d110f2e93926.mp4';\n    const FOUNDER_VIDEO_MOBILE = 'https://cdn.shopify.com/videos/c/o/v/41cf5aaef9584ffd8f5d89b447065f94.mp4';\n    const FOUNDER_VIDEO_DESKTOP = 'https://cdn.shopify.com/videos/c/o/v/e2a20473efe74fa4914660a06101e0dd.mp4';\n    // Testimonial video, shown right under the checkout card on the results page.\n    // Click-to-play (no autoplay): the customer decides to start it. #t=0.5 makes the\n    // still frame a moment at 0.5s so it is never a black first frame. NL/EN by the\n    // language selector, mobile/desktop by viewport.\n    const isMobileTestimonial = !!(window.matchMedia && window.matchMedia('(max-width: 749px)').matches);\n    const testimonialVideoSrc = isEN\n      ? (isMobileTestimonial ? TESTIMONIAL_VIDEO_EN_MOBILE : TESTIMONIAL_VIDEO_EN_DESKTOP)\n      : (isMobileTestimonial ? TESTIMONIAL_VIDEO_NL_MOBILE : TESTIMONIAL_VIDEO_NL_DESKTOP);\n    const testimonialVideoHTML =\n      `<div class=\"wr-bundle__testimonial-video\" style=\"margin:14px 0 0;border-radius:12px;overflow:hidden;border:1px solid #EAE6DF;background:#FDFBF7;box-shadow:0 4px 12px rgba(0,0,0,0.02);\">` +\n      `<video class=\"wr-bundle__testimonial-video-el\" src=\"${testimonialVideoSrc}#t=0.5\" controls playsinline preload=\"metadata\" style=\"display:block;width:100%;height:auto;background:#FDFBF7;\"></video>` +\n      `</div>`;\n\n    const bundlePageHTML = `\n      <div class=\"wr-page wr-page--bundle wr-page--enter\" data-page=\"bundle\" style=\"display: block;\">\n        <div class=\"wr-bundle__header\" style=\"text-align:center;margin:15px 0 20px;display:flex;flex-direction:column;align-items:center;gap:8px;\">\n          <span class=\"wr-bundle__kicker\" style=\"font-size:11px;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;color:#E07A5F;line-height:1.2;\">${packKicker}</span>\n          <h3 class=\"wr-bundle__title\" style=\"margin:0;padding:0;line-height:1.1;\">${packTitle}</h3>\n        </div>\n        <div class=\"wr-bundle-layout\">\n          <div class=\"wr-bundle__author\" style=\"display:flex;align-items:center;gap:14px;background:#fff;border:1px solid #EAE6DF;padding:14px;border-radius:12px;margin:0 0 18px;\">\n            <div style=\"flex-shrink:0;\">\n              <img src=\"https://cdn.shopify.com/s/files/1/0950/0757/2355/files/Adrien-Circle.png?v=1770890484\" alt=\"Adrien\" class=\"wr-founder-photo\" style=\"width:100px;height:100px;border-radius:50%;object-fit:cover;border:2px solid #E07A5F;\">\n            </div>\n            <div style=\"text-align:left;flex:1;min-width:0;\">\n              <span style=\"font-size:11px;font-weight:600;color:#888;display:block;letter-spacing:0.3px;margin-bottom:4px;\">\n                ${isEN ? 'Adrien, founder &amp; former winemaker' : 'Adrien, oprichter &amp; oud-wijnmaker'}\n              </span>\n              <p class=\"wr-founder-quote\" style=\"margin:0 0 7px;font-size:13px;color:#333;line-height:1.45;font-weight:500;\">\n                ${isEN\n                  ? `&ldquo;I chose these ${baseBottleCount} bottles for your palate myself. Not your style? <strong style=\"color:#1f2937;\">I replace it for free in your next order</strong>, that is my promise.&rdquo;`\n                  : `&ldquo;Ik koos deze ${baseBottleCount} flessen zelf voor jouw smaak. Niet jouw stijl? <strong style=\"color:#1f2937;\">Ik vervang hem gratis in je volgende bestelling</strong>, dat beloof ik.&rdquo;`}\n              </p>\n              <a\n                href=\"${FOUNDER_VIDEO_DESKTOP}\"\n                class=\"wr-founder-video-link\"\n                data-video-mobile=\"${FOUNDER_VIDEO_MOBILE}\"\n                data-video-desktop=\"${FOUNDER_VIDEO_DESKTOP}\"\n                style=\"font-size:11px;color:#E07A5F;text-decoration:underline;font-weight:600;\"\n              >\n                ${isEN ? 'Meet Adrien (30 sec)' : 'Maak kennis met Adrien (30 sec)'}\n              </a>\n            </div>\n          </div>\n          <div class=\"wr-bundle-col-left\" style=\"min-width:0;\">\n            <div class=\"wr-bundle-items\">\n              <div class=\"wr-bundle__tweak-panel-mount wr-bundle__tweak-panel-mount--desktop\"></div>\n              ${bundleSummaryHTML}\n              <div class=\"wr-bundle__tweak-panel-mount wr-bundle__tweak-panel-mount--mobile\"></div>\n            </div>\n          </div>\n          <div class=\"wr-bundle-col-right\">\n            ${checkoutCardHTML}\n            ${testimonialVideoHTML}\n            ${emailCaptureHTML}\n          </div>\n          ${urgencyCardHTML}\n        <div class=\"wr-sticky-buy\" role=\"region\" aria-label=\"${isEN ? 'Quick checkout' : 'Snel afrekenen'}\">\n          <div class=\"wr-sticky-buy__info\">\n            <span class=\"wr-sticky-buy__label\">${isEN ? 'Your Test Kit' : 'Jouw Test Kit'}</span>\n            <span class=\"wr-sticky-buy__price\">\u20AC29,90</span>\n          </div>\n          <button class=\"wr-sticky-buy__btn\" type=\"button\">${isEN ? 'Get it now \u2192' : 'Bestel nu \u2192'}</button>\n        </div>\n      </div>\n    `;\n    cardsEl.insertAdjacentHTML('beforeend', bundlePageHTML);\n\n    // Sticky mobile buy bar \u2192 triggers the real add-to-cart button.\n    const stickyBtn = cardsEl.querySelector('.wr-sticky-buy__btn');\n    const mainAddBtn = cardsEl.querySelector('.wr-bundle__add');\n    if (stickyBtn && mainAddBtn) {\n      stickyBtn.addEventListener('click', () => mainAddBtn.click());\n    }\n\n    this.setupPackReplacement(cardsEl, isEN);\n    this.setupUpsellBlock(cardsEl, isEN);\n\n    // Wine-by-wine reveal, played over the bundle (initial reveal only, not silent axis updates).\n    if (!preserveScroll) {\n      try {\n        const revealHTML = this.buildWineRevealHTML(this._packWines, isEN, catalog, '');\n        if (revealHTML) {\n          document.body.insertAdjacentHTML('beforeend', revealHTML);\n          const overlay = document.getElementById('wr-reveal');\n          if (overlay) {\n            this.playWineReveal(overlay, () => {\n              // Scroll to the TOP of the results, offset by the sticky header height\n              // so nothing (pack title) hides behind it.\n              const target = cardsEl.querySelector('.wr-page--bundle');\n              if (target) {\n                const headerH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-group-height')) || 100;\n                const top = target.getBoundingClientRect().top + window.pageYOffset - headerH - 12;\n                window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });\n              }\n            });\n          }\n        }\n      } catch (e) { console.warn('Wine reveal skipped:', e); }\n    }\n\n    const paymentIconMount = cardsEl.querySelector('.wr-checkout-payment-icons');\n    if (paymentIconMount) {\n      const footerIcons = Array.from(document.querySelectorAll('.payment-icons svg'));\n      const wantedPayments = [\n        { key: 'ideal', match: /ideal/i },\n        { key: 'visa', match: /^visa$/i },\n        { key: 'mastercard', match: /mastercard/i },\n        { key: 'apple-pay', match: /apple pay/i }\n      ];\n      const clonedIcons = wantedPayments\n        .map(({ match }) => footerIcons.find(svg => {\n          const title = svg.querySelector('title');\n          return title && match.test(title.textContent.trim());\n        }))\n        .filter(Boolean)\n        .map(svg => {\n          const clone = svg.cloneNode(true);\n          clone.setAttribute('aria-hidden', 'true');\n          clone.setAttribute('focusable', 'false');\n          clone.style.width = '38px';\n          clone.style.height = '24px';\n          clone.style.display = 'block';\n          return clone;\n        });\n\n      if (clonedIcons.length >= 3) {\n        paymentIconMount.replaceChildren(...clonedIcons);\n      }\n    }\n\n    const closeFounderVideoModal = () => {\n      const modal = document.querySelector('.wr-founder-video-modal');\n      if (!modal) return;\n      const video = modal.querySelector('video');\n      if (video) {\n        video.pause();\n        video.removeAttribute('src');\n        video.load();\n      }\n      modal.remove();\n      document.body.style.overflow = '';\n      document.removeEventListener('keydown', onFounderVideoEscClose);\n    };\n\n    const onFounderVideoEscClose = (event) => {\n      if (event.key === 'Escape') closeFounderVideoModal();\n    };\n\n    const openFounderVideoModal = (videoUrl) => {\n      closeFounderVideoModal();\n\n      const modal = document.createElement('div');\n      modal.className = 'wr-founder-video-modal';\n      modal.innerHTML = `\n        <div style=\"position:fixed;inset:0;z-index:9999;background:rgba(15,23,42,0.72);display:flex;align-items:center;justify-content:center;padding:16px;\">\n          <div style=\"position:relative;width:min(92vw,860px);max-height:88vh;background:#000;border-radius:12px;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,0.35);\">\n            <button type=\"button\" aria-label=\"Close video\" data-founder-video-close style=\"position:absolute;top:8px;right:8px;z-index:2;border:0;border-radius:999px;width:34px;height:34px;background:rgba(17,24,39,0.75);color:#fff;font-size:20px;line-height:1;cursor:pointer;\">\u00D7</button>\n            <video controls playsinline autoplay style=\"display:block;width:100%;height:auto;max-height:88vh;background:#000;\">\n              <source src=\"${videoUrl}\" type=\"video/mp4\">\n            </video>\n          </div>\n        </div>\n      `;\n\n      modal.addEventListener('click', (event) => {\n        if (event.target === modal.firstElementChild) closeFounderVideoModal();\n      });\n\n      const closeBtn = modal.querySelector('[data-founder-video-close]');\n      if (closeBtn) closeBtn.addEventListener('click', closeFounderVideoModal);\n\n      document.body.appendChild(modal);\n      document.body.style.overflow = 'hidden';\n      document.addEventListener('keydown', onFounderVideoEscClose);\n    };\n\n    const founderVideoLink = cardsEl.querySelector('.wr-founder-video-link');\n    if (founderVideoLink) {\n      founderVideoLink.addEventListener('click', (event) => {\n        event.preventDefault();\n        const isMobileVideo = window.matchMedia && window.matchMedia('(max-width: 749px)').matches;\n        const mobileVideoUrl = founderVideoLink.getAttribute('data-video-mobile') || FOUNDER_VIDEO_MOBILE;\n        const desktopVideoUrl = founderVideoLink.getAttribute('data-video-desktop') || FOUNDER_VIDEO_DESKTOP;\n        const videoUrl = isMobileVideo ? mobileVideoUrl : desktopVideoUrl;\n        openFounderVideoModal(videoUrl);\n      });\n    }\n\n    // Keep exact checkout plan copy even if a third-party script mutates text nodes.\n    const enforceCheckoutPlanCopy = () => {\n      const planEl = cardsEl.querySelector('.wr-bundle__plan--checkout');\n      if (!planEl) return;\n      const expected = isEN\n        ? [\n          '1. Taste your personalized box',\n          '2. Rate them in 1-click on the product page',\n          '3. Unlock perfect wines tailored for life'\n        ]\n        : [\n          '1. Proef je gepersonaliseerde box',\n          '2. Beoordeel ze met 1 klik op de productpagina',\n          '3. Ontgrendel perfecte wijnen op maat voor het leven'\n        ];\n      const textEls = planEl.querySelectorAll('.wr-bundle__plan-text');\n      if (!textEls || textEls.length < expected.length) return;\n      textEls.forEach((el, index) => {\n        if (index >= expected.length) return;\n        if ((el.textContent || '').trim() !== expected[index]) {\n          el.textContent = expected[index];\n        }\n      });\n    };\n    enforceCheckoutPlanCopy();\n    setTimeout(enforceCheckoutPlanCopy, 0);\n    setTimeout(enforceCheckoutPlanCopy, 400);\n\n    // Render tweak panel (open by default) into the mount point\n    const tweakMounts = cardsEl.querySelectorAll('.wr-bundle__tweak-panel-mount');\n    if (tweakMounts.length && typeof this.renderTweakPanel === 'function') {\n      tweakMounts.forEach(tweakMount => this.renderTweakPanel(tweakMount, isEN));\n    }\n\n    // ---- Event listeners ----\n\n    // Next wine / see selection buttons\n    cardsEl.querySelectorAll('.wr-page__next').forEach(btn => {\n      btn.addEventListener('click', () => goToPage(btn.dataset.next));\n    });\n\n    // Previous buttons\n    cardsEl.querySelectorAll('.wr-page__prev').forEach(btn => {\n      btn.addEventListener('click', () => goToPage(btn.dataset.prev));\n    });\n\n    // Bundle item \u2192 open product modal\n    const closeProductModal = () => {\n      const modal = document.querySelector('.wr-product-modal');\n      if (modal) modal.remove();\n      document.body.style.overflow = '';\n    };\n    const openProductModal = (item) => {\n      closeProductModal();\n      const wineId = item.dataset.wineId;\n      const p = catalog[String(wineId)] || {};\n      const productUrl = item.dataset.productUrl || p.url || (p.handle ? `/products/${p.handle}` : '');\n      const imgEl = item.querySelector('.wr-bundle-item__img');\n      const titleEl = item.querySelector('.wr-bundle-item__title');\n      const imgSrc = (p.image && p.image.url) ? p.image.url : (imgEl ? imgEl.getAttribute('src') : '');\n      const title = titleEl ? titleEl.textContent.trim() : (p.title || '');\n      const tag = item.dataset.typeLabel || '';\n      const priceCents = parseInt(item.dataset.displayPrice || '0', 10);\n      // Discounted bottles carry their pre-discount price, so the modal can show what the\n      // wine normally costs next to what this customer pays. Kit cards have no compare\n      // price and keep a single figure.\n      const comparePriceCents = parseInt(item.dataset.comparePrice || '0', 10);\n      const molTitle = item.dataset.molecularTitle || '';\n      const molText = item.dataset.molecularText || '';\n      const tasteAxesHTML = this.buildProductModalTasteAxes(item, isEN);\n      const closeText = isEN ? 'Close' : 'Sluiten';\n      const kitInclusionLabel = isEN ? 'Included in your Taste Kit' : 'Inbegrepen in je Taste Kit';\n\n      // Taste fingerprint: this wine's 3 lab axes vs your quiz taste, plus a match %.\n      const targetToPct = (v) => Math.round(((Math.max(-1.2, Math.min(1.2, Number(v) || 0)) + 1.2) / 2.4) * 100);\n      const ux = targetToPct(this.targetX), uy = targetToPct(this.targetY), uz = targetToPct(this.targetZ);\n      const wx = parseInt(item.dataset.axisX, 10), wy = parseInt(item.dataset.axisY, 10), wz = parseInt(item.dataset.axisZ, 10);\n      const hasAxes = [wx, wy, wz].every(Number.isFinite);\n      // No customer-to-wine match % here on purpose: it is derived from the 6 quiz answers,\n      // not from the lab, and it collided with the lab-measured \"80-90% alike\" promise\n      // (a 72% here read as a broken promise). The fingerprint chart shows closeness instead.\n      // Lab block, fingerprint, winemaker quote and technical sheet all come from the\n      // shared renderer in `assets/wine-fiche.js`, so this modal and the printed Taste Kit\n      // recap sheet (`/pages/kit-recap`) can never drift apart. theme.liquid loads that\n      // file immediately before this one; the guard below is only there so a missing asset\n      // degrades to no modal instead of a thrown error mid-checkout-funnel.\n      if (!window.VinPopWineFiche) {\n        console.warn('TasteProfileQuiz: wine-fiche.js not loaded, product modal skipped');\n        return;\n      }\n\n      const ficheHTML = window.VinPopWineFiche.build({\n        isEN,\n        wineId,\n        title,\n        imageUrl: imgSrc,\n        role: tag,\n        product: p,\n        price: priceCents\n          ? {\n              cents: priceCents,\n              compareCents: comparePriceCents,\n              discountRate: this.upsellDiscountRate,\n              kitLabel: kitInclusionLabel\n            }\n          : null,\n        axes: hasAxes ? { wx, wy, wz, ux, uy, uz } : null,\n        // `data-molecular-text` is the exact string the card the customer just clicked\n        // already shows in `.wr-bundle-item__why`; it is only a fallback for bottles whose\n        // winemaker has no note written yet.\n        why: molText || molTitle,\n        closeButton: true,\n        closeLabel: closeText\n      });\n\n      const modalHTML = `\n        <div class=\"wr-product-modal is-open\">\n          <div class=\"wr-product-modal__backdrop\" data-close-modal=\"1\"></div>\n          <div class=\"wr-product-modal__dialog\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Taste analysis\">${ficheHTML}</div>\n        </div>\n      `;\n      document.body.insertAdjacentHTML('beforeend', modalHTML);\n      document.body.style.overflow = 'hidden';\n      const modal = document.querySelector('.wr-product-modal');\n\n      // Enforce clean label/value separation in the exact facts container path.\n      const normalizeFactsSpacing = () => {\n        const factsRoot = document.querySelector('body > div.wr-product-modal.is-open > div.wr-product-modal__dialog > div > div.wr-product-modal__facts')\n          || modal.querySelector('.wr-product-modal__facts');\n        if (!factsRoot) return;\n        factsRoot.querySelectorAll('div').forEach((row) => {\n          const span = row.querySelector('span');\n          const strong = row.querySelector('strong');\n          if (!span || !strong) return;\n          const between = span.nextSibling;\n          if (!between || between.nodeType !== Node.TEXT_NODE || between.textContent !== ' ') {\n            row.insertBefore(document.createTextNode(' '), strong);\n          }\n        });\n      };\n      normalizeFactsSpacing();\n\n      modal.querySelectorAll('[data-close-modal]').forEach(el => {\n        el.addEventListener('click', closeProductModal);\n      });\n      const onEsc = (e) => {\n        if (e.key === 'Escape') { closeProductModal(); document.removeEventListener('keydown', onEsc); }\n      };\n      document.addEventListener('keydown', onEsc);\n\n      // Enrich missing facts from product page content (region/year/abv/closure often live there).\n      if (productUrl) {\n        fetch(productUrl, { credentials: 'same-origin' })\n          .then(r => r.ok ? r.text() : '')\n          .then(html => {\n            if (!html) return;\n            const doc = new DOMParser().parseFromString(html, 'text/html');\n            const text = (doc.body ? doc.body.innerText : '')\n              .replace(/\\u00a0/g, ' ')\n              .replace(/\\r/g, '');\n\n            const lines = text\n              .split('\\n')\n              .map(l => l.replace(/\\s+/g, ' ').trim())\n              .filter(Boolean);\n\n            const byLine = (labels) => {\n              const labelSet = labels.map(l => l.toLowerCase());\n              for (let i = 0; i < lines.length; i++) {\n                const line = lines[i];\n                const low = line.toLowerCase();\n                for (const label of labelSet) {\n                  if (low === label && lines[i + 1]) return lines[i + 1];\n                  if (low.startsWith(label + ':')) {\n                    const val = line.slice(label.length + 1).trim();\n                    if (val) return val;\n                    if (lines[i + 1]) return lines[i + 1];\n                  }\n                  if (low.startsWith(label + ' :')) {\n                    const val = line.slice(label.length + 2).trim();\n                    if (val) return val;\n                    if (lines[i + 1]) return lines[i + 1];\n                  }\n                }\n              }\n              return '';\n            };\n\n            const byRegex = (pattern) => {\n              const m = text.match(pattern);\n              return m ? m[1].trim() : '';\n            };\n\n            const fetched = {\n              country: byLine(['country of origin', 'land van herkomst']) || byRegex(/(?:Country of origin|Land van herkomst)\\s*:?\\s*([^\\n]+)/i),\n              region: byLine(['wine region', 'regio']) || byRegex(/(?:Wine region|Regio)\\s*:?\\s*([^\\n]+)/i),\n              grape: byLine(['grape variety', 'druivensoort']) || byRegex(/(?:Grape variety|Druivensoort)\\s*:?\\s*([^\\n]+)/i),\n              winemaker: byLine(['wine maker', 'wijnmaker']) || byRegex(/(?:Wine maker|Wijnmaker)\\s*:?\\s*([^\\n]+)/i),\n              year: byLine(['year']) || byRegex(/Year\\s*:?\\s*([0-9]{4})/i),\n              alcohol: byLine(['abv', 'alcoholpercentage']) || byRegex(/(?:ABV|Alcoholpercentage)\\s*:?\\s*([0-9]+(?:\\.[0-9]+)?%?)/i),\n              capacity: byLine(['capacity']) || byRegex(/Capacity\\s*:?\\s*([^\\n]+)/i),\n              closing: byLine(['closing']) || byRegex(/Closing\\s*:?\\s*([^\\n]+)/i),\n            };\n\n            const rows = Array.from(document.querySelectorAll('.wr-product-modal__facts > div'));\n            const map = {\n              'land van herkomst': fetched.country,\n              'country of origin': fetched.country,\n              'wine region': fetched.region,\n              'wijnregio': fetched.region,\n              'druivensoort': fetched.grape,\n              'grape variety': fetched.grape,\n              'wijnmaker': fetched.winemaker,\n              'winemaker': fetched.winemaker,\n              'year': fetched.year,\n              'jaar': fetched.year,\n              'alcoholpercentage': fetched.alcohol,\n              'alcohol percentage': fetched.alcohol,\n              'capacity': fetched.capacity,\n              'inhoud': fetched.capacity,\n              'closing': fetched.closing,\n              'closure': fetched.closing,\n              'sluiting': fetched.closing,\n            };\n\n            rows.forEach(row => {\n              const label = (row.querySelector('span')?.textContent || '')\n                .replace(/\\s*:\\s*$/, '')\n                .trim()\n                .toLowerCase();\n              const valueEl = row.querySelector('strong');\n              if (!valueEl) return;\n              const current = (valueEl.textContent || '').trim();\n              const nextVal = map[label] ? map[label].replace(/\\s+/g, ' ').trim() : '';\n              if (nextVal && nextVal !== current) {\n                valueEl.textContent = nextVal;\n              }\n            });\n\n            normalizeFactsSpacing();\n          })\n          .catch(() => {});\n      }\n    };\n\n    if (!cardsEl.dataset.bundleModalDelegated) {\n      cardsEl.dataset.bundleModalDelegated = '1';\n      cardsEl.addEventListener('click', (e) => {\n        if (e.target.closest('a, button')) return;\n        const item = e.target.closest('.wr-bundle-item');\n        if (item && cardsEl.contains(item)) openProductModal(item);\n      });\n      cardsEl.addEventListener('keydown', (e) => {\n        if (e.key !== 'Enter' && e.key !== ' ') return;\n        const item = e.target.closest('.wr-bundle-item');\n        if (!item || !cardsEl.contains(item)) return;\n        e.preventDefault();\n        openProductModal(item);\n      });\n    }\n\n    // Bundle: add the Starter Pack with line item properties, then open the cart drawer.\n    //\n    // We deliberately do NOT jump to checkout. This has now been tried twice and reverted\n    // twice. The 2026-07-30 version sent the customer straight to the payment page to cut\n    // a step; on 2026-08-01 the owner saw three clicks on this CTA and zero orders, and\n    // read it as customers being pushed onto a payment form before they were ready. This\n    // is cold paid traffic: the drawer lets them keep the page, and the reassurance on it,\n    // while they decide. Three clicks is far too small a sample to prove anything either\n    // way, so if the direct-to-checkout idea comes back, settle it with an A/B test rather\n    // than another blind flip.\n    const STARTER_PACK_VARIANT = 57742550860163;\n    // Locale-aware root ('/', '/en/', \u2026) so a customer reading the EN page is not\n    // dropped into a Dutch checkout. Always ends with a slash.\n    const shopRoot = (window.Shopify && window.Shopify.routes && window.Shopify.routes.root) || '/';\n    const addBtn = cardsEl.querySelector('.wr-bundle__add');\n    if (addBtn) {\n      addBtn.onclick = async () => {\n        addBtn.disabled = true;\n        addBtn.querySelector('.push-btn__surface').textContent = isEN ? '\u23F3 Adding to your cart\u2026' : '\u23F3 Toevoegen aan je winkelwagen\u2026';\n        try {\n          // Build line item properties with wine names\n          const properties = {};\n          this._packWines.forEach((wine, i) => {\n            const p = catalog[String(wine.wine_id)];\n            if (!p) return;\n            if (wine._isBonus) {\n              return;\n            } else if (wine._isSimilar) {\n              properties[isEN ? 'Similar Wine' : 'Vergelijkbare Wijn'] = p.title;\n            } else {\n              properties[`${isEN ? 'Quiz Match' : 'Quiz Match'} #${i + 1}`] = p.title;\n            }\n          });\n\n          // The gifted bottle is a PROPERTY, exactly like the paid ones, not a cart line of its\n          // own. The customer buys a single \u20AC29,90 product and the properties tell the warehouse\n          // what to pack, so nothing is ever charged for a wine and the page can never disagree\n          // with the receipt. It replaces the old dedicated 0\u20AC variant.\n          //\n          // WAREHOUSE: this line used to arrive as a separate order line. Bulpe Fulfilment must\n          // read it from the properties now, or they will pack one bottle short.\n          // KIT SNAPSHOT \u2014 see `KIT_RECAP_REFERRAL.md`.\n          // The order itself only ever stores wine TITLES, and the buyer is usually logged\n          // out here (paid Meta traffic), so `/api/quizz/save` has nothing either. Without\n          // this call the printed recap sheet and the guest rating page cannot be rebuilt\n          // from the order at all. It also freezes the fingerprint axes, which are min-max\n          // normalised against THIS quiz run's candidate set and are not reproducible later.\n          //\n          // Deliberately non-blocking: a snapshot failure must never cost a sale, so the\n          // token is simply absent and the order ships without a recap sheet. A copy is\n          // kept in localStorage so a failed snapshot can still be replayed afterwards.\n          const kitToken = await this.persistKitSnapshot(properties).catch(() => null);\n          if (kitToken) {\n            // Visible on purpose: fulfilment is manual and the merchant reads this order\n            // line in admin anyway, so the code to print sits next to the wine names.\n            // Never prefix with `_` \u2014 that hides it from admin (see QUIZ_BUNDLE_PAGE.md).\n            properties[isEN ? 'Kit ref' : 'Kit ref'] = kitToken;\n          }\n\n          // Which question set built this kit, so ratings can be compared per path in admin.\n          // Same English value in every locale to keep the orders filterable.\n          if (this.pathChooser && this.quizPath) {\n            properties['Quiz path'] = this.quizPath === 'expert' ? 'Wine lover' : 'Everyday';\n          }\n\n          Object.assign(properties, this.buildKitTraceProperties());\n\n          const cartItems = [{\n            id: STARTER_PACK_VARIANT,\n            quantity: 1,\n            properties: properties\n          }];\n\n          // Add calibration bonus bottle as a dedicated 0\u20AC variant in cart.\n          const calibrationBonusWine = (this._packWines || []).find(w => w && w._isBonus);\n          if (calibrationBonusWine && calibrationBonusWine.wine_id) {\n            const BONUS_VARIANT_ID = this.getFreeBonusVariantIdByProductId(calibrationBonusWine.wine_id);\n            if (BONUS_VARIANT_ID) {\n              cartItems.push({\n                id: BONUS_VARIANT_ID,\n                quantity: 1,\n                properties: {\n                  [isEN ? 'Calibration Bonus' : 'Calibration Bonus']: isEN ? 'Yes' : 'Ja'\n                }\n              });\n            }\n          }\n\n          // Extra bottles: added at full price. The 20% comes off in the cart through the\n          // \"Buy 1 Test Kit, get N at 20% off\" automatic discount, which is the only way\n          // the reduction can never apply to a bottle bought without the kit.\n          this.getAddedUpsellEntries().forEach((entry, index) => {\n            cartItems.push({\n              id: entry.variantId,\n              quantity: 1,\n              properties: {\n                [`${isEN ? 'Extra bottle (-20%)' : 'Extra fles (-20%)'} #${index + 1}`]: entry.product.title\n              }\n            });\n          });\n\n          // Ask for the same sections the theme's own cart requests: its cart\n          // count, drawer contents and live region all refresh from them.\n          const resp = await fetch(`${shopRoot}cart/add.js`, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({\n              items: cartItems,\n              sections: 'cart-items,cart-footer,cart-item-count,cart-live-region',\n              sections_url: window.location.pathname\n            })\n          });\n          if (!resp.ok) throw new Error('Cart API error');\n          const cartData = await resp.json();\n\n          // Hand over to the theme's cart, then open the drawer ourselves: the\n          // shop's \"open drawer on add to cart\" setting is off, so without this\n          // the click would look like nothing happened.\n          try {\n            document.body.dispatchEvent(new CustomEvent('shapes:cart:afteradditem', {\n              bubbles: true,\n              detail: { response: cartData }\n            }));\n            window.Alpine?.store('modals')?.open('cart');\n          } catch (drawerError) {\n            console.warn('Cart drawer refresh failed, sending to cart page:', drawerError);\n            window.location.href = `${shopRoot}cart`;\n            return;\n          }\n\n          addBtn.querySelector('.push-btn__surface').textContent = isEN ? '\u2713 Added to your cart' : '\u2713 Toegevoegd aan je winkelwagen';\n          addBtn.disabled = false;\n        } catch (e) {\n          console.error('Cart add failed:', e);\n          addBtn.querySelector('.push-btn__surface').textContent = isEN ? '\u274C Error. Try again' : '\u274C Fout. Probeer opnieuw';\n          addBtn.disabled = false;\n        }\n      };\n    }\n\n    // No email form is rendered on this page any more, so there is no submit handler to\n    // bind. `vinpop_saved_gift_tag` is only read now, and only ever written by a Shopify\n    // customer tag on the account itself.\n\n    // Skip / browse all wines\n    const skipBtn = cardsEl.querySelector('.wr-bundle__skip');\n    if (skipBtn) {\n      skipBtn.onclick = () => {\n        this.closePopup();\n        window.location.href = window.location.pathname + '?taste_profile=true';\n      };\n    }\n\n    // Account creation (save selection)\n    const emailBtn = cardsEl.querySelector('.wr-bundle__email-btn');\n    if (emailBtn) {\n      emailBtn.onclick = () => {\n        const returnUrl = isEN ? '/en/account/register' : '/nl/account/register';\n        window.location.href = returnUrl;\n      };\n    }\n\n    // Retake quiz\n    const retakeBtn = cardsEl.querySelector('.wr-bundle__retake-link');\n    if (retakeBtn) {\n      retakeBtn.onclick = () => {\n        this.setForceRetake(true);\n        localStorage.removeItem('vinpop-quiz-completed');\n        sessionStorage.removeItem('vinpop-quiz-completed');\n        sessionStorage.removeItem('vinpop_last_results');\n        sessionStorage.removeItem('vinpop_taste_profile');\n        sessionStorage.removeItem('tasteProfileResults');\n        sessionStorage.removeItem('vinpop_quiz_state');\n        this.quizSavePromise = null;\n        this.lastQuizSaveSignature = '';\n        this._reloadToQuizTop();\n      };\n    }\n\n    // Calibration Kit: jump directly to bundle and hide step navigation.\n    const skipWineSteps = /\\/collections\\/(?:test-kit|tasting-kit)(?:\\/|$)/.test(window.location.pathname);\n    if (skipWineSteps) {\n      goToPage('bundle');\n      const dotsEl = cardsEl.querySelector('.wr-dots');\n      if (dotsEl) dotsEl.style.display = 'none';\n\n      const bundlePrevBtn = cardsEl.querySelector('.wr-page--bundle .wr-page__prev');\n      if (bundlePrevBtn) bundlePrevBtn.remove();\n\n      const bundleNav = cardsEl.querySelector('.wr-page--bundle .wr-page__actions-nav');\n      if (bundleNav) bundleNav.style.justifyContent = 'center';\n    } else {\n      // Animate first page in for non-calibration flows\n      goToPage('0');\n      const firstPage = cardsEl.querySelector('[data-page=\"0\"]');\n      if (firstPage) firstPage.classList.add('wr-page--enter');\n    }\n    if (preserveScroll) {\n      requestAnimationFrame(() => {\n        if (preservedPopupContent && preservedPopupY !== null) {\n          preservedPopupContent.scrollTop = preservedPopupY;\n        }\n        if (preservedWindowY !== null) {\n          window.scrollTo({ top: preservedWindowY, behavior: 'auto' });\n        }\n      });\n    }\n  }\n\n  async fetchShopifyProducts(wines, container) {\n    try {\n      // Build collection URL with product IDs as handles\n      const productHandles = wines.map(w => w.wine_id).join(',');\n      \n      // Get current collection handle from URL\n      const collectionMatch = window.location.pathname.match(/\\/collections\\/([^\\/]+)/);\n      const collectionHandle = collectionMatch ? collectionMatch[1] : 'quiz';\n      \n      // Use Shopify Section Rendering API to get pre-rendered product HTML\n      const sectionUrl = `/collections/${collectionHandle}?section_id=main-collection-product-grid`;\n      \n      const response = await fetch(sectionUrl);\n      \n      if (!response.ok) {\n        throw new Error('Failed to fetch section from Shopify');\n      }\n      \n      const html = await response.text();\n      \n      // Parse the HTML to extract product tiles\n      const parser = new DOMParser();\n      const doc = parser.parseFromString(html, 'text/html');\n      const productItems = doc.querySelectorAll('li[data-product-id]');\n      \n      // Clear loading\n      container.innerHTML = '';\n      \n      if (productItems.length === 0) {\n        container.innerHTML = '<p class=\"no-results\">No products found. Please try again.</p>';\n        return;\n      }\n      \n      // Create a map of wine scores by product ID\n      const scoreMap = {};\n      wines.forEach(wine => {\n        scoreMap[wine.wine_id] = wine.score;\n      });\n      \n      // Collect products with scores and sort\n      const productsArray = [];\n      productItems.forEach(item => {\n        const productId = item.getAttribute('data-product-id');\n        const score = scoreMap[productId];\n        \n        // Only include products with 70% or higher compatibility\n        if (score !== undefined && score >= 70) {\n          productsArray.push({\n            element: item,\n            id: productId,\n            score: score\n          });\n        }\n      });\n      \n      // Sort by score (highest first)\n      productsArray.sort((a, b) => b.score - a.score);\n      \n      console.log(`\uD83C\uDF77 Found ${productsArray.length} products with 70%+ compatibility`);\n      \n      // Build HTML string instead of cloning nodes\n      let htmlString = '';\n      productsArray.forEach((productData, index) => {\n        // Get the outer HTML and add taste score badge\n        let productHtml = productData.element.outerHTML;\n        \n        // Insert taste score badge into the product HTML\n        // Find the product-tile__featured-media or product-media-object div\n        const tempDiv = document.createElement('div');\n        tempDiv.innerHTML = productHtml;\n        const mediaContainer = tempDiv.querySelector('.product-tile__featured-media .relative, .product-media-object');\n        \n        if (mediaContainer) {\n          const badge = document.createElement('div');\n          badge.className = 'taste-score-badge';\n          badge.innerHTML = `${productData.score}%`;\n          badge.style.cssText = 'position: absolute; top: 10px; right: 10px; background: linear-gradient(135deg, #8B4513, #D2691E); color: white; padding: 8px 12px; border-radius: 20px; font-weight: 700; font-size: 14px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); z-index: 10;';\n          mediaContainer.style.position = 'relative';\n          mediaContainer.appendChild(badge);\n          \n          productHtml = tempDiv.innerHTML;\n        }\n        \n        htmlString += productHtml;\n      });\n      \n      // Insert all HTML at once\n      container.innerHTML = htmlString;\n      \n      // Reinitialize Alpine.js components (quick-buy buttons, quantity selectors, etc.)\n      if (window.Alpine) {\n        console.log('\uD83D\uDD04 Reinitializing Alpine.js components...');\n        // Wait a bit for DOM to settle, then init all Alpine components in the container\n        setTimeout(() => {\n          // Find all elements with x-data in the newly added products\n          const alpineElements = container.querySelectorAll('[x-data]');\n          alpineElements.forEach(el => {\n            // Check if Alpine is not already initialized on this element\n            if (!el._x_dataStack) {\n              try {\n                window.Alpine.initTree(el);\n              } catch (e) {\n                console.warn('Could not init Alpine on element:', el, e);\n              }\n            }\n          });\n          \n          // Also reinit any data-island elements\n          const islands = container.querySelectorAll('data-island');\n          islands.forEach(island => {\n            if (!island._x_dataStack && island.hasAttribute('x-data')) {\n              try {\n                window.Alpine.initTree(island);\n              } catch (e) {\n                console.warn('Could not init Alpine on island:', island, e);\n              }\n            }\n          });\n        }, 100);\n      }\n      \n      // Dispatch events for other scripts that might need to initialize\n      document.dispatchEvent(new CustomEvent('vinpop:products-reordered'));\n      document.dispatchEvent(new CustomEvent('theme:product-tile:loaded'));\n      \n    } catch (error) {\n      console.error('Error fetching Shopify products:', error);\n      container.innerHTML = '<p class=\"error\">Error loading products. Please refresh the page.</p>';\n    }\n  }\n\n  showError() {\n    this.loadingEl.style.display = 'none';\n    const errorDiv = document.createElement('div');\n    errorDiv.className = 'taste-profile-error';\n    errorDiv.innerHTML = `\n      <p>Sorry, we couldn't load your recommendations. Please try again later.</p>\n      <button class=\"btn\" onclick=\"location.reload()\">Retry</button>\n    `;\n    this.popup.querySelector('.taste-profile-questions').appendChild(errorDiv);\n  }\n\n  // 3-axis \"taste fingerprint\": a triangle of the wine's lab axes (x/y/z) overlaid\n  // with the customer's quiz taste, so closeness is visible at a glance.\n  // The chart itself now lives in `assets/wine-fiche.js` because the printed kit recap\n  // sheet draws the same triangle; this wrapper is kept so existing call sites and any\n  // console debugging against the quiz instance keep working.\n  buildProductModalFingerprint(wx, wy, wz, ux, uy, uz, isEN) {\n    if (!window.VinPopWineFiche) return '';\n    return window.VinPopWineFiche.fingerprint(wx, wy, wz, ux, uy, uz, isEN);\n  }\n\n  buildProductModalTasteAxes(item, isEN) {\n    const toPct = (value) => {\n      const n = Number(value);\n      if (!Number.isFinite(n)) return null;\n      return Math.max(0, Math.min(100, Math.round(n)));\n    };\n    const x = toPct(item && item.dataset ? item.dataset.axisX : null);\n    const y = toPct(item && item.dataset ? item.dataset.axisY : null);\n    const z = toPct(item && item.dataset ? item.dataset.axisZ : null);\n    if (x === null || y === null || z === null) return '';\n\n    const band = (pct, low, mid, high) => {\n      if (pct < 36) return low;\n      if (pct > 64) return high;\n      return mid;\n    };\n    const escHTML = (s) => String(s)\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#39;');\n\n    const copy = isEN\n      ? {\n          title: 'What it will taste like',\n          fresh: 'Fresh',\n          round: 'Round',\n          soft: 'Soft',\n          structured: 'Structured',\n          light: 'Light',\n          intense: 'Intense',\n          xTitle: band(x, 'Fresh and crisp', 'Balanced freshness', 'Round and smooth'),\n          xText: band(\n            x,\n            'Like choosing lemon vinaigrette: expect a brighter, fresher sip.',\n            'Neither sharp nor heavy: it should feel easy to drink.',\n            'Like choosing creamy dressing or milk chocolate: softer edges, more roundness.'\n          ),\n          yTitle: band(y, 'Soft and gentle', 'Balanced structure', 'Bold structure'),\n          yText: band(\n            y,\n            'Like cappuccino or Brie: low bitterness, low grip.',\n            'Enough structure to feel like wine, without taking over.',\n            'Like black coffee or aged cheese: more grip, depth and character.'\n          ),\n          zTitle: band(z, 'Light impact', 'Medium intensity', 'High intensity'),\n          zText: band(\n            z,\n            'Best when you want something fresh and easy.',\n            'Enough flavour to notice, still comfortable for a first glass.',\n            'Closer to your Bold & Powerful answer: louder flavour and longer finish.'\n          )\n        }\n      : {\n          title: 'Hoe deze wijn smaakt',\n          fresh: 'Fris',\n          round: 'Rond',\n          soft: 'Zacht',\n          structured: 'Gestructureerd',\n          light: 'Licht',\n          intense: 'Intens',\n          xTitle: band(x, 'Fris en strak', 'Gebalanceerde frisheid', 'Rond en zacht'),\n          xText: band(\n            x,\n            'Zoals citroendressing: verwacht een frissere, levendigere slok.',\n            'Niet scherp en niet zwaar: makkelijk om te drinken.',\n            'Zoals romige dressing of melkchocolade: zachtere randen en meer rondheid.'\n          ),\n          yTitle: band(y, 'Zacht en vriendelijk', 'Gebalanceerde structuur', 'Krachtige structuur'),\n          yText: band(\n            y,\n            'Zoals cappuccino of Brie: weinig bitterheid en weinig grip.',\n            'Genoeg structuur om wijn te voelen, zonder te overheersen.',\n            'Zoals zwarte koffie of oude kaas: meer grip, diepte en karakter.'\n          ),\n          zTitle: band(z, 'Lichte indruk', 'Medium intensiteit', 'Hoge intensiteit'),\n          zText: band(\n            z,\n            'Goed als je iets fris en makkelijks wilt.',\n            'Genoeg smaak om op te vallen, nog steeds comfortabel voor een eerste glas.',\n            'Dichter bij jouw Krachtig & Vol antwoord: meer smaak en een langere afdronk.'\n          )\n        };\n\n    const row = (modifier, labelLeft, labelRight, value, title, text) => `\n      <div class=\"wr-product-modal__axis wr-product-modal__axis--${modifier}\">\n        <div class=\"wr-product-modal__axis-head\">\n          <span>${escHTML(title)}</span>\n          <strong>${value}%</strong>\n        </div>\n        <div class=\"wr-product-modal__axis-track\" aria-hidden=\"true\">\n          <span class=\"wr-product-modal__axis-fill\" style=\"width:${value}%\"></span>\n          <span class=\"wr-product-modal__axis-dot\" style=\"left:${value}%\"></span>\n        </div>\n        <div class=\"wr-product-modal__axis-scale\">\n          <span>${escHTML(labelLeft)}</span>\n          <span>${escHTML(labelRight)}</span>\n        </div>\n        <p>${escHTML(text)}</p>\n      </div>\n    `;\n\n    return `\n      <div class=\"wr-product-modal__taste-axes\">\n        <div class=\"wr-product-modal__taste-axes-head\">\n          <span>${escHTML(copy.title)}</span>\n        </div>\n        ${row('freshness', copy.fresh, copy.round, x, copy.xTitle, copy.xText)}\n        ${row('structure', copy.soft, copy.structured, y, copy.yTitle, copy.yText)}\n        ${row('intensity', copy.light, copy.intense, z, copy.zTitle, copy.zText)}\n      </div>\n    `;\n  }\n\n  buildDynamicWineMatchText(wine, fallbackText, isEN) {\n    if (!wine) return fallbackText || '';\n\n    const clampPct = (value) => {\n      const number = Number(value);\n      if (!Number.isFinite(number)) return null;\n      return Math.max(0, Math.min(100, Math.round(number)));\n    };\n    const targetToPct = (value) => Math.round(((Math.max(-1.2, Math.min(1.2, Number(value) || 0)) + 1.2) / 2.4) * 100);\n    const user = {\n      x: clampPct(wine._barUserX) ?? targetToPct(this.targetX),\n      y: clampPct(wine._barUserY) ?? targetToPct(this.targetY),\n      z: clampPct(wine._barUserZ) ?? targetToPct(this.targetZ),\n    };\n    const bottle = {\n      x: clampPct(wine._barWineX),\n      y: clampPct(wine._barWineY),\n      z: clampPct(wine._barWineZ),\n    };\n\n    const axisCopy = {\n      x: isEN\n        ? { name: 'freshness', left: 'fresh', mid: 'balanced', right: 'round' }\n        : { name: 'frisheid', left: 'fris', mid: 'gebalanceerd', right: 'rond' },\n      y: isEN\n        ? { name: 'structure', left: 'soft', mid: 'balanced', right: 'structured' }\n        : { name: 'structuur', left: 'zacht', mid: 'gebalanceerd', right: 'gestructureerd' },\n      z: isEN\n        ? { name: 'intensity', left: 'light', mid: 'balanced', right: 'intense' }\n        : { name: 'intensiteit', left: 'licht', mid: 'gebalanceerd', right: 'intens' },\n    };\n    const zoneFor = (pct, copy) => {\n      if (pct < 42) return copy.left;\n      if (pct > 58) return copy.right;\n      return copy.mid;\n    };\n    const axes = ['x', 'y', 'z']\n      .filter(axis => bottle[axis] !== null)\n      .map(axis => ({\n        axis,\n        copy: axisCopy[axis],\n        userPct: user[axis],\n        bottlePct: bottle[axis],\n        similarity: 100 - Math.abs(user[axis] - bottle[axis])\n      }))\n      .sort((a, b) => b.similarity - a.similarity);\n\n    if (!axes.length) return fallbackText || '';\n\n    const best = axes[0];\n    const second = axes[1];\n    const score = Number.isFinite(Number(wine.score)) ? Math.round(Number(wine.score)) : null;\n    const source = this.axisTweakActive\n      ? (isEN ? 'Your updated sliders' : 'Je aangepaste assen')\n      : (isEN ? 'Your quiz answers' : 'Je quizantwoorden');\n    const userZone = zoneFor(best.userPct, best.copy);\n    const bottleZone = zoneFor(best.bottlePct, best.copy);\n    const secondText = second\n      ? (isEN\n        ? ` It also stays close on ${second.copy.name}.`\n        : ` Hij blijft ook dicht bij je ${second.copy.name}.`)\n      : '';\n    const scoreText = score !== null\n      ? (isEN ? ` ${score}% match.` : ` ${score}% match.`)\n      : '';\n\n    if (userZone === bottleZone) {\n      return isEN\n        ? `${source} point to ${userZone} ${best.copy.name}. This bottle sits in the same taste zone.${scoreText}${secondText}`\n        : `${source} wijzen naar ${userZone} ${best.copy.name}. Deze fles zit in dezelfde smaakzone.${scoreText}${secondText}`;\n    }\n\n    return isEN\n      ? `${source} are closest to this bottle on ${best.copy.name}: you are ${userZone}, it is ${bottleZone}.${scoreText}${secondText}`\n      : `${source} komen het dichtst bij deze fles op ${best.copy.name}: jij bent ${userZone}, de fles is ${bottleZone}.${scoreText}${secondText}`;\n  }\n\n  // The gift bottle is not part of the scored selection, so its lab axes have to be looked\n  // up in the pool every wine was scored against. Without them the product modal cannot\n  // draw the taste fingerprint.\n  getScoredWineByProductId(productId) {\n    const id = String(productId || '');\n    if (!id) return null;\n    const pools = [this.calibrationSearchPool, this.recommendedWines, this._packWines];\n    for (const pool of pools) {\n      if (!Array.isArray(pool)) continue;\n      const found = pool.find(wine => wine && String(wine.wine_id) === id);\n      if (found) return found;\n    }\n    return null;\n  }\n\n  // Same lab axes the kit cards publish, so the modal draws the identical fingerprint.\n  buildAxisAttrs(wine) {\n    return [\n      Number.isFinite(Number(wine?._barWineX)) ? `data-axis-x=\"${Math.round(Number(wine._barWineX))}\"` : '',\n      Number.isFinite(Number(wine?._barWineY)) ? `data-axis-y=\"${Math.round(Number(wine._barWineY))}\"` : '',\n      Number.isFinite(Number(wine?._barWineZ)) ? `data-axis-z=\"${Math.round(Number(wine._barWineZ))}\"` : '',\n    ].filter(Boolean).join(' ');\n  }\n\n  // Free gift bottle card, rendered next to the kit bottles. It is never scored and never\n  // shows a match %: it is a gift, and pretending it was chosen for this customer's taste\n  // would undermine the very thing the quiz is proving. The fingerprint is still shown,\n  // because measuring the bottle is not the same claim as matching it to the customer.\n  buildRebelBonusCardHTML(productId, isEN, catalog = window.shopCatalog || {}) {\n    const rebelProd = catalog[String(productId || '')];\n    if (!rebelProd) return '';\n    const axisAttrs = this.buildAxisAttrs(this.getScoredWineByProductId(productId));\n    const escAttr = (s) => String(s || '').replace(/\"/g, '&quot;');\n    const escHTML = (s) => String(s || '')\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#39;');\n    const rebelImgUrl = rebelProd.image && rebelProd.image.url ? rebelProd.image.url : '';\n    const rebelUrl = rebelProd.url || '';\n    const rebelPrice = this.getPaidDisplayPrice(rebelProd);\n    const rebelTypeLabel = isEN ? '\uD83C\uDF81 Free gift' : '\uD83C\uDF81 Gratis cadeau';\n    const rebelMolecularTitle = isEN ? '\uD83C\uDF81 YOUR FREE BOTTLE' : '\uD83C\uDF81 JOUW GRATIS FLES';\n    const rebelMolecularText = isEN\n      ? 'A thank-you gift, not a taste match: this bottle is on us for trying VinPop.'\n      : 'Een bedankje, geen smaakmatch: deze fles is van ons omdat je VinPop probeert.';\n    const openLabel = isEN ? 'Open details' : 'Details openen';\n\n    return (\n      `<div class=\"wr-bundle-item wr-bundle-item--rebel-bonus\" role=\"button\" tabindex=\"0\" data-wine-id=\"${escAttr(productId)}\" data-product-url=\"${escAttr(rebelUrl)}\" data-display-price=\"${rebelPrice}\" data-type-label=\"${escAttr(rebelTypeLabel)}\" data-molecular-title=\"${escAttr(rebelMolecularTitle)}\" data-molecular-text=\"${escAttr(rebelMolecularText)}\" ${axisAttrs}>` +\n      `<div class=\"wr-bundle-item__img-frame\">` +\n      `<img src=\"${escAttr(rebelImgUrl)}\" alt=\"${escAttr(rebelProd.title)}\" class=\"wr-bundle-item__img\">` +\n      `</div>` +\n      `<div class=\"wr-bundle-item__info\">` +\n      `<div class=\"wr-bundle-item__type\">${escHTML(rebelTypeLabel)}</div>` +\n      `<div class=\"wr-bundle-item__title\">${escHTML(rebelProd.title)}</div>` +\n      this.buildProvenanceHTML(rebelProd, isEN, escHTML) +\n      `<div class=\"wr-bundle-item__why\">` +\n      `<span>${isEN ? 'Why this wine' : 'Waarom deze wijn'}</span>` +\n      `<p>${escHTML(rebelMolecularText)}</p>` +\n      `</div>` +\n      `<div class=\"wr-bundle-item__score-row\">` +\n      `<span class=\"wr-bundle-item__open-label\">${escHTML(openLabel)}</span>` +\n      `</div>` +\n      `</div>` +\n      `</div>`\n    );\n  }\n\n  // ------------------------------------------------------------------\n  // +1 bottle upsell: one extra bottle at 20% off, picked from the same\n  // scored candidate list as the kit, swappable through a Replace button.\n  // ------------------------------------------------------------------\n\n  getPaidVariantId(product) {\n    const variants = Array.isArray(product?.variants) ? product.variants : [];\n    const availablePaid = variants.find(v => v && v.available && Number(v.price) > 0);\n    return availablePaid ? availablePaid.id : null;\n  }\n\n  formatEurosFromCents(cents) {\n    return (Math.round(Number(cents) || 0) / 100).toFixed(2).replace('.', ',');\n  }\n\n  // `product.vendor` IS the winemaker on this store (vendor \"Aotearoa\" has a matching\n  // \"Aotearoa\" collection, the convention `snippets/product-block-vendor.liquid` already\n  // relies on). It is still dropped when it repeats the shop name, since printing \"VinPop\"\n  // under every bottle proves nothing.\n  // Returns '' whenever there is no usable name, and the caller then omits the line.\n  growerName(product) {\n    const vendor = String(product?.vendor || '').trim();\n    if (!vendor) return '';\n    const flatten = (value) => String(value || '').toLowerCase().replace(/[^a-z0-9]/g, '');\n    const shop = flatten(String(window.Shopify?.shop || '').replace(/\\.myshopify\\.com$/i, ''));\n    const flatVendor = flatten(vendor);\n    if (!flatVendor || flatVendor === 'vinpop' || (shop && flatVendor === shop)) return '';\n    return vendor;\n  }\n\n  // Provenance strip under the bottle title: who made it, where it comes from, and the one\n  // claim that separates it from a supermarket shelf. This is the premium argument the\n  // Dutch market actually reads, since a \u20AC9 retail price is no premium signal against a \u20AC7\n  // supermarket bottle. Grower, region and country are all optional in the catalog, so the\n  // strip degrades to the supermarket line alone rather than printing empty separators.\n  buildProvenanceHTML(product, isEN, escHTML) {\n    const grower = this.growerName(product);\n    const place = [product?.region, product?.country]\n      .map(value => String(value || '').trim())\n      .filter(Boolean)\n      .join(', ');\n    const origin = [grower, place].filter(Boolean);\n    return (\n      `<div class=\"wr-bundle-item__provenance\">` +\n      (origin.length\n        ? `<span class=\"wr-bundle-item__origin\">${origin.map(part => escHTML(part)).join(' \u00B7 ')}</span>`\n        : '') +\n      `<span class=\"wr-bundle-item__exclusive\">${isEN ? 'Not in supermarkets' : 'Niet in de supermarkt'}</span>` +\n      `</div>`\n    );\n  }\n\n  // Rounded down, so the badge never claims a bigger discount than the two numbers\n  // printed next to it actually add up to. Returns 0 when there is nothing to save,\n  // which is the signal to hide the badge rather than print \"-0%\".\n  discountPercent(retailCents, paidCents) {\n    const retail = Math.round(Number(retailCents) || 0);\n    const paid = Math.round(Number(paidCents) || 0);\n    if (retail <= 0 || paid >= retail) return 0;\n    return Math.floor(((retail - paid) / retail) * 100);\n  }\n\n  // Products the Buy X Get Y automatic discount actually covers, published by\n  // `snippets/wine-catalog-data.liquid` from the discount's \"Customer gets\" collection.\n  getUpsellEligibleIds() {\n    const raw = window.vinpopUpsellEligibleIds;\n    if (!Array.isArray(raw) || !raw.length) return null;\n    return new Set(raw.map(String));\n  }\n\n  // `candidates` arrives already scored against the customer's quiz point and sorted by\n  // taste distance, exactly like the kit bottles. Wines inside the customer's taste zone\n  // are promoted ahead of the rest, so an added bottle is never a generic cross-sell.\n  buildUpsellCandidates(candidates, packWines, catalog = window.shopCatalog || {}) {\n    // KILL SWITCH. The whole upsell renders from this pool, so returning [] here hides the\n    // teaser and every extra-bottle card on both the first render and the silent re-render.\n    // Off depuis le 2026-07-28 : la remise -20% sur bouteilles suppl\u00E9mentaires est retir\u00E9e\n    // du parcours le temps de tester une autre approche (et elle ne s'appliquait pas au\n    // checkout, donc afficher un prix barr\u00E9 non honor\u00E9 trompait le client).\n    if (this.upsellEnabled === false) {\n      console.warn('\uD83C\uDF77 Upsell hidden: disabled until the -20% discount is confirmed at checkout.');\n      return [];\n    }\n\n    // The catalog is wider than the discount's collection: it also loads the blind tasting\n    // SKUs and the wishing-list wines. Offering one of those would print -20% on a bottle\n    // Shopify charges in full, so no eligibility list means no upsell at all.\n    const eligible = this.getUpsellEligibleIds();\n    if (!eligible) {\n      console.error('\uD83C\uDF77 Upsell disabled: window.vinpopUpsellEligibleIds is missing or empty. Check that snippets/wine-catalog-data.liquid renders on this page.');\n      return [];\n    }\n\n    const packIds = new Set((packWines || []).map(wine => String(wine.wine_id)));\n    const matches = [];\n    const seen = new Set();\n    const skippedIneligible = [];\n    const cuts = { inKit: 0, duplicate: 0, notInCatalog: 0, outOfStock: 0, notEligible: 0, noPaidVariant: 0 };\n\n    (candidates || []).forEach(wine => {\n      const productId = String(wine?.wine_id || '');\n      if (!productId) return;\n      if (packIds.has(productId)) { cuts.inKit += 1; return; }\n      if (seen.has(productId)) { cuts.duplicate += 1; return; }\n      const product = catalog[productId];\n      if (!product) { cuts.notInCatalog += 1; return; }\n      if (product.available === false) { cuts.outOfStock += 1; return; }\n      if (!eligible.has(productId)) {\n        seen.add(productId);\n        cuts.notEligible += 1;\n        skippedIneligible.push(`${productId} ${product.title || ''}`);\n        return;\n      }\n      const variantId = this.getPaidVariantId(product);\n      const price = this.getPaidDisplayPrice(product);\n      if (!variantId || !price) { cuts.noPaidVariant += 1; return; }\n      seen.add(productId);\n      matches.push({ productId, variantId, product, price, wine });\n    });\n\n    // Stable partition: inside the taste zone first, each group keeping the incoming\n    // taste-distance order. `_insideTasteZone` is the same test the kit selection uses.\n    const inZone = matches.filter(entry => entry.wine && entry.wine._insideTasteZone);\n    const outZone = matches.filter(entry => !entry.wine || !entry.wine._insideTasteZone);\n    const picked = inZone.concat(outZone).slice(0, this.upsellPoolSize);\n\n    // Surfaced so an over-restrictive discount collection is visible instead of silently\n    // shrinking the choice the customer gets.\n    if (skippedIneligible.length) {\n      console.warn('\uD83C\uDF77 Upsell skipped (not in the discount collection):', skippedIneligible);\n    }\n    // The pool size is the hard ceiling on how many bottles the customer can add, so when\n    // it comes out below `upsellMaxBottles` this breakdown says which filter ate the wines.\n    console.log(\n      `\uD83C\uDF77 Upsell pool: ${picked.length} wines (${inZone.length} inside the taste zone) ` +\n      `from ${(candidates || []).length} candidates, colour preference \"${this.colorPreference}\". ` +\n      `Cut: ${cuts.inKit} in the kit, ${cuts.duplicate} duplicates, ${cuts.notInCatalog} not in the catalog, ` +\n      `${cuts.outOfStock} out of stock, ${cuts.notEligible} outside the discount collection, ` +\n      `${cuts.noPaidVariant} without a paid variant.`\n    );\n    if (picked.length < this.upsellMaxBottles) {\n      console.warn(\n        `\uD83C\uDF77 Upsell capped at ${picked.length} bottles instead of ${this.upsellMaxBottles}: ` +\n        'not enough eligible wines match this customer. Widen the discount collection, ' +\n        'restock, or relax the colour preference to go deeper.'\n      );\n    }\n\n    return picked;\n  }\n\n  // Rendered as a real `.wr-bundle-item`, not a bespoke card: it inherits the kit card\n  // layout, and on mobile it inherits `order: 10` and the 2-column grid cell, so it lands\n  // in the flow right after the kit bottles instead of jumping above them.\n  buildUpsellCardHTML(card, position, isEN) {\n    const entry = this._upsell.pool[card.options[card.optionIndex]];\n    if (!entry) return '';\n\n    const escAttr = (s) => String(s == null ? '' : s).replace(/\"/g, '&quot;');\n    const escHTML = (s) => String(s == null ? '' : s)\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#39;');\n\n    const product = entry.product;\n    const discountLabel = `${Math.round(this.upsellDiscountRate * 100)}%`;\n    const oldPrice = entry.price;\n    const newPrice = Math.round(oldPrice * (1 - this.upsellDiscountRate));\n    const imgUrl = product.image && product.image.url ? product.image.url : '';\n    const productUrl = product.url || (product.handle ? `/products/${product.handle}` : '');\n    const typeLabel = isEN ? `\uD83C\uDF77 Extra bottle -${discountLabel}` : `\uD83C\uDF77 Extra fles -${discountLabel}`;\n    const whyText = entry.wine && entry.wine._insideTasteZone\n      ? (isEN\n        ? 'Inside your taste zone: this bottle sits close to the taste point your answers drew.'\n        : 'Binnen je smaakzone: deze fles ligt dicht bij het smaakpunt uit je antwoorden.')\n      : (isEN\n        ? 'Selected based on your quiz answers, right after the three bottles of your kit.'\n        : 'Gekozen op basis van je quizantwoorden, direct na de drie flessen van je kit.');\n    const openLabel = isEN ? 'Open details' : 'Details openen';\n    const reachableOptions = this.getUpsellCardOptions(position);\n\n    // Same lab axes the kit cards publish. Without these the product modal cannot draw the\n    // taste fingerprint, which is the whole proof that this bottle fits the customer.\n    const wine = entry.wine || {};\n    const axisAttrs = [\n      Number.isFinite(Number(wine._barWineX)) ? `data-axis-x=\"${Math.round(Number(wine._barWineX))}\"` : '',\n      Number.isFinite(Number(wine._barWineY)) ? `data-axis-y=\"${Math.round(Number(wine._barWineY))}\"` : '',\n      Number.isFinite(Number(wine._barWineZ)) ? `data-axis-z=\"${Math.round(Number(wine._barWineZ))}\"` : '',\n    ].filter(Boolean).join(' ');\n\n    return (\n      `<div class=\"wr-bundle-item wr-bundle-item--upsell\" role=\"button\" tabindex=\"0\" aria-label=\"${escAttr(openLabel)}\" data-upsell-position=\"${position}\" data-wine-id=\"${escAttr(entry.productId)}\" data-product-url=\"${escAttr(productUrl)}\" data-display-price=\"${newPrice}\" data-compare-price=\"${oldPrice}\" data-type-label=\"${escAttr(typeLabel)}\" data-molecular-title=\"${escAttr(isEN ? 'YOUR EXTRA BOTTLE' : 'JOUW EXTRA FLES')}\" data-molecular-text=\"${escAttr(whyText)}\" ${axisAttrs}>` +\n      `<div class=\"wr-bundle-item__img-frame\">` +\n      `<img src=\"${escAttr(imgUrl)}\" alt=\"${escAttr(product.title)}\" class=\"wr-bundle-item__img\">` +\n      `</div>` +\n      `<div class=\"wr-bundle-item__info\">` +\n      `<div class=\"wr-bundle-item__type\">${escHTML(typeLabel)}</div>` +\n      `<div class=\"wr-bundle-item__title\">${escHTML(product.title)}</div>` +\n      `<div class=\"wr-bundle-item__why\">` +\n      `<span>${isEN ? 'Why this wine' : 'Waarom deze wijn'}</span>` +\n      `<p>${escHTML(whyText)}</p>` +\n      `</div>` +\n      `<div class=\"wr-bundle-item__score-row wr-upsell-item__price-row\">` +\n      `<s class=\"wr-upsell-item__price-old\">\u20AC${this.formatEurosFromCents(oldPrice)}</s>` +\n      `<span class=\"wr-upsell-item__price-new\">\u20AC${this.formatEurosFromCents(newPrice)}</span>` +\n      `</div>` +\n      `<div class=\"wr-upsell-item__actions\" style=\"display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:10px;\">` +\n      (reachableOptions.length > 1\n        // Replace = a real secondary button: bordered pill, terracotta, swap icon + counter.\n        ? `<button type=\"button\" class=\"wr-upsell-item__swap\" style=\"display:inline-flex;align-items:center;gap:5px;padding:7px 12px;border:1.5px solid #E07A5F;border-radius:999px;background:#fff;color:#C4573A;font-size:12px;font-weight:800;line-height:1;cursor:pointer;\">\uD83D\uDD04 ${isEN ? 'Swap wine' : 'Wissel wijn'} <span class=\"wr-upsell-item__swap-count\" style=\"font-weight:700;opacity:0.7;\">${reachableOptions.indexOf(card.options[card.optionIndex]) + 1}/${reachableOptions.length}</span></button>`\n        : `<span></span>`) +\n      // Remove = destructive, and it must not look like the swap button: no border, muted\n      // red, a \u2715 glyph, pushed to the opposite edge so the two are never mistaken.\n      `<button type=\"button\" class=\"wr-upsell-item__remove\" style=\"display:inline-flex;align-items:center;gap:4px;padding:7px 4px;border:0;background:none;color:#B0453A;font-size:12px;font-weight:600;line-height:1;cursor:pointer;\">\u2715 ${isEN ? 'Remove' : 'Verwijder'}</button>` +\n      `</div>` +\n      `</div>` +\n      `</div>`\n    );\n  }\n\n  // First pool index no card is showing: the wine the teaser offers next.\n  getTeaserPoolIndex() {\n    const state = this._upsell;\n    if (!state) return null;\n    const shown = this.getDisplayedUpsellIndices();\n    for (let index = 0; index < state.pool.length; index += 1) {\n      if (!shown.has(index)) return index;\n    }\n    return null;\n  }\n\n  // The plain \"Add a bottle\" button got looked at but not clicked (Clarity): text alone\n  // does not read as an offer. The teaser is instead a real product card showing the next\n  // recommended bottle, its struck price and a filled Add button, so the discount is a\n  // thing the customer sees, not a line they skim. Being a `.wr-bundle-item` it inherits\n  // `order: 10` and lands after the kit bottles on both layouts, no `order` juggling.\n  buildUpsellTeaserHTML(isEN) {\n    const poolIndex = this.getTeaserPoolIndex();\n    if (poolIndex === null) return '';\n    const entry = this._upsell.pool[poolIndex];\n    if (!entry) return '';\n\n    const escAttr = (s) => String(s == null ? '' : s).replace(/\"/g, '&quot;');\n    const escHTML = (s) => String(s == null ? '' : s)\n      .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;').replace(/'/g, '&#39;');\n\n    const product = entry.product;\n    const wine = entry.wine || {};\n    const discountLabel = `${Math.round(this.upsellDiscountRate * 100)}%`;\n    const oldPrice = entry.price;\n    const newPrice = Math.round(oldPrice * (1 - this.upsellDiscountRate));\n    const imgUrl = product.image && product.image.url ? product.image.url : '';\n    const productUrl = product.url || (product.handle ? `/products/${product.handle}` : '');\n    const bottleNo = (Array.isArray(this._packWines) ? this._packWines.length : 3) + this._upsell.added.length + 1;\n    const typeLabel = isEN ? `\u2795 Add bottle ${bottleNo} \u00B7 -${discountLabel}` : `\u2795 Fles ${bottleNo} toevoegen \u00B7 -${discountLabel}`;\n    const whyText = wine._insideTasteZone\n      ? (isEN\n        ? 'Inside your taste zone: this bottle sits close to the taste point your answers drew.'\n        : 'Binnen je smaakzone: deze fles ligt dicht bij het smaakpunt uit je antwoorden.')\n      : (isEN\n        ? 'Selected based on your quiz answers, right after the three bottles of your kit.'\n        : 'Gekozen op basis van je quizantwoorden, direct na de drie flessen van je kit.');\n    const openLabel = isEN ? 'Open details' : 'Details openen';\n    const addLabel = isEN ? `+ Add to my box \u00B7 \u20AC${this.formatEurosFromCents(newPrice)}` : `+ Toevoegen \u00B7 \u20AC${this.formatEurosFromCents(newPrice)}`;\n\n    const axisAttrs = [\n      Number.isFinite(Number(wine._barWineX)) ? `data-axis-x=\"${Math.round(Number(wine._barWineX))}\"` : '',\n      Number.isFinite(Number(wine._barWineY)) ? `data-axis-y=\"${Math.round(Number(wine._barWineY))}\"` : '',\n      Number.isFinite(Number(wine._barWineZ)) ? `data-axis-z=\"${Math.round(Number(wine._barWineZ))}\"` : '',\n    ].filter(Boolean).join(' ');\n\n    // Filled terracotta, inline: the whole reason we are here is that a subtle affordance\n    // was invisible. Inline also means no stylesheet can strip it, the lesson from the CTA.\n    const addStyle = [\n      'display:flex', 'align-items:center', 'justify-content:center', 'gap:6px',\n      'width:100%', 'min-height:46px', 'margin-top:10px', 'padding:12px 14px',\n      'border:0', 'border-radius:12px', 'background:#E07A5F', 'color:#fff',\n      'font-size:14px', 'font-weight:800', 'line-height:1.2', 'text-align:center',\n      'cursor:pointer', 'box-sizing:border-box'\n    ].join(';');\n\n    return (\n      `<div class=\"wr-bundle-item wr-bundle-item--upsell wr-bundle-item--upsell-teaser\" role=\"button\" tabindex=\"0\" aria-label=\"${escAttr(openLabel)}\" data-upsell-teaser=\"1\" data-wine-id=\"${escAttr(entry.productId)}\" data-product-url=\"${escAttr(productUrl)}\" data-display-price=\"${newPrice}\" data-compare-price=\"${oldPrice}\" data-type-label=\"${escAttr(typeLabel)}\" data-molecular-title=\"${escAttr(isEN ? 'YOUR EXTRA BOTTLE' : 'JOUW EXTRA FLES')}\" data-molecular-text=\"${escAttr(whyText)}\" ${axisAttrs}>` +\n      `<div class=\"wr-bundle-item__img-frame\">` +\n      `<img src=\"${escAttr(imgUrl)}\" alt=\"${escAttr(product.title)}\" class=\"wr-bundle-item__img\">` +\n      `</div>` +\n      `<div class=\"wr-bundle-item__info\">` +\n      `<div class=\"wr-bundle-item__type\">${escHTML(typeLabel)}</div>` +\n      `<div class=\"wr-bundle-item__title\">${escHTML(product.title)}</div>` +\n      `<div class=\"wr-bundle-item__why\">` +\n      `<span>${isEN ? 'Why this wine' : 'Waarom deze wijn'}</span>` +\n      `<p>${escHTML(whyText)}</p>` +\n      `</div>` +\n      `<div class=\"wr-bundle-item__score-row wr-upsell-item__price-row\">` +\n      `<s class=\"wr-upsell-item__price-old\">\u20AC${this.formatEurosFromCents(oldPrice)}</s>` +\n      `<span class=\"wr-upsell-item__price-new\">\u20AC${this.formatEurosFromCents(newPrice)}</span>` +\n      `</div>` +\n      `<button type=\"button\" class=\"wr-upsell-teaser__add\" style=\"${addStyle}\">${escHTML(addLabel)}</button>` +\n      `</div>` +\n      `</div>`\n    );\n  }\n\n  // Only the bottle a card currently shows is exclusive. Alternatives are shared between\n  // cards, so a 9-bottle box needs 9 distinct wines, not 27. Reserving whole blocks was\n  // what stopped the customer at 3 bottles: the eligible pool ran out.\n  getDisplayedUpsellIndices(exceptPosition = -1) {\n    const shown = new Set();\n    (this._upsell?.added || []).forEach((card, position) => {\n      if (position === exceptPosition) return;\n      shown.add(card.options[card.optionIndex]);\n    });\n    return shown;\n  }\n\n  // A card gets `upsellSwapOptions` alternatives to cycle through, taken from the best\n  // wines no other card is showing right now.\n  takeUpsellOptionBlock() {\n    const state = this._upsell;\n    if (!state) return null;\n    const shown = this.getDisplayedUpsellIndices();\n    const block = [];\n    for (let index = 0; index < state.pool.length && block.length < this.upsellSwapOptions; index += 1) {\n      if (!shown.has(index)) block.push(index);\n    }\n    return block.length ? block : null;\n  }\n\n  // Pool indices this card can actually show: the one it displays, plus the alternatives\n  // no other card has taken. The Replace counter is built from this, never from the raw\n  // block, so it can never promise an option the customer cannot reach.\n  getUpsellCardOptions(position) {\n    const card = this._upsell?.added?.[position];\n    if (!card) return [];\n    const current = card.options[card.optionIndex];\n    const shown = this.getDisplayedUpsellIndices(position);\n    return card.options.filter(index => index === current || !shown.has(index));\n  }\n\n  // Next reachable option, as an index into `card.options`. null when Replace would be a\n  // no-op, which is when the button is not rendered at all.\n  getNextUpsellOptionIndex(position) {\n    const card = this._upsell?.added?.[position];\n    if (!card) return null;\n    const reachable = this.getUpsellCardOptions(position);\n    if (reachable.length < 2) return null;\n    const current = card.options[card.optionIndex];\n    const nextPoolIndex = reachable[(reachable.indexOf(current) + 1) % reachable.length];\n    return card.options.indexOf(nextPoolIndex);\n  }\n\n  canAddAnotherUpsellBottle() {\n    const state = this._upsell;\n    if (!state) return false;\n    if (state.added.length >= this.upsellMaxBottles) return false;\n    return this.getDisplayedUpsellIndices().size < state.pool.length;\n  }\n\n  // Every extra bottle currently in the box, in display order.\n  getAddedUpsellEntries() {\n    const state = this._upsell;\n    if (!state) return [];\n    return state.added\n      .map(card => state.pool[card.options[card.optionIndex]])\n      .filter(Boolean);\n  }\n\n  // Repaints every upsell card and the CTA inside `.wr-bundle-items`, then re-syncs the\n  // prices the page announces. Single entry point: add, replace and remove all call this.\n  renderUpsellState(cardsEl, isEN) {\n    const listEl = cardsEl?.querySelector('.wr-bundle-items');\n    if (!listEl || !this._upsell) return;\n\n    listEl.querySelectorAll('.wr-bundle-item--upsell').forEach(el => el.remove());\n\n    if (this._upsell.pool.length) {\n      // Committed bottles, then the teaser for the next one. Both are `.wr-bundle-item`s,\n      // so they inherit `order: 10` and flow right after the kit bottles on both layouts,\n      // which is what finally killed the `order` positioning bug the plain button had.\n      const html =\n        this._upsell.added\n          .map((card, position) => this.buildUpsellCardHTML(card, position, isEN))\n          .join('') +\n        (this.canAddAnotherUpsellBottle() ? this.buildUpsellTeaserHTML(isEN) : '');\n\n      const kitCards = listEl.querySelectorAll('.wr-bundle-item:not(.wr-bundle-item--upsell)');\n      const lastKitCard = kitCards[kitCards.length - 1];\n      if (lastKitCard) {\n        lastKitCard.insertAdjacentHTML('afterend', html);\n      } else {\n        listEl.insertAdjacentHTML('beforeend', html);\n      }\n    }\n\n    this.syncUpsellPricing(cardsEl, isEN);\n  }\n\n  syncUpsellPricing(cardsEl, isEN) {\n    const added = this.getAddedUpsellEntries();\n    const kitCents = 2990;\n    const extraCents = added.reduce(\n      (sum, entry) => sum + Math.round(entry.price * (1 - this.upsellDiscountRate)),\n      0\n    );\n    const totalEuros = this.formatEurosFromCents(kitCents + extraCents);\n\n    const btnSurface = cardsEl.querySelector('.wr-bundle__add .push-btn__surface');\n    if (btnSurface && !cardsEl.querySelector('.wr-bundle__add')?.disabled) {\n      btnSurface.textContent = isEN\n        ? `Get my Taste Kit - \u20AC${totalEuros.replace(',', '.')}`\n        : `Ontvang mijn Taste Kit - \u20AC${totalEuros}`;\n    }\n\n    const stickyPrice = cardsEl.querySelector('.wr-sticky-buy__price');\n    if (stickyPrice) stickyPrice.textContent = `\u20AC${totalEuros}`;\n\n    const mathPrice = cardsEl.querySelector('.wr-bundle__math-total-price');\n    if (mathPrice) mathPrice.textContent = `\u20AC${totalEuros}`;\n\n    const mathUpsell = cardsEl.querySelector('.wr-bundle__math-upsell');\n    if (mathUpsell) {\n      mathUpsell.style.display = added.length ? 'flex' : 'none';\n      const countEl = mathUpsell.querySelector('.wr-bundle__math-upsell-count');\n      if (countEl) countEl.textContent = String(added.length);\n      const nounEl = mathUpsell.querySelector('.wr-bundle__math-upsell-noun');\n      if (nounEl) {\n        nounEl.textContent = added.length > 1\n          ? (isEN ? 'extra bottles you picked, at 20% off' : 'extra flessen die je koos, met 20% korting')\n          : (isEN ? 'extra bottle you picked, at 20% off' : 'extra fles die je koos, met 20% korting');\n      }\n    }\n\n    const math = this._bundleMath;\n    if (!math) return;\n\n    const bottleCount = math.baseCount + added.length;\n    const wineCents = math.wineCents + added.reduce((sum, entry) => sum + entry.price, 0);\n    const paidCents = kitCents + extraCents;\n\n    const setText = (selector, value) => {\n      const el = cardsEl.querySelector(selector);\n      if (el) el.textContent = value;\n    };\n\n    setText('.wr-bundle__math-total-count', String(bottleCount));\n    setText('.wr-bundle__math-normal-bottle', `\u20AC${this.formatEurosFromCents(Math.round(wineCents / Math.max(1, bottleCount)))}`);\n    setText('.wr-bundle__math-paid-bottle', `\u20AC${this.formatEurosFromCents(Math.round(paidCents / Math.max(1, bottleCount)))}`);\n\n    const mathPercent = cardsEl.querySelector('.wr-bundle__math-percent');\n    if (mathPercent) {\n      const percent = this.discountPercent(wineCents, paidCents);\n      mathPercent.textContent = `-${percent}%`;\n      mathPercent.style.display = percent > 0 ? 'inline-block' : 'none';\n    }\n\n    // The saving line reads the shipping tier at the NEW bottle count, so a fifth bottle moves\n    // the delivery term from \u20AC15,00 to \u20AC17,00 the same way it moves the wine term. Both are\n    // printed, so a customer who checks the addition finds it correct at every count.\n    const savingEl = cardsEl.querySelector('.wr-bundle__math-saving');\n    if (savingEl) {\n      const shippingCents = typeof math.getShippingCents === 'function'\n        ? math.getShippingCents(bottleCount)\n        : 0;\n      const wineSavingCents = Math.max(0, wineCents - paidCents);\n      const totalSavingCents = wineSavingCents + shippingCents;\n      savingEl.style.display = totalSavingCents > 0 ? 'block' : 'none';\n      setText('.wr-bundle__math-saving-total', `\u20AC${this.formatEurosFromCents(totalSavingCents)}`);\n      setText('.wr-bundle__math-saving-wine', `\u20AC${this.formatEurosFromCents(wineSavingCents)}`);\n      setText('.wr-bundle__math-saving-shipping', `\u20AC${this.formatEurosFromCents(shippingCents)}`);\n    }\n  }\n\n  // Re-rendering removes the clicked button from the DOM. That drops focus to <body> and,\n  // because the cards briefly disappear, the document shrinks and the browser clamps the\n  // scroll position. On desktop, where the cards stack in one column, that reads as the\n  // page jumping upwards. Restore both the scroll offsets and the focus.\n  renderUpsellPreservingViewport(cardsEl, isEN, focusSelector) {\n    const popupContent = this.popup ? this.popup.querySelector('.taste-profile-popup-content') : null;\n    const windowY = window.scrollY;\n    const popupY = popupContent ? popupContent.scrollTop : null;\n\n    this.renderUpsellState(cardsEl, isEN);\n\n    if (popupContent && popupY !== null) popupContent.scrollTop = popupY;\n    if (window.scrollY !== windowY) window.scrollTo({ top: windowY, behavior: 'instant' });\n\n    if (focusSelector) {\n      const next = cardsEl.querySelector(focusSelector);\n      if (next && typeof next.focus === 'function') next.focus({ preventScroll: true });\n    }\n  }\n\n  setupUpsellBlock(cardsEl, isEN) {\n    if (!cardsEl || !this._upsell) return;\n\n    // The upsell cards are `.wr-bundle-item`s, so the delegated handler that opens the\n    // product modal also matches them. That is wanted for the card body, but a click on\n    // Replace or Remove must not open a modal. Both listeners sit on `cardsEl`, so only\n    // stopImmediatePropagation can keep the modal one from firing.\n    const handleUpsellClick = (event) => {\n      const state = this._upsell;\n      if (!state) return;\n\n      const swapBtn = event.target.closest('.wr-upsell-item__swap');\n      const removeBtn = event.target.closest('.wr-upsell-item__remove');\n      const addBtn = event.target.closest('.wr-upsell-teaser__add');\n      if (!swapBtn && !removeBtn && !addBtn) return;\n      if (!cardsEl.contains(event.target)) return;\n\n      event.preventDefault();\n      event.stopImmediatePropagation();\n\n      if (addBtn) {\n        // The teaser shows the first free wine; committing it takes a fresh option block\n        // whose [0] is that same wine, so the bottle added is exactly the one previewed.\n        const options = this.takeUpsellOptionBlock();\n        if (options) state.added.push({ options, optionIndex: 0 });\n        // Land focus on the next teaser's Add so a customer building a case keeps clicking\n        // the same spot instead of chasing a button that moved down the page.\n        this.renderUpsellPreservingViewport(cardsEl, isEN, '.wr-upsell-teaser__add');\n        return;\n      }\n\n      const cardEl = (swapBtn || removeBtn).closest('.wr-bundle-item--upsell');\n      const position = parseInt(cardEl?.dataset.upsellPosition ?? '-1', 10);\n      if (position < 0 || position >= state.added.length) return;\n\n      if (swapBtn) {\n        const next = this.getNextUpsellOptionIndex(position);\n        if (next !== null) state.added[position].optionIndex = next;\n        this.renderUpsellPreservingViewport(\n          cardsEl, isEN,\n          `.wr-bundle-item--upsell[data-upsell-position=\"${position}\"] .wr-upsell-item__swap`\n        );\n      } else {\n        state.added.splice(position, 1);\n        this.renderUpsellPreservingViewport(cardsEl, isEN, '.wr-upsell-teaser__add');\n      }\n    };\n\n    // Enter/Space on one of our buttons must press the button, not open the card modal.\n    const handleUpsellKeydown = (event) => {\n      if (event.key !== 'Enter' && event.key !== ' ') return;\n      if (!event.target.closest('.wr-upsell-item__swap, .wr-upsell-item__remove, .wr-upsell-teaser__add')) return;\n      event.stopImmediatePropagation();\n    };\n\n    cardsEl.addEventListener('click', handleUpsellClick);\n    cardsEl.addEventListener('keydown', handleUpsellKeydown);\n\n    this.renderUpsellState(cardsEl, isEN);\n  }\n\n  isReplacementProductAvailable(product) {\n    if (!product || product.available === false) return false;\n    const variants = Array.isArray(product.variants) ? product.variants : [];\n    if (!variants.length) return !!product.available;\n    return variants.some(variant => {\n      if (!variant || variant.available === false) return false;\n      if (variant.inventoryManaged && Number(variant.inventoryQuantity) <= 0) return false;\n      return true;\n    });\n  }\n\n  getPackReplacementOptions(position, catalog = window.shopCatalog || {}) {\n    const current = this._packWines?.[position];\n    if (!current) return [];\n\n    const shown = new Set(this._packWines\n      .filter((wine, index) => index !== position && wine)\n      .map(wine => String(wine.wine_id)));\n\n    const currentId = String(current.wine_id);\n    const otherTotal = this._packWines.reduce((sum, wine, index) => {\n      if (!wine || index === position) return sum;\n      return sum + this.getPaidDisplayPrice(catalog[String(wine.wine_id)]);\n    }, 0);\n    const band = this.getPackPriceBand(catalog, product => this.isReplacementProductAvailable(product));\n    const replacementMax = 4000;\n    const options = [current];\n    const seen = new Set([currentId]);\n\n    (this._packReplacementCandidates || []).forEach(candidate => {\n      const productId = String(candidate?.wine_id || '');\n      const product = catalog[productId];\n      const nextTotal = otherTotal + this.getPaidDisplayPrice(product);\n      if (!productId || seen.has(productId) || shown.has(productId)) return;\n      if (!this.isReplacementProductAvailable(product)) return;\n      if (nextTotal < band.min || nextTotal > replacementMax) return;\n      seen.add(productId);\n      options.push(candidate);\n    });\n\n    return options;\n  }\n\n  applyPackReplacement(position, replacement) {\n    const current = this._packWines?.[position];\n    if (!current || !replacement) return;\n    const roleKeys = [\n      '_calibrationRole', '_calibrationIndex', 'reason_title', 'reason_text',\n      '_isSimilar', '_similarTo', '_isBonus'\n    ];\n    const next = { ...replacement };\n    roleKeys.forEach(key => {\n      if (Object.prototype.hasOwnProperty.call(current, key)) next[key] = current[key];\n      else delete next[key];\n    });\n    this._packWines[position] = next;\n  }\n\n  renderPackReplacementState(cardsEl, isEN) {\n    const listEl = cardsEl?.querySelector('.wr-bundle-items');\n    if (!listEl) return;\n    const catalog = window.shopCatalog || {};\n    const html = this.buildBundleItemCardsHTML(this._packWines || [], isEN);\n    const insertionPoint = listEl.querySelector('.wr-bundle__retake-panel--mobile, .wr-bundle__tweak-panel--mobile');\n    listEl.querySelectorAll('.wr-bundle-item:not(.wr-bundle-item--upsell)').forEach(item => item.remove());\n    if (insertionPoint) insertionPoint.insertAdjacentHTML('beforebegin', html);\n    else listEl.insertAdjacentHTML('beforeend', html);\n\n    const wineCents = (this._packWines || []).reduce(\n      (sum, wine) => sum + this.getPaidDisplayPrice(catalog[String(wine.wine_id)]),\n      0\n    );\n    if (this._bundleMath) {\n      this._bundleMath.wineCents = wineCents;\n      this._bundleMath.baseCount = (this._packWines || []).length;\n    }\n    this.renderUpsellState(cardsEl, isEN);\n  }\n\n  setupPackReplacement(cardsEl, isEN) {\n    if (!cardsEl || cardsEl.dataset.packReplacementDelegated) return;\n    cardsEl.dataset.packReplacementDelegated = '1';\n\n    cardsEl.addEventListener('click', event => {\n      const button = event.target.closest('[data-replace-pack]');\n      if (!button || !cardsEl.contains(button)) return;\n      event.preventDefault();\n      event.stopImmediatePropagation();\n\n      const catalog = window.shopCatalog || {};\n      if (button.hasAttribute('data-replace-pack')) {\n        const position = Number(button.getAttribute('data-replace-pack'));\n        const options = this.getPackReplacementOptions(position, catalog);\n        const currentId = String(this._packWines?.[position]?.wine_id || '');\n        const currentIndex = options.findIndex(wine => String(wine.wine_id) === currentId);\n        const replacement = options[(currentIndex + 1) % options.length];\n        if (!replacement || String(replacement.wine_id) === currentId) return;\n        this.applyPackReplacement(position, replacement);\n      }\n\n      const windowY = window.scrollY;\n      this.renderPackReplacementState(cardsEl, isEN);\n      if (window.scrollY !== windowY) window.scrollTo({ top: windowY, behavior: 'instant' });\n      const nextButton = cardsEl.querySelector(`[data-replace-pack=\"${button.getAttribute('data-replace-pack')}\"]`);\n      if (nextButton) nextButton.focus({ preventScroll: true });\n    });\n\n    cardsEl.addEventListener('keydown', event => {\n      if (event.key !== 'Enter' && event.key !== ' ') return;\n      if (event.target.closest('[data-replace-pack]')) {\n        event.stopImmediatePropagation();\n      }\n    });\n  }\n\n  buildBundleItemCardsHTML(packWines, isEN) {\n    const catalog = window.shopCatalog || {};\n    const savedProfile = (() => {\n      try { return JSON.parse(sessionStorage.getItem('vinpop_taste_profile') || '{}'); } catch(e) { return {}; }\n    })();\n    const getMolecularText = (profile, wineIdx, isENLang) => {\n      const tannin = profile.tanninTolerance || 'medium';\n      const acidity = profile.acidityTolerance || 'medium';\n      const sweetness = profile.sweetnessPreference || 'balanced';\n      const complexity = profile.complexityPreference || 'medium';\n      const mood = profile.moodPreference || 'smooth';\n      const en = [\n        tannin === 'high' ? 'You drink it black. Same bold intensity as your espresso.' : tannin === 'low' ? 'You prefer cappuccino or tea. No harsh tannins here.' : 'Your bitterness threshold guided this pick.',\n        acidity === 'high' ? 'You chose lemon vinaigrette. This wine has that same crisp bite.' : acidity === 'low' ? 'You went for creamy dressing. This wine is round, no sharp edges.' : 'You picked olive oil & salt. Clean and balanced.',\n        sweetness === 'dry' ? 'You chose dark chocolate 70%+. Same dry, structured finish.' : sweetness === 'sweet' ? 'You prefer milk chocolate. This wine is soft and fruity.' : 'You picked fruit jellies. Same bright, fruity character.',\n        complexity === 'complex' ? 'You chose aged Gouda. This wine has the same layered depth.' : complexity === 'simple' ? 'You chose Brie. Light, creamy, no rough edges.' : 'Your cheese choice points to a balanced complexity.',\n        mood === 'bold' ? 'You asked for Bold & Powerful. This wine delivers exactly that.' : mood === 'fresh' ? 'You wanted Fresh & Fruity. This is exactly it.' : 'You picked Smooth & Round. That\\'s precisely what this is.',\n      ];\n      const nl = [\n        tannin === 'high' ? 'Jij drinkt het puur zwart. Dezelfde krachtige intensiteit als jouw espresso.' : tannin === 'low' ? 'Jij prefereert cappuccino of thee. Geen zware tannines hier.' : 'Jouw bitterheidsdrempel stuurde deze keuze.',\n        acidity === 'high' ? 'Jij koos citroendressing. Deze wijn heeft dezelfde frisse bite.' : acidity === 'low' ? 'Jij ging voor romige dressing. Ronde wijn, geen scherpe kanten.' : 'Jij koos olijfolie & zout. Clean en uitgebalanceerd.',\n        sweetness === 'dry' ? 'Jij koos pure chocolade 70%+. Dezelfde droge, gestructureerde afdronk.' : sweetness === 'sweet' ? 'Jij houdt van melkchocolade. Deze wijn is zacht en fruitig.' : 'Jij koos fruitgummies. Hetzelfde levendige, fruitige karakter.',\n        complexity === 'complex' ? 'Jij koos oude Gouda. Deze wijn heeft dezelfde gelaagde diepte.' : complexity === 'simple' ? 'Jij koos Brie. Licht, romig, geen ruwe randjes.' : 'Jouw kaaskeuze wijst op een uitgebalanceerde complexiteit.',\n        mood === 'bold' ? 'Jij vroeg om Krachtig & Koel. Deze wijn levert dat precies.' : mood === 'fresh' ? 'Jij wilde Fris & Fruitig. Dit is precies dat.' : 'Jij koos Zacht & Rond. Dat is precies wat dit is.',\n      ];\n      const pool = isENLang ? en : nl;\n      return pool[wineIdx % pool.length];\n    };\n    const escAttr = (s) => String(s || '').replace(/\"/g, '&quot;');\n    const escHTML = (s) => String(s || '')\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#39;');\n    const topMatches = packWines.filter(w => !w._isSimilar && !w._isBonus).slice(0, 2);\n    const topMatchNumberById = {\n      [String(topMatches[0]?.wine_id || '')]: 1,\n      [String(topMatches[1]?.wine_id || '')]: 2\n    };\n\n    return packWines.map((wine, i) => {\n      const p = catalog[String(wine.wine_id)];\n      if (!p) return '';\n      const imgUrl = p.image && p.image.url ? p.image.url : '';\n      const productUrl = p.url || '';\n      const displayPrice = this.getPaidDisplayPrice(p);\n      let typeLabel, typeClass, openLabel, molecularText;\n      const molecularTitle = isEN ? '\uD83D\uDD2C MOLECULAR PLAYLIST MATCH' : '\uD83D\uDD2C MOLECULAIRE PLAYLIST MATCH';\n      if (wine._calibrationRole) {\n        typeClass = `wr-bundle-item--calibration wr-bundle-item--calibration-${wine._calibrationIndex}`;\n        typeLabel = isEN ? `\uD83C\uDF77 Bottle ${wine._calibrationIndex}: ${wine.reason_title}` : `\uD83C\uDF77 Fles ${wine._calibrationIndex}: ${wine.reason_title}`;\n        openLabel = isEN ? 'Open details' : 'Details openen';\n        molecularText = wine.reason_text || getMolecularText(savedProfile, i, isEN);\n      } else if (wine._isSimilar) {\n        typeClass = 'wr-bundle-item--similar';\n        const pct = wine.score || 90;\n        const linkedMatchNo = topMatchNumberById[String(wine._similarTo)] || 1;\n        const linkedMatchText = getMolecularText(savedProfile, linkedMatchNo - 1, isEN);\n        typeLabel = isEN ? `\uD83C\uDFAF Surprise Match: ${pct}% taste similarity with #match ${linkedMatchNo}.` : `\uD83C\uDFAF Surprise Match: ${pct}% smaakovereenkomst met #match ${linkedMatchNo}.`;\n        openLabel = isEN ? 'Linked to your top match.' : 'Gekoppeld aan jouw topmatch.';\n        molecularText = isEN ? `${linkedMatchText} This extra bottle was added because it tastes very close to that match.` : `${linkedMatchText} Deze extra fles is toegevoegd omdat hij qua smaak heel dicht bij die match ligt.`;\n        molecularText = this.buildDynamicWineMatchText(wine, molecularText, isEN);\n      } else if (wine._isBonus) {\n        typeClass = 'wr-bundle-item--bonus';\n        typeLabel = isEN ? '\uD83C\uDF81 Bonus: Included to sharpen your taste profile.' : '\uD83C\uDF81 Bonus: Inbegrepen om je smaakprofiel scherper te maken.';\n        openLabel = isEN ? 'Open details' : 'Details openen';\n        molecularText = getMolecularText(savedProfile, i, isEN);\n        molecularText = this.buildDynamicWineMatchText(wine, molecularText, isEN);\n      } else {\n        typeClass = '';\n        typeLabel = isEN ? `\uD83C\uDF77 Quiz Match #${i + 1}` : `\uD83C\uDF77 Quizmatch #${i + 1}`;\n        openLabel = isEN ? 'Open details' : 'Details openen';\n        molecularText = getMolecularText(savedProfile, i, isEN);\n        molecularText = this.buildDynamicWineMatchText(wine, molecularText, isEN);\n      }\n      const axisAttrs = [\n        Number.isFinite(Number(wine._barWineX)) ? `data-axis-x=\"${Math.round(Number(wine._barWineX))}\"` : '',\n        Number.isFinite(Number(wine._barWineY)) ? `data-axis-y=\"${Math.round(Number(wine._barWineY))}\"` : '',\n        Number.isFinite(Number(wine._barWineZ)) ? `data-axis-z=\"${Math.round(Number(wine._barWineZ))}\"` : '',\n      ].filter(Boolean).join(' ');\n      const canReplace = this.getPackReplacementOptions(i, catalog).length > 1;\n      return `\n        <div class=\"wr-bundle-item ${typeClass}\" role=\"button\" tabindex=\"0\" aria-label=\"${escAttr(openLabel)}\" data-wine-id=\"${wine.wine_id}\" data-product-url=\"${escAttr(productUrl)}\" data-display-price=\"${displayPrice}\" data-type-label=\"${escAttr(typeLabel)}\" data-molecular-title=\"${escAttr(molecularTitle)}\" data-molecular-text=\"${escAttr(molecularText)}\" ${axisAttrs}>\n          <div class=\"wr-bundle-item__img-frame\">\n            <img src=\"${imgUrl}\" alt=\"${escAttr(p.title)}\" class=\"wr-bundle-item__img\">\n          </div>\n          <div class=\"wr-bundle-item__info\">\n            <div class=\"wr-bundle-item__type\">${escHTML(typeLabel)}</div>\n            <div class=\"wr-bundle-item__title\">${escHTML(p.title)}</div>\n            ${this.buildProvenanceHTML(p, isEN, escHTML)}\n            <div class=\"wr-bundle-item__why\">\n              <span>${isEN ? 'Why this wine' : 'Waarom deze wijn'}</span>\n              <p>${escHTML(molecularText)}</p>\n            </div>\n            <div class=\"wr-bundle-item__score-row\">\n              <span class=\"wr-bundle-item__open-label\">${escHTML(openLabel)}</span>\n              ${canReplace ? `<button type=\"button\" class=\"wr-bundle-item__replace\" data-replace-pack=\"${i}\">${isEN ? '\u21BB Replace wine' : '\u21BB Wijn vervangen'}</button>` : ''}\n            </div>\n          </div>\n        </div>\n      `;\n    }).join('');\n  }\n\n  async updateBundleItemsFromRecommendations(wines, options = {}) {\n    if (options.requestId && options.requestId !== this.axisUpdateRequestId) return;\n\n    const cardsEl = this.popup?.querySelector('.wine-reveal-cards') || document.querySelector('.wine-reveal-cards');\n    const listEl = cardsEl?.querySelector('.wr-bundle-items') || document.querySelector('.wr-bundle-items');\n    if (!listEl) return;\n\n    const isEN = (document.documentElement.lang || '').toLowerCase().slice(0, 2) !== 'nl';\n    const catalog = window.shopCatalog || {};\n    const isCatalogProductInStock = (product) => {\n      if (!product || product.available === false) return false;\n      const variants = Array.isArray(product.variants) ? product.variants : [];\n      if (!variants.length) return !!product.available;\n      // `available` stays true for a tracked variant that is set to \"continue\n      // selling when out of stock\" (backorder) even at 0 units. Treat a tracked\n      // variant with 0 or fewer units on hand as out of stock so it is never\n      // recommended, matching the merchant's real inventory. Untracked variants\n      // (inventoryManaged === false) keep relying on `available`.\n      return variants.some(v => {\n        if (!v || v.available === false) return false;\n        if (v.inventoryManaged && Number(v.inventoryQuantity) <= 0) return false;\n        return true;\n      });\n    };\n    // Same band as the first render, from the same helper, so a silent axis update can never\n    // re-price the kit against different rules.\n    const { min: MIN_PACK_PRICE, max: MAX_PACK_PRICE } = this.getPackPriceBand(catalog, isCatalogProductInStock);\n    const calibrationSelection = await this.buildCalibrationBottleSelection(wines, {\n      isEN,\n      catalog,\n      minPackPrice: MIN_PACK_PRICE,\n      maxPackPrice: MAX_PACK_PRICE,\n      maxPrice: MAX_PACK_PRICE,\n      isCatalogProductInStock,\n      getPaidDisplayPrice: (product) => this.getPaidDisplayPrice(product)\n    });\n    if (options.requestId && options.requestId !== this.axisUpdateRequestId) return;\n    const packWines = [...calibrationSelection.selectedWines];\n    if (packWines.length < 2) return;\n\n    this._fastBonusProductId = '';\n\n    this._packWines = packWines;\n    this._packReplacementCandidates = calibrationSelection.candidates || [];\n    sessionStorage.setItem('tasteProfileResults', JSON.stringify(wines));\n    sessionStorage.setItem('vinpop_taste_profile', JSON.stringify(this.buildTasteProfile()));\n    sessionStorage.setItem('vinpop_last_results', JSON.stringify(wines));\n\n    const html = this.buildBundleItemCardsHTML(packWines, isEN);\n    const mobilePanel = listEl.querySelector('.wr-bundle__retake-panel--mobile, .wr-bundle__tweak-panel--mobile');\n    const insertionPoint = mobilePanel;\n    listEl.querySelectorAll('.wr-bundle-item').forEach(item => item.remove());\n    if (insertionPoint) {\n      insertionPoint.insertAdjacentHTML('beforebegin', html);\n    } else {\n      listEl.insertAdjacentHTML('beforeend', html);\n    }\n\n    // The kit changed, so the next best matches changed with it. Rebuild the pool, then\n    // re-add the same products the customer had chosen when they are still on offer, each\n    // with a fresh block of alternatives.\n    const previouslyAdded = this.getAddedUpsellEntries().map(entry => entry.productId);\n    const pool = this.buildUpsellCandidates(calibrationSelection.candidates, packWines, catalog);\n    this._upsell = { pool, added: [] };\n    previouslyAdded.slice(0, this.upsellMaxBottles).forEach(productId => {\n      const poolIndex = pool.findIndex(entry => entry.productId === productId);\n      if (poolIndex < 0 || this.getDisplayedUpsellIndices().has(poolIndex)) return;\n      const block = this.takeUpsellOptionBlock();\n      if (!block) return;\n      // Keep the wine the customer picked at the front of its own option block.\n      const options = [poolIndex].concat(block.filter(index => index !== poolIndex));\n      this._upsell.added.push({ options: options.slice(0, this.upsellSwapOptions), optionIndex: 0 });\n    });\n    // The listeners are delegated on cardsEl and were bound on first render, so this only\n    // has to repaint. Binding again here would double every click.\n    this.renderUpsellState(cardsEl || document, isEN);\n  }\n\n  // ------------------------------------------------------------------\n  // Tweak panel: shown on the bundle page, lets user revise answers.\n  // Open by default. On change, re-runs the recommendation flow.\n  // ------------------------------------------------------------------\n  renderTweakPanel(mountEl, isEN) {\n    if (!mountEl) return;\n\n    const isMobilePanel = mountEl.classList.contains('wr-bundle__tweak-panel-mount--mobile');\n    const isMobileViewport = !!(window.matchMedia && window.matchMedia('(max-width: 719px)').matches);\n\n    // Keep a single retake panel in the DOM: mobile mount on mobile, desktop mount on desktop.\n    if ((isMobileViewport && !isMobilePanel) || (!isMobileViewport && isMobilePanel)) {\n      mountEl.remove();\n      return;\n    }\n\n    const title = isEN ? 'Want another colour?' : 'Liever een andere kleur?';\n    const cta = isEN ? 'Retake the quiz' : 'Quiz opnieuw doen';\n    const normalizeMixForBoth = (value) => {\n      const red = parseInt(value, 10);\n      return red >= 2 ? 2 : 1;\n    };\n\n    const activeMode = (this.colorPreference === 'red' || this.colorPreference === 'white')\n      ? this.colorPreference\n      : 'both';\n    const initialMix = this.colorPreference === 'mix'\n      ? this.normalizeColorMixPreference(normalizeMixForBoth(this.colorMixPreference && this.colorMixPreference.red))\n      : this.normalizeColorMixPreference(this.colorPreference === 'red' ? 2 : this.colorPreference === 'white' ? 1 : 2);\n\n    const colors = {\n      red: isEN ? 'Only red' : 'Alleen rood',\n      white: isEN ? 'Only white' : 'Alleen wit',\n      both: isEN ? 'Mix red + white' : 'Mix rood + wit'\n    };\n    const mixLabel = isEN ? 'How many of each?' : 'Hoeveel van elk?';\n    const mixHint = '';\n    const mixUnit = isEN ? 'red' : 'rood';\n    const mixUnitWhite = isEN ? 'white' : 'wit';\n    const applyLabel = isEN ? 'Updating your box...' : 'Je box wordt bijgewerkt...';\n    const alreadySelectedLabel = isEN\n      ? 'Already selected. This is your current box.'\n      : 'Al geselecteerd. Dit is je huidige box.';\n\n    const panel = document.createElement('div');\n    panel.className = 'wr-bundle__retake-panel';\n    if (isMobilePanel) {\n      panel.classList.add('wr-bundle__retake-panel--mobile');\n      if (isMobileViewport) {\n        panel.classList.add('wr-bundle__retake-panel--desktop');\n      }\n    } else {\n      panel.classList.add('wr-bundle__retake-panel--desktop');\n    }\n    panel.innerHTML =\n      `<div class=\"wr-tweak-panel__header\">` +\n      `<p class=\"wr-tweak-panel__label\">${title}</p>` +\n      `<button class=\"push-btn wr-tweak-panel__retake-btn\" type=\"button\"><span class=\"push-btn__surface wr-tweak-panel__retake-surface\">${cta}</span></button>` +\n      `</div>` +\n      `<div class=\"wr-bundle-color-control\" data-bundle-color-control>` +\n      `<div class=\"wr-bundle-color-control__choices\" role=\"tablist\" aria-label=\"${isEN ? 'Wine color preference' : 'Wijnkleur voorkeur'}\">` +\n      `<button class=\"wr-bundle-color-control__choice${activeMode === 'red' ? ' is-active' : ''}\" type=\"button\" data-color-mode=\"red\">${colors.red}</button>` +\n      `<button class=\"wr-bundle-color-control__choice${activeMode === 'white' ? ' is-active' : ''}\" type=\"button\" data-color-mode=\"white\">${colors.white}</button>` +\n      `<button class=\"wr-bundle-color-control__choice${activeMode === 'both' ? ' is-active' : ''}\" type=\"button\" data-color-mode=\"both\">${colors.both}</button>` +\n      `</div>` +\n      `<div class=\"wr-bundle-color-control__mix${activeMode === 'both' ? ' is-visible' : ''}\" data-bundle-mix-wrap>` +\n      `<p class=\"wr-bundle-color-control__mix-label\">${mixLabel}</p>` +\n      `<div class=\"wr-bundle-color-control__presets\">` +\n      `<button class=\"wr-bundle-color-control__preset${initialMix.red === 2 ? ' is-active' : ''}\" type=\"button\" data-mix-red=\"2\">2 ${mixUnit} + 1 ${mixUnitWhite}</button>` +\n      `<button class=\"wr-bundle-color-control__preset${initialMix.red === 1 ? ' is-active' : ''}\" type=\"button\" data-mix-red=\"1\">1 ${mixUnit} + 2 ${mixUnitWhite}</button>` +\n      `</div>` +\n      `<p class=\"wr-bundle-color-control__mix-value\" data-bundle-mix-value>${initialMix.red} ${mixUnit} + ${initialMix.white} ${mixUnitWhite}</p>` +\n      (mixHint ? `<p class=\"wr-bundle-color-control__hint\">${mixHint}</p>` : '') +\n      `</div>` +\n      `<p class=\"wr-bundle-color-control__feedback\" data-bundle-feedback role=\"status\" aria-live=\"polite\" hidden></p>` +\n      `</div>`;\n    mountEl.replaceWith(panel);\n\n    const colorButtons = Array.from(panel.querySelectorAll('[data-color-mode]'));\n    const mixWrap = panel.querySelector('[data-bundle-mix-wrap]');\n    const mixPresets = Array.from(panel.querySelectorAll('[data-mix-red]'));\n    const mixValue = panel.querySelector('[data-bundle-mix-value]');\n    const feedback = panel.querySelector('[data-bundle-feedback]');\n    let currentMode = activeMode;\n    let pendingMix = initialMix;\n    let colorUpdateBusy = false;\n    let feedbackTimer = null;\n\n    const hideFeedback = () => {\n      if (feedbackTimer) clearTimeout(feedbackTimer);\n      if (feedback) feedback.hidden = true;\n    };\n\n    const showAlreadySelected = (button) => {\n      if (!feedback) return;\n      feedback.textContent = alreadySelectedLabel;\n      feedback.hidden = false;\n      button.classList.remove('is-nudged');\n      void button.offsetWidth;\n      button.classList.add('is-nudged');\n      if (feedbackTimer) clearTimeout(feedbackTimer);\n      feedbackTimer = setTimeout(() => {\n        feedback.hidden = true;\n        button.classList.remove('is-nudged');\n      }, 2600);\n    };\n\n    const updateMixLabel = () => {\n      if (!mixValue) return;\n      mixValue.textContent = `${pendingMix.red} ${mixUnit} + ${pendingMix.white} ${mixUnitWhite}`;\n    };\n\n    const updateChoiceUI = () => {\n      colorButtons.forEach(button => {\n        const isActive = button.dataset.colorMode === currentMode;\n        button.classList.toggle('is-active', isActive);\n        button.setAttribute('aria-pressed', isActive ? 'true' : 'false');\n      });\n      mixPresets.forEach(button => {\n        const red = parseInt(button.dataset.mixRed, 10);\n        const isActive = pendingMix.red === red;\n        button.classList.toggle('is-active', isActive);\n        button.setAttribute('aria-pressed', isActive ? 'true' : 'false');\n      });\n      if (mixWrap) {\n        mixWrap.classList.toggle('is-visible', currentMode === 'both');\n      }\n      updateMixLabel();\n    };\n\n    const refreshRecommendations = async () => {\n      if (colorUpdateBusy) return;\n      colorUpdateBusy = true;\n      panel.classList.add('is-updating');\n      const labelEl = panel.querySelector('.wr-tweak-panel__label');\n      const previousLabel = labelEl ? labelEl.textContent : '';\n      if (labelEl) labelEl.textContent = applyLabel;\n      try {\n        await this.fetchResults({ silentUpdate: true });\n      } finally {\n        colorUpdateBusy = false;\n        panel.classList.remove('is-updating');\n        if (labelEl) labelEl.textContent = previousLabel || title;\n      }\n    };\n\n    const applyMode = async (nextMode, shouldRefresh = true) => {\n      currentMode = nextMode;\n      if (nextMode === 'red' || nextMode === 'white') {\n        this.colorPreference = nextMode;\n        this.colorMixPreference = null;\n      } else {\n        this.colorPreference = 'mix';\n        pendingMix = this.normalizeColorMixPreference(normalizeMixForBoth(pendingMix.red));\n        this.colorMixPreference = { ...pendingMix };\n      }\n      updateChoiceUI();\n      if (shouldRefresh) {\n        await refreshRecommendations();\n      }\n    };\n\n    colorButtons.forEach(button => {\n      button.addEventListener('click', () => {\n        const nextMode = button.dataset.colorMode;\n        if (!nextMode) return;\n        if (nextMode === currentMode) {\n          showAlreadySelected(button);\n          return;\n        }\n        hideFeedback();\n        applyMode(nextMode, true);\n      });\n    });\n\n    mixPresets.forEach(button => {\n      button.addEventListener('click', () => {\n        const redCount = normalizeMixForBoth(button.dataset.mixRed);\n        if (redCount === pendingMix.red) {\n          showAlreadySelected(button);\n          return;\n        }\n        hideFeedback();\n        pendingMix = this.normalizeColorMixPreference(redCount);\n        if (currentMode === 'both') {\n          this.colorPreference = 'mix';\n          this.colorMixPreference = { ...pendingMix };\n          updateChoiceUI();\n          refreshRecommendations();\n        } else {\n          updateChoiceUI();\n        }\n      });\n    });\n\n    updateChoiceUI();\n\n    panel.querySelector('.wr-tweak-panel__retake-btn')?.addEventListener('click', () => {\n      this.setForceRetake(true);\n      localStorage.removeItem('vinpop-quiz-completed');\n      sessionStorage.removeItem('vinpop-quiz-completed');\n      sessionStorage.removeItem('vinpop_last_results');\n      sessionStorage.removeItem('vinpop_taste_profile');\n      sessionStorage.removeItem('tasteProfileResults');\n      sessionStorage.removeItem('vinpop_quiz_state');\n      this.quizSavePromise = null;\n      this.lastQuizSaveSignature = '';\n      this._reloadToQuizTop();\n    });\n  }\n\n  async debugCalibrationRecommendation(wines = this.recommendedWines || []) {\n    const catalog = window.shopCatalog || {};\n    const isCatalogProductInStock = (product) => {\n      if (!product || product.available === false) return false;\n      const variants = Array.isArray(product.variants) ? product.variants : [];\n      if (!variants.length) return !!product.available;\n      // `available` stays true for a tracked variant that is set to \"continue\n      // selling when out of stock\" (backorder) even at 0 units. Treat a tracked\n      // variant with 0 or fewer units on hand as out of stock so it is never\n      // recommended, matching the merchant's real inventory. Untracked variants\n      // (inventoryManaged === false) keep relying on `available`.\n      return variants.some(v => {\n        if (!v || v.available === false) return false;\n        if (v.inventoryManaged && Number(v.inventoryQuantity) <= 0) return false;\n        return true;\n      });\n    };\n    const selection = await this.buildCalibrationBottleSelection(wines, {\n      catalog,\n      maxPrice: 1400,\n      isCatalogProductInStock,\n      getPaidDisplayPrice: (product) => this.getPaidDisplayPrice(product)\n    });\n    const selected = selection.selectedWines;\n    const ids = selected.map(wine => String(wine.wine_id));\n    const unique = ids.length === new Set(ids).size;\n    const bottleB = selected[1];\n    const bottleC = selected[2];\n    const knownDistanceMap = await this.getPerceptiveDistanceMap('15513163760003');\n    const knownDistance = knownDistanceMap.get('15988234125699');\n    const report = {\n      unique_product_ids: unique,\n      selected_ids: ids,\n      distance_1_2: bottleB?.distance_to_bottle_1,\n      distance_1_3: bottleC?.distance_to_bottle_1,\n      distance_2_3: bottleC?.distance_to_bottle_2,\n      pairwise_target_12_18:\n        Number(bottleB?.distance_to_bottle_1) >= 12 && Number(bottleB?.distance_to_bottle_1) <= 18 &&\n        Number(bottleC?.distance_to_bottle_1) >= 12 && Number(bottleC?.distance_to_bottle_1) <= 18 &&\n        Number(bottleC?.distance_to_bottle_2) >= 12 && Number(bottleC?.distance_to_bottle_2) <= 18,\n      free_bottle_is_separate: !ids.includes(this.colorPreference === 'white' ? '15513213370755' : '15510612148611'),\n      known_distance_15513163760003_to_15988234125699: knownDistance,\n      known_distance_matches_expected_8: knownDistance === 8\n    };\n    console.table(report);\n    return { report, selection };\n  }\n}\n\n\n// Create instance\nif (document.readyState === 'loading') {\n  document.addEventListener('DOMContentLoaded', function() {\n    window.tasteProfileQuiz = new TasteProfileQuiz();\n    applyTasteProfileFilter();\n  });\n} else {\n  window.tasteProfileQuiz = new TasteProfileQuiz();\n  applyTasteProfileFilter();\n}\n\n// Filter products based on taste profile\nfunction applyTasteProfileFilter() {\n  // Si le nouveau syst\u00E8me (wine-recommendation-manager.js) est actif, il g\u00E8re le rendu\n  // shopCatalog est d\u00E9fini par wine-catalog-data.liquid (script inline, charg\u00E9 avant les scripts defer)\n  //\n  // On teste le descripteur et non la valeur : depuis que le catalogue est servi en JSON\n  // inerte, lire window.shopCatalog d\u00E9clenche le JSON.parse des 82 Ko. Or ici on ne veut\n  // que savoir si le global existe, on ne lit jamais la donn\u00E9e avant de sortir. Passer par\n  // getOwnPropertyDescriptor r\u00E9pond \u00E0 la m\u00EAme question sans r\u00E9veiller l'accesseur, que le\n  // global soit pos\u00E9 en accesseur paresseux ou en propri\u00E9t\u00E9 simple.\n  if (Object.getOwnPropertyDescriptor(window, 'shopCatalog')) {\n    console.log('\uD83C\uDF77 applyTasteProfileFilter: Skipping - new recommendation manager handles rendering');\n    return;\n  }\n  \n  var urlParams = new URLSearchParams(window.location.search);\n  if (urlParams.get('taste_profile') !== 'true') return;\n  \n  var resultsJson = sessionStorage.getItem('tasteProfileResults');\n  if (!resultsJson) return;\n  \n  var wines = JSON.parse(resultsJson);\n  var wineMap = {};\n  for (var i = 0; i < wines.length; i++) {\n    wineMap[wines[i].wine_id] = wines[i].score;\n  }\n  \n  var productGrid = document.querySelector('#facets-results ul.grid, #facets-results ul');\n  if (!productGrid) return;\n  \n  var allProducts = productGrid.querySelectorAll('li[data-product-id]');\n  \n  for (var j = 0; j < allProducts.length; j++) {\n    var productLi = allProducts[j];\n    var productId = productLi.getAttribute('data-product-id');\n    var score = wineMap[productId];\n    \n    if (!score || score < 80) {\n      productLi.remove();\n      continue;\n    }\n    \n    var compatText = score >= 90 ? \n      (window.themeStrings && window.themeStrings.taste_profile ? window.themeStrings.taste_profile.love_it : 'You will love it') :\n      (window.themeStrings && window.themeStrings.taste_profile ? window.themeStrings.taste_profile.like_it : 'You will like it');\n    \n    var existingBadge = productLi.querySelector('.taste-score-badge');\n    if (existingBadge) existingBadge.remove();\n    \n    var productTile = productLi.querySelector('.product-tile');\n    if (productTile) {\n      var badgeId = 'taste-score-' + productId;\n      var rotate = Math.random() > 0.5 ? -5 : 5;\n      \n      var badge = document.createElement('div');\n      badge.className = 'sticker-' + badgeId + ' pointer-events-none absolute z-20 origin-top-left will-change-transform';\n      badge.style.cssText = 'top:15%;right:10%;transform:rotate(' + rotate + 'deg) translate(50%,-50%);';\n      \n      badge.innerHTML = '<div class=\"sticker__container rotate-anim relative\">' +\n        '<div class=\"relative max-w-none\">' +\n        '<div class=\"absolute inset-0 bg-scheme-accent-1 text-scheme-accent-1-contrast\" style=\"clip-path:url(#clip-sold-out)\">' +\n        '<div class=\"sticker__text sticker-text-sold-out absolute inset-0 flex items-center justify-center text-center font-heading\" style=\"padding:2rem;\">' +\n        '<div>' + score + '%</div>' +\n        '</div></div>' +\n        '<svg class=\"sticker__width-container relative max-w-none text-scheme-accent-1-contrast\" viewBox=\"0 0 200 200\" xmlns=\"http://www.w3.org/2000/svg\" style=\"width:75px;\">' +\n        '<use class=\"sticker__svg fill-transparent\" href=\"#outline-sold-out\" />' +\n        '</svg></div></div>';\n      \n      productTile.style.position = 'relative';\n      productTile.insertBefore(badge, productTile.firstChild);\n    }\n    \n    // \uD83D\uDD27 SUPPRIM\u00C9: Ne plus afficher le texte \"You will love it\"\n    // Les utilisateurs voient d\u00E9j\u00E0 le badge avec le score %\n    var existingText = productLi.querySelector('.taste-compat-text');\n    if (existingText) existingText.remove();\n    \n    productLi.setAttribute('data-taste-score', score);\n  }\n  \n  var remaining = productGrid.querySelectorAll('li[data-product-id]');\n  var productsArray = Array.from(remaining);\n  productsArray.sort(function(a, b) {\n    var scoreA = parseInt(a.getAttribute('data-taste-score')) || 0;\n    var scoreB = parseInt(b.getAttribute('data-taste-score')) || 0;\n    return scoreB - scoreA;\n  });\n  for (var k = 0; k < productsArray.length; k++) {\n    productGrid.appendChild(productsArray[k]);\n  }\n}\n\ndocument.addEventListener('shopify:section:load', function() {\n  var urlParams = new URLSearchParams(window.location.search);\n  if (urlParams.get('taste_profile') === 'true') {\n    setTimeout(applyTasteProfileFilter, 300);\n  }\n});\n"],
  "mappings": "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",
  "names": ["selected", "distanceFromA", "window", "distanceFromB", "profile", "isEN", "labels", "options"]
}
