{
  "version": 3,
  "sources": ["/cdn/shop/t/3/assets/pvi-product.js"],
  "sourcesContent": ["/* PVI Product Page JavaScript\n   Lightbox, Sticky Bar, Video Embed, AI Chatbot\n   Loaded defer \u2014 runs after DOM ready */\n\n(function() {\n  'use strict';\n\n  var API_BASE = 'https://pvi-ai-assistant.silent-voice-b4f1.workers.dev';\n  var product = window.__pviProduct || {};\n  // Page context: prefer what the Liquid gives us, but if it's missing (e.g. the\n  // theme didn't inject __pviPageContext), derive it from the URL so the greeting\n  // always follows the page \u2014 especially collection pages.\n  function pviHumanize(h) {\n    var s = String(h || '').replace(/[-_]+/g, ' ').trim();\n    s = s.replace(/\\b([a-z])/g, function(m, c) { return c.toUpperCase(); });\n    // Fix common acronyms so the AI doesn't say \"Iptv\" / \"Rf\"\n    s = s.replace(/\\b(Iptv|Rf|Hdmi|Sdi|Av|Hd|Ndi|Srt|Catv|Qam|Atsc|Dvbt|Dvbc|Isdbt|Vumatrix|Infinium|Vecoax|Vecaster|Vecoder|Veplayer)\\b/g, function(w) {\n      var map = { Iptv: 'IPTV', Rf: 'RF', Hdmi: 'HDMI', Sdi: 'SDI', Av: 'AV', Hd: 'HD', Ndi: 'NDI', Srt: 'SRT', Catv: 'CATV', Qam: 'QAM', Atsc: 'ATSC', Dvbt: 'DVB-T', Dvbc: 'DVB-C', Isdbt: 'ISDB-T', Vumatrix: 'VuMATRIX', Infinium: 'INFINIUM', Vecoax: 'VeCOAX', Vecaster: 'VeCASTER', Vecoder: 'VeCODER', Veplayer: 'VePLAYER' };\n      return map[w] || w;\n    });\n    s = s.replace(/\\b4k\\b/gi, '4K');\n    return s;\n  }\n  function pviCollectionName(handle) {\n    var map = {\n      'rf-modulators-catv-hdmi': 'RF Modulators', 'rf-modulators': 'RF Modulators',\n      'hdmi-rf-modulators': 'RF Modulators', 'modulators': 'RF Modulators',\n      'iptv-encoders': 'IPTV Encoders', 'encoders': 'IPTV Encoders',\n      'iptv-decoders': 'IPTV Decoders', 'decoders': 'IPTV Decoders',\n      'vumatrix-4k-video-wall-kits': 'VuMATRIX Video Wall Kits',\n      'vumatrix': 'VuMATRIX HDMI-over-IP', 'hdmi-over-ip': 'VuMATRIX HDMI-over-IP',\n      'av-over-ip': 'VuMATRIX HDMI-over-IP',\n      '4k-over-everything-iptv-rf': 'INFINIUM 4K System', 'infinium': 'INFINIUM Modular System'\n    };\n    return map[handle] || pviHumanize(handle);\n  }\n  function pviResolvePageContext() {\n    var ctx = window.__pviPageContext || {};\n    if (ctx && ctx.page_type) return ctx; // trust the Liquid if it provided it\n    var path = (location.pathname || '').toLowerCase();\n    path = path.replace(/^\\/[a-z]{2}(-[a-z]{2})?(?=\\/|$)/, ''); // strip locale prefix\n    var out = { page_type: '', page_title: '', collection_handle: '', page_handle: '' };\n    var m;\n    if ((m = path.match(/\\/products\\/([^\\/?#]+)/))) {\n      out.page_type = 'product'; out.page_handle = m[1];\n    } else if ((m = path.match(/\\/collections\\/([^\\/?#]+)/))) {\n      out.page_type = 'collection'; out.collection_handle = m[1];\n      out.page_title = pviCollectionName(m[1]);\n    } else if ((m = path.match(/\\/pages\\/([^\\/?#]+)/))) {\n      out.page_type = 'page'; out.page_handle = m[1]; out.page_title = pviHumanize(m[1]);\n    } else if (path === '' || path === '/') {\n      out.page_type = 'index';\n    }\n    // Let any non-empty Liquid values win over the URL-derived ones\n    if (ctx) {\n      out.page_title = ctx.page_title || out.page_title;\n      out.collection_handle = ctx.collection_handle || out.collection_handle;\n      out.page_handle = ctx.page_handle || out.page_handle;\n    }\n    return out;\n  }\n  var pageCtx = pviResolvePageContext();\n  // Fallback: if on a product page but Liquid didn't inject __pviProduct,\n  // derive handle and title from the URL so the AI still knows the page\n  if (!product.handle || !product.title) {\n    var _pm = (location.pathname || '').match(/\\/products\\/([^\\/?#]+)/);\n    if (_pm) {\n      if (!product.handle) product.handle = _pm[1];\n      if (!product.title) product.title = pviHumanize(_pm[1]);\n    }\n  }\n  // Session continuity: persist across page navigation so the chat remembers\n  // the whole customer visit (all questions/answers). Uses localStorage so it\n  // works across tabs too, with a 3-hour sliding expiry so a later visit is fresh.\n  var PVI_SESSION_KEY = 'pvi-chat-session';\n  var PVI_SESSION_TTL = 3 * 60 * 60 * 1000; // 3 hours\n  var pviRestored = (function() {\n    try {\n      var raw = localStorage.getItem(PVI_SESSION_KEY);\n      if (!raw) return null;\n      var obj = JSON.parse(raw);\n      if (!obj || !obj.ts || (Date.now() - obj.ts) > PVI_SESSION_TTL) {\n        localStorage.removeItem(PVI_SESSION_KEY);\n        return null;\n      }\n      return obj;\n    } catch (_) { return null; }\n  })();\n  var sessionId = (pviRestored && pviRestored.id) || ('pvi-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8));\n  var chatHistory = (pviRestored && pviRestored.history && pviRestored.history.length) ? pviRestored.history : [];\n  // Mode tracking: 'info' or 'support' once chosen, null if not yet asked\n  var chosenPersona = (pviRestored && pviRestored.chosenPersona) || null;\n  // UNIFIED DEPARTMENTS (Jul 30 2026) \u2014 the web now mirrors the phone office:\n  // Dana (reception) answers NEUTRAL pages and routes; Lisa (sales), John\n  // (tech support), Mary (customer service) take over via mode_update events.\n  // pviActivePersona drives the chat name labels and web-voice persona restarts.\n  var PVI_PERSONA_LABELS = { reception: 'Dana \\u00b7 Reception', sales: 'Lisa \\u00b7 Sales', support: 'John \\u00b7 Tech Support', service: 'Mary \\u00b7 Customer Service' };\n  var pviLastLabeledPersona = null;\n  var pviActivePersona = (pviRestored && pviRestored.activePersona) || null;\n  // Dana is PHONE-ONLY (Jul 30 2026): sessions saved before the change may\n  // still carry 'reception' \u2014 remap so Lisa answers instead.\n  if (pviActivePersona === 'reception') pviActivePersona = null;\n  // One-shot department hint from a quick-action chip click (Aug 5 2026):\n  // set by the chip handler, sent with exactly one /api/chat request.\n  var pviChipPersonaHint = '';\n  var supportProduct = (pviRestored && pviRestored.supportProduct) || null;\n  // When a TEXT mode switch navigates to another page, remember it so the\n  // destination page can post a product-confirming greeting + reopen the panel\n  // (otherwise the reload drops the conversation into silence). Consumed once.\n  var pviPendingSwitch = (pviRestored && pviRestored.pendingSwitch) || null;\n  var hasInteracted = !!(pviRestored && pviRestored.history && pviRestored.history.length > 0);\n  // Link dedupe \u2014 CONSECUTIVE ONLY. A link is skipped ONLY if it was in the\n  // MOST RECENT link print. Printing a DIFFERENT link clears the block, so\n  // the same link can be printed again later. Exactly:\n  //   print #1 \u2192 OK \u00B7 print #1 \u2192 skip \u00B7 print #2 \u2192 OK \u00B7 print #1 \u2192 OK\n  //   print #1 \u2192 skip \u00B7 print #2 \u2192 OK \u00B7 print #2 \u2192 skip\n  // Persisted across page navigations (same session).\n  var pviShownLinks = (pviRestored && pviRestored.shownLinks) || {};\n  function pviLinkShown(url) { return !!pviShownLinks[url]; }\n  function pviMarkLinkBatch(urls) {\n    pviShownLinks = {};\n    for (var i = 0; i < urls.length; i++) pviShownLinks[urls[i]] = 1;\n    pviSaveSession();\n  }\n  function pviMarkLinkShown(url) { pviMarkLinkBatch([url]); }\n  function pviSaveSession() {\n    try {\n      var panel = document.getElementById('pviChatPanel');\n      localStorage.setItem(PVI_SESSION_KEY, JSON.stringify({\n        id: sessionId,\n        history: chatHistory.slice(-40),\n        open: (panel && panel.classList.contains('open')) ? 1 : 0,\n        mode: currentMode,\n        chosenPersona: chosenPersona,\n        activePersona: pviActivePersona,\n        supportProduct: supportProduct,\n        pendingSwitch: pviPendingSwitch,\n        shownLinks: pviShownLinks,\n        ts: Date.now()\n      }));\n    } catch (_) {}\n  }\n  function pviEscapeHtml(x) { return String(x).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // Universal form submission \u2014 used for quotes, callbacks, support tickets, etc.\n  // Sends to: (1) worker /api/lead for KV backup\n  //           (2) hidden Shopify page form for email delivery to sales@\n  // The Shopify form lives on a standalone hidden page (not in any menu).\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  function pviSubmitForm(data) {\n    var payload = {\n      type: data.type || 'quote_request',\n      name: data.name || '',\n      email: data.email || '',\n      phone: data.phone || '',\n      product: data.product || (product && product.title) || '',\n      quantity: data.quantity || '',\n      message: data.message || '',\n      source: currentMode === 'voice' ? 'voice_chat' : 'text_chat',\n      page_url: window.location.pathname\n    };\n\n    // 1) Send to worker for KV backup\n    fetch(API_BASE + '/api/lead', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(payload)\n    }).then(function(r) {\n      console.log('[PVI Form] Worker lead saved:', r.ok);\n    }).catch(function(e) {\n      console.error('[PVI Form] Worker lead error:', e);\n    });\n\n    // Email is sent by the worker via Web3Forms \u2014 no frontend form needed.\n  }\n\n  // Listen for lead data from the AI's streamed response (hidden tag extraction)\n  function pviCheckForLeadTag(fullText) {\n    var match = fullText.match(/<!--PVI_LEAD:(.*?)-->/);\n    if (!match) return;\n    try {\n      var data = JSON.parse(match[1]);\n      pviSubmitForm(data);\n    } catch (_) {}\n  }\n\n  // Voice quote confirmation \u2014 extract lead from conversation history when the AI\n  // confirms sending a quote (voice can't emit hidden HTML tags, so we parse the chat)\n  var pviVoiceQuoteSubmitted = false;\n\n  // Convert spoken email (\"john at gmail dot com\") to standard format\n  function pviNormalizeSpokenEmail(text) {\n    var t = text.toLowerCase()\n      .replace(/\\s+dot\\s+/gi, '.')\n      .replace(/\\s+at\\s+/gi, '@')\n      .replace(/\\s+underscore\\s+/gi, '_')\n      .replace(/\\s+dash\\s+/gi, '-')\n      .replace(/\\s+hyphen\\s+/gi, '-');\n    var em = t.match(/[\\w.+-]+@[\\w.-]+\\.\\w{2,}/);\n    return em ? em[0] : null;\n  }\n\n  // Extract lead info (name, email, phone, product) from conversation history\n  function pviExtractLeadFromHistory() {\n    var name = '', email = '', phone = '', qty = '', prod = '';\n    for (var i = chatHistory.length - 1; i >= 0; i--) {\n      var msg = chatHistory[i].content || '';\n      if (!email) {\n        var em = msg.match(/[\\w.+-]+@[\\w.-]+\\.\\w{2,}/);\n        if (em) { email = em[0]; }\n        else {\n          var spoken = pviNormalizeSpokenEmail(msg);\n          if (spoken) email = spoken;\n        }\n      }\n      if (!phone) {\n        var ph = msg.match(/\\b(\\+?\\d[\\d\\s\\-().]{7,}\\d)\\b/);\n        if (ph) phone = ph[1];\n      }\n      if (!name) {\n        var nameMatch = msg.match(/(?:(?:my name is|i'm|i am|this is|name:?|call me)\\s+)([A-Z][a-z]+(?:\\s+[A-Z][a-z]+)?)/i);\n        if (nameMatch) { name = nameMatch[1]; }\n        else if (chatHistory[i].role === 'assistant') {\n          var aiName = msg.match(/(?:for|to)\\s+([A-Z][a-z]+(?:\\s+[A-Z][a-z]+)?)\\s+(?:at|@)/i);\n          if (aiName) name = aiName[1];\n        }\n      }\n      if (!qty) {\n        var qm = msg.match(/(\\d+)\\s*(?:unit|piece|qty|quantity|of them|of those|modules?|modulators?|encoders?|channels?)/i);\n        if (qm) qty = qm[1];\n      }\n    }\n    prod = (product && product.title) || '';\n    return { name: name, email: email, phone: phone, quantity: qty, product: prod };\n  }\n\n  function pviVoiceCheckQuoteConfirm(aiText) {\n    if (pviVoiceQuoteSubmitted) return;\n    var lower = aiText.toLowerCase();\n\n    // Detect quote confirmation\n    var isQuoteConfirm = /(\\bsend\\b.*\\bquote\\b|\\bquote\\b.*\\bshortly\\b|\\bquote\\b.*\\bsent\\b|\\bquote\\b.*\\bsubmit|\\bsales team\\b.*\\bget back|\\bsales team\\b.*\\bsend|\\bperfect.*\\bquote\\b|\\bwe('|.wi)ll send|\\bsend (you|it) (over|right|shortly)|got (it|that).*\\bquote\\b)/i.test(lower);\n\n    // Detect support ticket confirmation\n    var isTicketConfirm = /(\\bticket\\b.*\\breceived\\b|\\bticket\\b.*\\bsubmit|\\bticket\\b.*\\bopen|\\bopened.*\\bticket\\b|\\bticket\\b.*\\bcreated|\\bcontact you\\b.*\\bsoon\\b.*\\bpossible\\b|\\blabs are open\\b|\\bengineers?\\b.*\\bget back\\b|\\bengineers?\\b.*\\brespond\\b|\\burgency\\b)/i.test(lower);\n\n    if (!isQuoteConfirm && !isTicketConfirm) return;\n\n    var info = pviExtractLeadFromHistory();\n\n    if (!info.email) {\n      console.log('[PVI Voice] Confirmation detected but no email found in conversation');\n      return;\n    }\n\n    pviVoiceQuoteSubmitted = true;\n\n    if (isTicketConfirm) {\n      // Build detailed message from recent conversation\n      var issueDetails = '';\n      for (var i = Math.max(0, chatHistory.length - 10); i < chatHistory.length; i++) {\n        if (chatHistory[i].role === 'user') {\n          issueDetails += chatHistory[i].content + ' | ';\n        }\n      }\n      pviSubmitForm({\n        type: 'support_ticket',\n        name: info.name,\n        email: info.email,\n        phone: info.phone,\n        product: info.product,\n        quantity: '',\n        message: 'Voice support ticket. Customer issue: ' + issueDetails.substring(0, 500)\n      });\n      console.log('[PVI Voice] Support ticket auto-submitted for', info.email);\n    } else {\n      var aiRecap = aiText.match(/(\\d+)\\s*(?:unit|\u00D7|x)\\s+(\\w[\\w\\s-]+)/i);\n      if (aiRecap) {\n        if (!info.quantity) info.quantity = aiRecap[1];\n        if (!info.product) info.product = aiRecap[2].trim();\n      }\n      pviSubmitForm({\n        type: 'quote_request',\n        name: info.name,\n        email: info.email,\n        phone: info.phone,\n        product: info.product,\n        quantity: info.quantity,\n        message: 'Voice quote request. Last AI: ' + aiText.substring(0, 200)\n      });\n      console.log('[PVI Voice] Quote auto-submitted for', info.email);\n    }\n  }\n  var isStreaming = false;\n  var chatGeneration = 0;\n  var currentMode = 'text';\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // i18n \u2014 UI strings + auto-detect from subdomain\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  var SUPPORTED_LANGS = ['en','es','it','fr','de','pt','ja','zh','ko','ru','ar','nl'];\n\n  var I18N = {\n    en: {\n      name: 'English',\n      voiceHint: 'Click <strong>Voice</strong> to switch to voice mode.',\n      welcome: 'Hi! How can I help you today?',\n      welcomeProduct: 'Hi! You are on the <strong>{product}</strong> page. Would you like any information on this product, or anything else?',\n      welcomeReturn: 'I see you\\'re now looking at <strong>{product}</strong>. What would you like to know?',\n      welcomeReception: 'Hi, this is <strong>Dana</strong>! Are you looking at a new purchase, need tech support on a unit you own, or checking on an existing order?',\n      chips: ['Tell me about this product', 'Help me choose', 'How do I set it up?', 'Get a quote'],\n      chipsNoProduct: ['Customer Service', 'Login Reset', 'Check an Order', 'Technical Support', 'Get a Quote', 'Help Me Choose a Product'],\n      chipPrompts: ['Tell me about the {product}', 'Help me choose the right modulator for my needs', 'How do I set up the {product}?', 'I need a quote for a bulk order'],\n      chipPromptsNoProduct: ['I need customer service', 'I forgot my store account login, can you help me reset it?', 'I want to check the status of my order', 'I need technical support with my PVI product', 'I need a quote for a bulk order', 'Help me choose the right product for my needs'],\n      placeholder: 'Type your question...',\n      listening: 'Listening... Speak naturally',\n      voiceMode: 'AI Voice Mode',\n      textMode: 'Text Chat Mode',\n      talkHuman: 'Talk to a specialist',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    es: {\n      name: 'Espa\\u00f1ol',\n      voiceHint: 'Haz clic en <strong>Voz</strong> para cambiar al modo de voz.',\n      welcome: '\\u00a1Hola! \\u00bfEn qu\\u00e9 puedo ayudarte hoy?',\n      welcomeProduct: '\\u00a1Hola! Est\\u00e1s en la p\\u00e1gina del <strong>{product}</strong>. \\u00bfTe gustar\\u00eda informaci\\u00f3n sobre este producto, o cualquier otra cosa?',\n      welcomeReturn: 'Veo que ahora est\\u00e1s mirando <strong>{product}</strong>. \\u00bfQu\\u00e9 te gustar\\u00eda saber?',\n      welcomeReception: '\\u00a1Hola, soy <strong>Dana</strong>! \\u00bfBuscas una nueva compra, necesitas soporte t\\u00e9cnico para un equipo que ya tienes, o quieres consultar un pedido existente?',\n      chips: ['Cu\\u00e9ntame sobre este producto', 'Ay\\u00fadame a elegir', '\\u00bfC\\u00f3mo lo configuro?', 'Solicitar cotizaci\\u00f3n'],\n      chipsNoProduct: ['Atenci\\u00f3n al Cliente', 'Restablecer Acceso', 'Consultar un Pedido', 'Soporte T\\u00e9cnico', 'Solicitar Cotizaci\\u00f3n', 'Ay\\u00fadame a Elegir un Producto'],\n      chipPrompts: ['H\\u00e1blame sobre el {product}', 'Ay\\u00fadame a elegir el modulador adecuado', '\\u00bfC\\u00f3mo configuro el {product}?', 'Necesito una cotizaci\\u00f3n para un pedido grande'],\n      chipPromptsNoProduct: ['Necesito atenci\\u00f3n al cliente', 'Olvid\\u00e9 mi contrase\\u00f1a de acceso a la tienda, \\u00bfpuedes ayudarme a restablecerla?', 'Quiero consultar el estado de mi pedido', 'Necesito soporte t\\u00e9cnico para mi producto PVI', 'Necesito una cotizaci\\u00f3n para un pedido grande', 'Ay\\u00fadame a elegir el producto correcto'],\n      placeholder: 'Escribe tu pregunta...',\n      listening: 'Escuchando... Habla con naturalidad',\n      voiceMode: 'Modo Voz IA',\n      textMode: 'Modo Chat de Texto',\n      talkHuman: 'Hablar con un especialista',\n      langSwitch: 'in English',\n      langSwitchTo: 'en'\n    },\n    it: {\n      name: 'Italiano',\n      voiceHint: 'Clicca <strong>Voce</strong> per passare alla modalit\\u00e0 vocale.',\n      welcome: 'Ciao! Come posso aiutarti oggi?',\n      welcomeProduct: 'Ciao! Sei sulla pagina del <strong>{product}</strong>. Vuoi informazioni su questo prodotto, o qualsiasi altra cosa?',\n      welcomeReturn: 'Vedo che stai guardando <strong>{product}</strong>. Cosa vorresti sapere?',\n      welcomeReception: 'Ciao, sono <strong>Dana</strong>! Stai valutando un nuovo acquisto, ti serve assistenza tecnica per un\\u2019unit\\u00e0 che possiedi, o vuoi controllare un ordine esistente?',\n      chips: ['Parlami di questo prodotto', 'Aiutami a scegliere', 'Come lo configuro?', 'Richiedi un preventivo'],\n      chipsNoProduct: ['Servizio Clienti', 'Reset Password', 'Controlla un Ordine', 'Supporto Tecnico', 'Richiedi un Preventivo', 'Aiutami a Scegliere un Prodotto'],\n      chipPrompts: ['Parlami del {product}', 'Aiutami a scegliere il modulatore giusto', 'Come configuro il {product}?', 'Vorrei un preventivo per un ordine grande'],\n      chipPromptsNoProduct: ['Ho bisogno del servizio clienti', 'Ho dimenticato la password di accesso al negozio, puoi aiutarmi a reimpostarla?', 'Vorrei controllare lo stato del mio ordine', 'Ho bisogno di supporto tecnico per il mio prodotto PVI', 'Vorrei un preventivo per un ordine grande', 'Aiutami a scegliere il prodotto giusto'],\n      placeholder: 'Scrivi la tua domanda...',\n      listening: 'In ascolto... Parla normalmente',\n      voiceMode: 'Modalit\\u00e0 Voce IA',\n      textMode: 'Chat Testuale',\n      talkHuman: 'Parla con uno specialista',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    fr: {\n      name: 'Fran\\u00e7ais',\n      voiceHint: 'Cliquez sur <strong>Voix</strong> pour passer au mode vocal.',\n      welcome: 'Bonjour ! Comment puis-je vous aider aujourd\\'hui ?',\n      welcomeProduct: 'Bonjour ! Vous \\u00eates sur la page <strong>{product}</strong>. Souhaitez-vous des informations sur ce produit, ou autre chose ?',\n      welcomeReturn: 'Je vois que vous regardez maintenant <strong>{product}</strong>. Que souhaitez-vous savoir ?',\n      welcomeReception: 'Bonjour, c\\u2019est <strong>Dana</strong> ! Vous envisagez un nouvel achat, avez besoin d\\u2019assistance technique pour un appareil que vous poss\\u00e9dez, ou souhaitez v\\u00e9rifier une commande existante ?',\n      chips: ['Parlez-moi de ce produit', 'Aidez-moi \\u00e0 choisir', 'Comment le configurer ?', 'Demander un devis'],\n      chipsNoProduct: ['Service Client', 'R\\u00e9initialiser Mon Acc\\u00e8s', 'Suivre une Commande', 'Support Technique', 'Demander un Devis', 'Aidez-moi \\u00e0 Choisir un Produit'],\n      chipPrompts: ['Parlez-moi du {product}', 'Aidez-moi \\u00e0 choisir le bon modulateur', 'Comment configurer le {product} ?', 'J\\'ai besoin d\\'un devis pour une grosse commande'],\n      chipPromptsNoProduct: ['J\\'ai besoin du service client', 'J\\'ai oubli\\u00e9 mon mot de passe de connexion \\u00e0 la boutique, pouvez-vous m\\'aider \\u00e0 le r\\u00e9initialiser ?', 'Je veux v\\u00e9rifier le statut de ma commande', 'J\\'ai besoin de support technique pour mon produit PVI', 'J\\'ai besoin d\\'un devis pour une grosse commande', 'Aidez-moi \\u00e0 choisir le bon produit'],\n      placeholder: 'Tapez votre question...',\n      listening: 'J\\'\\u00e9coute... Parlez naturellement',\n      voiceMode: 'Mode Voix IA',\n      textMode: 'Mode Chat Texte',\n      talkHuman: 'Parler \\u00e0 un sp\\u00e9cialiste',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    de: {\n      name: 'Deutsch',\n      voiceHint: 'Klicken Sie auf <strong>Stimme</strong>, um in den Sprachmodus zu wechseln.',\n      welcome: 'Hallo! Wie kann ich Ihnen heute helfen?',\n      welcomeProduct: 'Hallo! Sie sind auf der Seite <strong>{product}</strong>. M\\u00f6chten Sie Informationen zu diesem Produkt oder etwas anderem?',\n      welcomeReturn: 'Ich sehe, Sie schauen sich jetzt <strong>{product}</strong> an. Was m\\u00f6chten Sie wissen?',\n      welcomeReception: 'Hallo, hier ist <strong>Dana</strong>! M\\u00f6chten Sie etwas Neues kaufen, brauchen Sie technischen Support f\\u00fcr ein Ger\\u00e4t, das Sie besitzen, oder m\\u00f6chten Sie eine bestehende Bestellung pr\\u00fcfen?',\n      chips: ['Erz\\u00e4hlen Sie mir vom Produkt', 'Helfen Sie mir bei der Auswahl', 'Wie installiere ich es?', 'Angebot anfordern'],\n      chipsNoProduct: ['Kundenservice', 'Login zur\\u00fccksetzen', 'Bestellung pr\\u00fcfen', 'Technischer Support', 'Angebot anfordern', 'Hilfe bei der Produktwahl'],\n      chipPrompts: ['Erz\\u00e4hlen Sie mir \\u00fcber den {product}', 'Helfen Sie mir, den richtigen Modulator zu w\\u00e4hlen', 'Wie richte ich den {product} ein?', 'Ich brauche ein Angebot f\\u00fcr eine Gro\\u00dfbestellung'],\n      chipPromptsNoProduct: ['Ich brauche den Kundenservice', 'Ich habe mein Shop-Passwort vergessen, k\\u00f6nnen Sie mir helfen, es zur\\u00fcckzusetzen?', 'Ich m\\u00f6chte den Status meiner Bestellung pr\\u00fcfen', 'Ich brauche technischen Support f\\u00fcr mein PVI-Produkt', 'Ich brauche ein Angebot f\\u00fcr eine Gro\\u00dfbestellung', 'Helfen Sie mir, das richtige Produkt zu w\\u00e4hlen'],\n      placeholder: 'Geben Sie Ihre Frage ein...',\n      listening: 'H\\u00f6re zu... Sprechen Sie nat\\u00fcrlich',\n      voiceMode: 'KI-Sprachmodus',\n      textMode: 'Text-Chat-Modus',\n      talkHuman: 'Mit einem Spezialisten sprechen',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    pt: {\n      name: 'Portugu\\u00eas',\n      voiceHint: 'Clique em <strong>Voz</strong> para mudar para o modo de voz.',\n      welcome: 'Ol\\u00e1! Como posso ajudar hoje?',\n      welcomeProduct: 'Ol\\u00e1! Voc\\u00ea est\\u00e1 na p\\u00e1gina do <strong>{product}</strong>. Gostaria de informa\\u00e7\\u00f5es sobre este produto, ou algo mais?',\n      welcomeReturn: 'Vejo que voc\\u00ea est\\u00e1 olhando <strong>{product}</strong>. O que gostaria de saber?',\n      welcomeReception: 'Ol\\u00e1, aqui \\u00e9 a <strong>Dana</strong>! Est\\u00e1 pensando em uma nova compra, precisa de suporte t\\u00e9cnico para um equipamento que j\\u00e1 possui, ou quer verificar um pedido existente?',\n      chips: ['Fale-me sobre este produto', 'Ajude-me a escolher', 'Como configurar?', 'Pedir or\\u00e7amento'],\n      chipsNoProduct: ['Atendimento ao Cliente', 'Redefinir Acesso', 'Consultar um Pedido', 'Suporte T\\u00e9cnico', 'Pedir Or\\u00e7amento', 'Ajude-me a Escolher um Produto'],\n      chipPrompts: ['Fale-me sobre o {product}', 'Ajude-me a escolher o modulador certo', 'Como configurar o {product}?', 'Preciso de um or\\u00e7amento para um pedido grande'],\n      chipPromptsNoProduct: ['Preciso de atendimento ao cliente', 'Esqueci minha senha de acesso \\u00e0 loja, pode me ajudar a redefinir?', 'Quero verificar o status do meu pedido', 'Preciso de suporte t\\u00e9cnico para meu produto PVI', 'Preciso de um or\\u00e7amento para um pedido grande', 'Ajude-me a escolher o produto certo'],\n      placeholder: 'Digite sua pergunta...',\n      listening: 'Ouvindo... Fale naturalmente',\n      voiceMode: 'Modo Voz IA',\n      textMode: 'Modo Chat de Texto',\n      talkHuman: 'Falar com um especialista',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    ja: {\n      name: '\\u65e5\\u672c\\u8a9e',\n      voiceHint: '<strong>\\u97f3\\u58f0</strong>\\u3092\\u30af\\u30ea\\u30c3\\u30af\\u3057\\u3066\\u97f3\\u58f0\\u30e2\\u30fc\\u30c9\\u306b\\u5207\\u308a\\u66ff\\u3048\\u3066\\u304f\\u3060\\u3055\\u3044\\u3002',\n      welcome: '\\u3053\\u3093\\u306b\\u3061\\u306f\\uff01\\u4f55\\u304a\\u624b\\u4f1d\\u3044\\u3057\\u307e\\u3057\\u3087\\u3046\\u304b\\uff1f',\n      welcomeProduct: '\\u3053\\u3093\\u306b\\u3061\\u306f\\uff01<strong>{product}</strong>\\u306e\\u30da\\u30fc\\u30b8\\u3092\\u3054\\u89a7\\u3044\\u305f\\u3060\\u3044\\u3066\\u3044\\u307e\\u3059\\u3002\\u3053\\u306e\\u88fd\\u54c1\\u3084\\u305d\\u306e\\u4ed6\\u306e\\u3054\\u8cea\\u554f\\u306f\\u3042\\u308a\\u307e\\u3059\\u304b\\uff1f',\n      chips: ['\\u3053\\u306e\\u88fd\\u54c1\\u306b\\u3064\\u3044\\u3066', '\\u9078\\u3073\\u65b9\\u3092\\u6559\\u3048\\u3066', '\\u8a2d\\u5b9a\\u65b9\\u6cd5\\u306f\\uff1f', '\\u898b\\u7a4d\\u3092\\u4f9d\\u983c'],\n      chipsNoProduct: ['\\u30ab\\u30b9\\u30bf\\u30de\\u30fc\\u30b5\\u30fc\\u30d3\\u30b9', '\\u30ed\\u30b0\\u30a4\\u30f3\\u30ea\\u30bb\\u30c3\\u30c8', '\\u6ce8\\u6587\\u306e\\u78ba\\u8a8d', '\\u30c6\\u30af\\u30cb\\u30ab\\u30eb\\u30b5\\u30dd\\u30fc\\u30c8', '\\u898b\\u7a4d\\u3092\\u4f9d\\u983c', '\\u88fd\\u54c1\\u9078\\u3073\\u306e\\u304a\\u624b\\u4f1d\\u3044'],\n      chipPrompts: ['{product}\\u306b\\u3064\\u3044\\u3066\\u6559\\u3048\\u3066', '\\u9069\\u5207\\u306a\\u30e2\\u30b8\\u30e5\\u30ec\\u30fc\\u30bf\\u30fc\\u3092\\u9078\\u3076\\u306e\\u3092\\u624b\\u4f1d\\u3063\\u3066', '{product}\\u306e\\u8a2d\\u5b9a\\u65b9\\u6cd5\\u306f\\uff1f', '\\u5927\\u91cf\\u6ce8\\u6587\\u306e\\u898b\\u7a4d\\u308a\\u304c\\u6b32\\u3057\\u3044'],\n      chipPromptsNoProduct: ['\\u30ab\\u30b9\\u30bf\\u30de\\u30fc\\u30b5\\u30fc\\u30d3\\u30b9\\u3092\\u304a\\u9858\\u3044\\u3057\\u307e\\u3059', '\\u30b9\\u30c8\\u30a2\\u306e\\u30ed\\u30b0\\u30a4\\u30f3\\u30d1\\u30b9\\u30ef\\u30fc\\u30c9\\u3092\\u5fd8\\u308c\\u305f\\u306e\\u3067\\u30ea\\u30bb\\u30c3\\u30c8\\u3092\\u624b\\u4f1d\\u3063\\u3066\\u304f\\u3060\\u3055\\u3044', '\\u6ce8\\u6587\\u306e\\u72b6\\u6cc1\\u3092\\u78ba\\u8a8d\\u3057\\u305f\\u3044', 'PVI\\u88fd\\u54c1\\u306e\\u30c6\\u30af\\u30cb\\u30ab\\u30eb\\u30b5\\u30dd\\u30fc\\u30c8\\u304c\\u5fc5\\u8981', '\\u5927\\u91cf\\u6ce8\\u6587\\u306e\\u898b\\u7a4d\\u308a\\u304c\\u6b32\\u3057\\u3044', '\\u9069\\u5207\\u306a\\u88fd\\u54c1\\u3092\\u9078\\u3076\\u306e\\u3092\\u624b\\u4f1d\\u3063\\u3066'],\n      placeholder: '\\u8cea\\u554f\\u3092\\u5165\\u529b...',\n      listening: '\\u8074\\u3044\\u3066\\u3044\\u307e\\u3059...\\u81ea\\u7136\\u306b\\u8a71\\u3057\\u3066\\u304f\\u3060\\u3055\\u3044',\n      voiceMode: 'AI\\u97f3\\u58f0\\u30e2\\u30fc\\u30c9',\n      textMode: '\\u30c6\\u30ad\\u30b9\\u30c8\\u30c1\\u30e3\\u30c3\\u30c8\\u30e2\\u30fc\\u30c9',\n      talkHuman: '\\u5c02\\u9580\\u5bb6\\u3068\\u8a71\\u3059',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    zh: {\n      name: '\\u4e2d\\u6587',\n      voiceHint: '\\u70b9\\u51fb<strong>\\u8bed\\u97f3</strong>\\u5207\\u6362\\u5230\\u8bed\\u97f3\\u6a21\\u5f0f\\u3002',\n      welcome: '\\u4f60\\u597d\\uff01\\u4eca\\u5929\\u6709\\u4ec0\\u4e48\\u53ef\\u4ee5\\u5e2e\\u4f60\\u7684\\uff1f',\n      welcomeProduct: '\\u4f60\\u597d\\uff01\\u4f60\\u6b63\\u5728\\u6d4f\\u89c8<strong>{product}</strong>\\u9875\\u9762\\u3002\\u4f60\\u60f3\\u4e86\\u89e3\\u8fd9\\u4e2a\\u4ea7\\u54c1\\u6216\\u5176\\u4ed6\\u4ec0\\u4e48\\u5417\\uff1f',\n      chips: ['\\u4ecb\\u7ecd\\u8fd9\\u4e2a\\u4ea7\\u54c1', '\\u5e2e\\u6211\\u9009\\u62e9', '\\u5982\\u4f55\\u8bbe\\u7f6e\\uff1f', '\\u83b7\\u53d6\\u62a5\\u4ef7'],\n      chipsNoProduct: ['\\u5ba2\\u6237\\u670d\\u52a1', '\\u91cd\\u7f6e\\u767b\\u5f55', '\\u67e5\\u8be2\\u8ba2\\u5355', '\\u6280\\u672f\\u652f\\u6301', '\\u83b7\\u53d6\\u62a5\\u4ef7', '\\u5e2e\\u6211\\u9009\\u62e9\\u4ea7\\u54c1'],\n      chipPrompts: ['\\u544a\\u8bc9\\u6211\\u5173\\u4e8e{product}\\u7684\\u4fe1\\u606f', '\\u5e2e\\u6211\\u9009\\u62e9\\u5408\\u9002\\u7684\\u8c03\\u5236\\u5668', '\\u5982\\u4f55\\u8bbe\\u7f6e{product}\\uff1f', '\\u6211\\u9700\\u8981\\u5927\\u8ba2\\u5355\\u62a5\\u4ef7'],\n      chipPromptsNoProduct: ['\\u6211\\u9700\\u8981\\u5ba2\\u6237\\u670d\\u52a1', '\\u6211\\u5fd8\\u8bb0\\u4e86\\u5546\\u5e97\\u767b\\u5f55\\u5bc6\\u7801\\uff0c\\u8bf7\\u5e2e\\u6211\\u91cd\\u7f6e', '\\u6211\\u60f3\\u67e5\\u8be2\\u8ba2\\u5355\\u72b6\\u6001', '\\u6211\\u7684PVI\\u4ea7\\u54c1\\u9700\\u8981\\u6280\\u672f\\u652f\\u6301', '\\u6211\\u9700\\u8981\\u5927\\u8ba2\\u5355\\u62a5\\u4ef7', '\\u5e2e\\u6211\\u9009\\u62e9\\u5408\\u9002\\u7684\\u4ea7\\u54c1'],\n      placeholder: '\\u8f93\\u5165\\u95ee\\u9898...',\n      listening: '\\u6b63\\u5728\\u542c... \\u8bf7\\u81ea\\u7136\\u8bf4\\u8bdd',\n      voiceMode: 'AI\\u8bed\\u97f3\\u6a21\\u5f0f',\n      textMode: '\\u6587\\u5b57\\u804a\\u5929\\u6a21\\u5f0f',\n      talkHuman: '\\u4e0e\\u4e13\\u5bb6\\u4ea4\\u8c08',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    ko: {\n      name: '\\ud55c\\uad6d\\uc5b4',\n      voiceHint: '<strong>\\uc74c\\uc131</strong>\\uc744 \\ud074\\ub9ad\\ud574 \\uc74c\\uc131 \\ubaa8\\ub4dc\\ub85c \\uc804\\ud658\\ud558\\uc138\\uc694.',\n      welcome: '\\uc548\\ub155\\ud558\\uc138\\uc694! \\ubb34\\uc5c7\\uc744 \\ub3c4\\uc640\\ub4dc\\ub9b4\\uae4c\\uc694?',\n      welcomeProduct: '\\uc548\\ub155\\ud558\\uc138\\uc694! <strong>{product}</strong> \\ud398\\uc774\\uc9c0\\uc5d0 \\uacc4\\uc2dc\\ub124\\uc694. \\uc774 \\uc81c\\ud488\\uc774\\ub098 \\ub2e4\\ub978 \\uac83\\uc5d0 \\ub300\\ud574 \\uad81\\uae08\\ud55c \\uc810\\uc774 \\uc788\\uc73c\\uc138\\uc694?',\n      chips: ['\\uc774 \\uc81c\\ud488\\uc5d0 \\ub300\\ud574', '\\uc120\\ud0dd \\ub3c4\\uc6c0', '\\uc124\\uc815 \\ubc29\\ubc95\\uc740?', '\\uacac\\uc801 \\uc694\\uccad'],\n      chipsNoProduct: ['\\uace0\\uac1d \\uc11c\\ube44\\uc2a4', '\\ub85c\\uadf8\\uc778 \\uc7ac\\uc124\\uc815', '\\uc8fc\\ubb38 \\uc870\\ud68c', '\\uae30\\uc220 \\uc9c0\\uc6d0', '\\uacac\\uc801 \\uc694\\uccad', '\\uc81c\\ud488 \\uc120\\ud0dd \\ub3c4\\uc6c0'],\n      chipPrompts: ['{product}\\uc5d0 \\ub300\\ud574 \\uc54c\\ub824\\uc8fc\\uc138\\uc694', '\\uc801\\ud569\\ud55c \\ubcc0\\uc870\\uae30 \\uc120\\ud0dd\\uc744 \\ub3c4\\uc640\\uc8fc\\uc138\\uc694', '{product} \\uc124\\uc815 \\ubc29\\ubc95\\uc740?', '\\ub300\\ub7c9 \\uc8fc\\ubb38 \\uacac\\uc801\\uc774 \\ud544\\uc694\\ud569\\ub2c8\\ub2e4'],\n      chipPromptsNoProduct: ['\\uace0\\uac1d \\uc11c\\ube44\\uc2a4\\uac00 \\ud544\\uc694\\ud569\\ub2c8\\ub2e4', '\\uc2a4\\ud1a0\\uc5b4 \\ub85c\\uadf8\\uc778 \\ube44\\ubc00\\ubc88\\ud638\\ub97c \\uc78a\\uc5b4\\ubc84\\ub838\\uc5b4\\uc694. \\uc7ac\\uc124\\uc815\\uc744 \\ub3c4\\uc640\\uc8fc\\uc138\\uc694', '\\uc8fc\\ubb38 \\uc0c1\\ud0dc\\ub97c \\ud655\\uc778\\ud558\\uace0 \\uc2f6\\uc2b5\\ub2c8\\ub2e4', 'PVI \\uc81c\\ud488 \\uae30\\uc220 \\uc9c0\\uc6d0\\uc774 \\ud544\\uc694\\ud569\\ub2c8\\ub2e4', '\\ub300\\ub7c9 \\uc8fc\\ubb38 \\uacac\\uc801\\uc774 \\ud544\\uc694\\ud569\\ub2c8\\ub2e4', '\\uc801\\ud569\\ud55c \\uc81c\\ud488 \\uc120\\ud0dd\\uc744 \\ub3c4\\uc640\\uc8fc\\uc138\\uc694'],\n      placeholder: '\\uc9c8\\ubb38\\uc744 \\uc785\\ub825\\ud558\\uc138\\uc694...',\n      listening: '\\ub4e3\\ub294 \\uc911... \\uc790\\uc5f0\\uc2a4\\ub7fd\\uac8c \\ub9d0\\uc500\\ud558\\uc138\\uc694',\n      voiceMode: 'AI \\uc74c\\uc131 \\ubaa8\\ub4dc',\n      textMode: '\\ud14d\\uc2a4\\ud2b8 \\ucc44\\ud305 \\ubaa8\\ub4dc',\n      talkHuman: '\\uc804\\ubb38\\uac00\\uc640 \\ub300\\ud654',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    ru: {\n      name: '\\u0420\\u0443\\u0441\\u0441\\u043a\\u0438\\u0439',\n      voiceHint: '\\u041d\\u0430\\u0436\\u043c\\u0438\\u0442\\u0435 <strong>\\u0413\\u043e\\u043b\\u043e\\u0441</strong>, \\u0447\\u0442\\u043e\\u0431\\u044b \\u043f\\u0435\\u0440\\u0435\\u043a\\u043b\\u044e\\u0447\\u0438\\u0442\\u044c\\u0441\\u044f \\u0432 \\u0433\\u043e\\u043b\\u043e\\u0441\\u043e\\u0432\\u043e\\u0439 \\u0440\\u0435\\u0436\\u0438\\u043c.',\n      welcome: '\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442! \\u0427\\u0435\\u043c \\u044f \\u043c\\u043e\\u0433\\u0443 \\u043f\\u043e\\u043c\\u043e\\u0447\\u044c?',\n      welcomeProduct: '\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442! \\u0412\\u044b \\u043d\\u0430 \\u0441\\u0442\\u0440\\u0430\\u043d\\u0438\\u0446\\u0435 <strong>{product}</strong>. \\u0425\\u043e\\u0442\\u0438\\u0442\\u0435 \\u0438\\u043d\\u0444\\u043e\\u0440\\u043c\\u0430\\u0446\\u0438\\u044e \\u043e \\u044d\\u0442\\u043e\\u043c \\u043f\\u0440\\u043e\\u0434\\u0443\\u043a\\u0442\\u0435 \\u0438\\u043b\\u0438 \\u043e \\u0447\\u0451\\u043c-\\u0442\\u043e \\u0435\\u0449\\u0451?',\n      chips: ['\\u0420\\u0430\\u0441\\u0441\\u043a\\u0430\\u0436\\u0438 \\u043e \\u043f\\u0440\\u043e\\u0434\\u0443\\u043a\\u0442\\u0435', '\\u041f\\u043e\\u043c\\u043e\\u0433\\u0438 \\u0432\\u044b\\u0431\\u0440\\u0430\\u0442\\u044c', '\\u041a\\u0430\\u043a \\u043d\\u0430\\u0441\\u0442\\u0440\\u043e\\u0438\\u0442\\u044c?', '\\u0417\\u0430\\u043f\\u0440\\u043e\\u0441\\u0438\\u0442\\u044c \\u0446\\u0435\\u043d\\u0443'],\n      chipsNoProduct: ['\\u041a\\u043b\\u0438\\u0435\\u043d\\u0442\\u0441\\u043a\\u0438\\u0439 \\u0441\\u0435\\u0440\\u0432\\u0438\\u0441', '\\u0421\\u0431\\u0440\\u043e\\u0441 \\u043f\\u0430\\u0440\\u043e\\u043b\\u044f', '\\u041f\\u0440\\u043e\\u0432\\u0435\\u0440\\u0438\\u0442\\u044c \\u0437\\u0430\\u043a\\u0430\\u0437', '\\u0422\\u0435\\u0445\\u043f\\u043e\\u0434\\u0434\\u0435\\u0440\\u0436\\u043a\\u0430', '\\u0417\\u0430\\u043f\\u0440\\u043e\\u0441\\u0438\\u0442\\u044c \\u0446\\u0435\\u043d\\u0443', '\\u041f\\u043e\\u043c\\u043e\\u0433\\u0438 \\u0432\\u044b\\u0431\\u0440\\u0430\\u0442\\u044c \\u043f\\u0440\\u043e\\u0434\\u0443\\u043a\\u0442'],\n      chipPrompts: ['\\u0420\\u0430\\u0441\\u0441\\u043a\\u0430\\u0436\\u0438 \\u043e {product}', '\\u041f\\u043e\\u043c\\u043e\\u0433\\u0438 \\u0432\\u044b\\u0431\\u0440\\u0430\\u0442\\u044c \\u043f\\u043e\\u0434\\u0445\\u043e\\u0434\\u044f\\u0449\\u0438\\u0439 \\u043c\\u043e\\u0434\\u0443\\u043b\\u044f\\u0442\\u043e\\u0440', '\\u041a\\u0430\\u043a \\u043d\\u0430\\u0441\\u0442\\u0440\\u043e\\u0438\\u0442\\u044c {product}?', '\\u041d\\u0443\\u0436\\u0435\\u043d \\u0440\\u0430\\u0441\\u0447\\u0435\\u0442 \\u0434\\u043b\\u044f \\u043e\\u043f\\u0442\\u043e\\u0432\\u043e\\u0433\\u043e \\u0437\\u0430\\u043a\\u0430\\u0437\\u0430'],\n      chipPromptsNoProduct: ['\\u041c\\u043d\\u0435 \\u043d\\u0443\\u0436\\u0435\\u043d \\u043a\\u043b\\u0438\\u0435\\u043d\\u0442\\u0441\\u043a\\u0438\\u0439 \\u0441\\u0435\\u0440\\u0432\\u0438\\u0441', '\\u042f \\u0437\\u0430\\u0431\\u044b\\u043b \\u043f\\u0430\\u0440\\u043e\\u043b\\u044c \\u043e\\u0442 \\u043c\\u0430\\u0433\\u0430\\u0437\\u0438\\u043d\\u0430, \\u043f\\u043e\\u043c\\u043e\\u0433\\u0438\\u0442\\u0435 \\u0435\\u0433\\u043e \\u0441\\u0431\\u0440\\u043e\\u0441\\u0438\\u0442\\u044c', '\\u0425\\u043e\\u0447\\u0443 \\u043f\\u0440\\u043e\\u0432\\u0435\\u0440\\u0438\\u0442\\u044c \\u0441\\u0442\\u0430\\u0442\\u0443\\u0441 \\u043c\\u043e\\u0435\\u0433\\u043e \\u0437\\u0430\\u043a\\u0430\\u0437\\u0430', '\\u041d\\u0443\\u0436\\u043d\\u0430 \\u0442\\u0435\\u0445\\u043f\\u043e\\u0434\\u0434\\u0435\\u0440\\u0436\\u043a\\u0430 \\u043f\\u043e PVI', '\\u041d\\u0443\\u0436\\u0435\\u043d \\u0440\\u0430\\u0441\\u0447\\u0435\\u0442 \\u0434\\u043b\\u044f \\u043e\\u043f\\u0442\\u043e\\u0432\\u043e\\u0433\\u043e \\u0437\\u0430\\u043a\\u0430\\u0437\\u0430', '\\u041f\\u043e\\u043c\\u043e\\u0433\\u0438 \\u0432\\u044b\\u0431\\u0440\\u0430\\u0442\\u044c \\u043f\\u043e\\u0434\\u0445\\u043e\\u0434\\u044f\\u0449\\u0438\\u0439 \\u043f\\u0440\\u043e\\u0434\\u0443\\u043a\\u0442'],\n      placeholder: '\\u0412\\u0432\\u0435\\u0434\\u0438\\u0442\\u0435 \\u0432\\u043e\\u043f\\u0440\\u043e\\u0441...',\n      listening: '\\u0421\\u043b\\u0443\\u0448\\u0430\\u044e... \\u0413\\u043e\\u0432\\u043e\\u0440\\u0438\\u0442\\u0435 \\u0435\\u0441\\u0442\\u0435\\u0441\\u0442\\u0432\\u0435\\u043d\\u043d\\u043e',\n      voiceMode: '\\u0413\\u043e\\u043b\\u043e\\u0441\\u043e\\u0432\\u043e\\u0439 \\u0440\\u0435\\u0436\\u0438\\u043c \\u0418\\u0418',\n      textMode: '\\u0422\\u0435\\u043a\\u0441\\u0442\\u043e\\u0432\\u044b\\u0439 \\u0447\\u0430\\u0442',\n      talkHuman: '\\u0421\\u0432\\u044f\\u0437\\u0430\\u0442\\u044c\\u0441\\u044f \\u0441\\u043e \\u0441\\u043f\\u0435\\u0446\\u0438\\u0430\\u043b\\u0438\\u0441\\u0442\\u043e\\u043c',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    ar: {\n      name: '\\u0627\\u0644\\u0639\\u0631\\u0628\\u064a\\u0629',\n      voiceHint: '\\u0627\\u0646\\u0642\\u0631 \\u0639\\u0644\\u0649 <strong>\\u0635\\u0648\\u062a</strong> \\u0644\\u0644\\u062a\\u0628\\u062f\\u064a\\u0644 \\u0625\\u0644\\u0649 \\u0648\\u0636\\u0639 \\u0627\\u0644\\u0635\\u0648\\u062a.',\n      welcome: '\\u0645\\u0631\\u062d\\u0628\\u0627\\u064b! \\u0643\\u064a\\u0641 \\u064a\\u0645\\u0643\\u0646\\u0646\\u064a \\u0645\\u0633\\u0627\\u0639\\u062f\\u062a\\u0643 \\u0627\\u0644\\u064a\\u0648\\u0645\\u061f',\n      welcomeProduct: '\\u0645\\u0631\\u062d\\u0628\\u0627\\u064b! \\u0623\\u0646\\u062a \\u0639\\u0644\\u0649 \\u0635\\u0641\\u062d\\u0629 <strong>{product}</strong>. \\u0647\\u0644 \\u062a\\u0631\\u063a\\u0628 \\u0641\\u064a \\u0645\\u0639\\u0644\\u0648\\u0645\\u0627\\u062a \\u0639\\u0646 \\u0647\\u0630\\u0627 \\u0627\\u0644\\u0645\\u0646\\u062a\\u062c \\u0623\\u0648 \\u0623\\u064a \\u0634\\u064a\\u0621 \\u0622\\u062e\\u0631\\u061f',\n      chips: ['\\u0623\\u062e\\u0628\\u0631\\u0646\\u064a \\u0639\\u0646 \\u0647\\u0630\\u0627 \\u0627\\u0644\\u0645\\u0646\\u062a\\u062c', '\\u0633\\u0627\\u0639\\u062f\\u0646\\u064a \\u0641\\u064a \\u0627\\u0644\\u0627\\u062e\\u062a\\u064a\\u0627\\u0631', '\\u0643\\u064a\\u0641 \\u0623\\u0642\\u0648\\u0645 \\u0628\\u0625\\u0639\\u062f\\u0627\\u062f\\u0647\\u061f', '\\u0627\\u0637\\u0644\\u0628 \\u0639\\u0631\\u0636 \\u0633\\u0639\\u0631'],\n      chipsNoProduct: ['\\u062e\\u062f\\u0645\\u0629 \\u0627\\u0644\\u0639\\u0645\\u0644\\u0627\\u0621', '\\u0625\\u0639\\u0627\\u062f\\u0629 \\u062a\\u0639\\u064a\\u064a\\u0646 \\u0627\\u0644\\u062f\\u062e\\u0648\\u0644', '\\u062a\\u062a\\u0628\\u0651\\u0639 \\u0637\\u0644\\u0628', '\\u0627\\u0644\\u062f\\u0639\\u0645 \\u0627\\u0644\\u0641\\u0646\\u064a', '\\u0627\\u0637\\u0644\\u0628 \\u0639\\u0631\\u0636 \\u0633\\u0639\\u0631', '\\u0633\\u0627\\u0639\\u062f\\u0646\\u064a \\u0641\\u064a \\u0627\\u062e\\u062a\\u064a\\u0627\\u0631 \\u0645\\u0646\\u062a\\u062c'],\n      chipPrompts: ['\\u0623\\u062e\\u0628\\u0631\\u0646\\u064a \\u0639\\u0646 {product}', '\\u0633\\u0627\\u0639\\u062f\\u0646\\u064a \\u0641\\u064a \\u0627\\u062e\\u062a\\u064a\\u0627\\u0631 \\u0627\\u0644\\u0645\\u0639\\u062f\\u0651\\u0644 \\u0627\\u0644\\u0645\\u0646\\u0627\\u0633\\u0628', '\\u0643\\u064a\\u0641 \\u0623\\u0639\\u062f\\u0651 {product}\\u061f', '\\u0623\\u062d\\u062a\\u0627\\u062c \\u0639\\u0631\\u0636 \\u0633\\u0639\\u0631 \\u0644\\u0637\\u0644\\u0628\\u064a\\u0651\\u0629 \\u0643\\u0628\\u064a\\u0631\\u0629'],\n      chipPromptsNoProduct: ['\\u0623\\u062d\\u062a\\u0627\\u062c \\u0625\\u0644\\u0649 \\u062e\\u062f\\u0645\\u0629 \\u0627\\u0644\\u0639\\u0645\\u0644\\u0627\\u0621', '\\u0646\\u0633\\u064a\\u062a \\u0643\\u0644\\u0645\\u0629 \\u0645\\u0631\\u0648\\u0631 \\u062d\\u0633\\u0627\\u0628\\u064a \\u0641\\u064a \\u0627\\u0644\\u0645\\u062a\\u062c\\u0631\\u060c \\u0633\\u0627\\u0639\\u062f\\u0646\\u064a \\u0641\\u064a \\u0625\\u0639\\u0627\\u062f\\u0629 \\u062a\\u0639\\u064a\\u064a\\u0646\\u0647\\u0627', '\\u0623\\u0631\\u064a\\u062f \\u0627\\u0644\\u062a\\u062d\\u0642\\u0642 \\u0645\\u0646 \\u062d\\u0627\\u0644\\u0629 \\u0637\\u0644\\u0628\\u064a', '\\u0623\\u062d\\u062a\\u0627\\u062c \\u062f\\u0639\\u0645\\u064b\\u0627 \\u0641\\u0646\\u064a\\u064b\\u0627 \\u0644\\u0645\\u0646\\u062a\\u062c PVI', '\\u0623\\u062d\\u062a\\u0627\\u062c \\u0639\\u0631\\u0636 \\u0633\\u0639\\u0631 \\u0644\\u0637\\u0644\\u0628\\u064a\\u0651\\u0629 \\u0643\\u0628\\u064a\\u0631\\u0629', '\\u0633\\u0627\\u0639\\u062f\\u0646\\u064a \\u0641\\u064a \\u0627\\u062e\\u062a\\u064a\\u0627\\u0631 \\u0627\\u0644\\u0645\\u0646\\u062a\\u062c \\u0627\\u0644\\u0645\\u0646\\u0627\\u0633\\u0628'],\n      placeholder: '\\u0627\\u0643\\u062a\\u0628 \\u0633\\u0624\\u0627\\u0644\\u0643...',\n      listening: '\\u0623\\u0633\\u062a\\u0645\\u0639... \\u062a\\u062d\\u062f\\u0651\\u062b \\u0628\\u0637\\u0628\\u064a\\u0639\\u064a\\u0651\\u0629',\n      voiceMode: '\\u0648\\u0636\\u0639 \\u0627\\u0644\\u0635\\u0648\\u062a \\u0644\\u0644\\u0630\\u0643\\u0627\\u0621 \\u0627\\u0644\\u0627\\u0635\\u0637\\u0646\\u0627\\u0639\\u064a',\n      textMode: '\\u0648\\u0636\\u0639 \\u0627\\u0644\\u062f\\u0631\\u062f\\u0634\\u0629 \\u0627\\u0644\\u0646\\u0635\\u064a\\u0629',\n      talkHuman: '\\u062a\\u062d\\u062f\\u0651\\u062b \\u0625\\u0644\\u0649 \\u0623\\u062e\\u0635\\u0627\\u0626\\u064a',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    },\n    nl: {\n      name: 'Nederlands',\n      voiceHint: 'Klik op <strong>Stem</strong> om naar de stemmodus te schakelen.',\n      welcome: 'Hallo! Hoe kan ik je vandaag helpen?',\n      welcomeProduct: 'Hallo! Je bent op de <strong>{product}</strong>-pagina. Wil je informatie over dit product, of iets anders?',\n      chips: ['Vertel me over dit product', 'Help me kiezen', 'Hoe stel ik het in?', 'Offerte aanvragen'],\n      chipsNoProduct: ['Klantenservice', 'Login Resetten', 'Bestelling Controleren', 'Technische Ondersteuning', 'Offerte Aanvragen', 'Help Me een Product Kiezen'],\n      chipPrompts: ['Vertel me over de {product}', 'Help me de juiste modulator te kiezen', 'Hoe stel ik de {product} in?', 'Ik heb een offerte nodig voor een grote bestelling'],\n      chipPromptsNoProduct: ['Ik heb klantenservice nodig', 'Ik ben mijn winkelwachtwoord vergeten, kun je helpen het te resetten?', 'Ik wil de status van mijn bestelling controleren', 'Ik heb technische ondersteuning nodig voor mijn PVI-product', 'Ik heb een offerte nodig voor een grote bestelling', 'Help me het juiste product te kiezen'],\n      placeholder: 'Typ je vraag...',\n      listening: 'Luistert... Spreek natuurlijk',\n      voiceMode: 'AI Spraakmodus',\n      textMode: 'Tekstchatmodus',\n      talkHuman: 'Spreek met een specialist',\n      langSwitch: 'in Espa\\u00f1ol',\n      langSwitchTo: 'es'\n    }\n  };\n\n  function detectLanguage() {\n    var cfg = window.__pviChatbot || {};\n    if (cfg.forcedLanguage && I18N[cfg.forcedLanguage]) return cfg.forcedLanguage;\n    try {\n      var stored = localStorage.getItem('pvi-lang');\n      if (stored && I18N[stored]) return stored;\n    } catch(_) {}\n    var host = (location.hostname || '').toLowerCase();\n    var prefix = host.split('.')[0];\n    if (I18N[prefix]) return prefix;\n    var nav = (navigator.language || 'en').toLowerCase().split('-')[0];\n    if (I18N[nav]) return nav;\n    return 'en';\n  }\n\n  var currentLang = detectLanguage();\n  function t() { return I18N[currentLang] || I18N.en; }\n\n  // \u2500\u2500 Lightbox \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  var overlay = null;\n  function openLB(src, alt) {\n    overlay = document.createElement('div');\n    overlay.className = 'pvi-lb-overlay';\n    var img = document.createElement('img');\n    img.src = src; img.alt = alt || '';\n    overlay.appendChild(img);\n    document.body.appendChild(overlay);\n    document.body.style.overflow = 'hidden';\n    requestAnimationFrame(function() { overlay.classList.add('active'); });\n    overlay.addEventListener('click', closeLB);\n  }\n  function closeLB() {\n    if (!overlay) return;\n    overlay.classList.remove('active');\n    setTimeout(function() {\n      if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);\n      overlay = null; document.body.style.overflow = '';\n    }, 300);\n  }\n  document.addEventListener('click', function(e) {\n    if (e.target.classList.contains('pvi-zoomable')) { e.preventDefault(); openLB(e.target.src, e.target.alt); }\n  });\n  document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeLB(); });\n\n  // \u2500\u2500 Sticky Bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  var stickyBar = document.getElementById('pviStickyBar');\n  var mainSubmit = document.querySelector('.product-form__submit');\n  if (stickyBar && mainSubmit) {\n    var io = new IntersectionObserver(function(entries) {\n      stickyBar.classList.toggle('visible', !entries[0].isIntersecting);\n    }, { threshold: 0 });\n    io.observe(mainSubmit);\n  }\n\n  // \u2500\u2500 Scroll Reveal (Apple-style fade-in) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  (function initScrollReveal() {\n    var revealEls = document.querySelectorAll('.pvi-section-label, .pvi-section-title, .pvi-section-subtitle, .pvi-section-body-text, .pvi-split-media, .pvi-full-section-image, .pvi-feat-card, .pvi-stat-card, .pvi-app-card, .pvi-accessory-card, .pvi-comparison-grid, .pvi-video-container');\n    revealEls = Array.prototype.filter.call(revealEls, function(el) {\n      if (el.classList.contains('pvi-cinematic-zoom')) return false;\n      if (el.closest('[data-pvi-cinematic=\"1\"]')) return false;\n      return true;\n    });\n    if (!revealEls.length) return;\n    revealEls.forEach(function(el) { el.classList.add('pvi-reveal'); });\n    var revealObs = new IntersectionObserver(function(entries) {\n      entries.forEach(function(entry) {\n        if (entry.isIntersecting) {\n          entry.target.classList.add('pvi-visible');\n          revealObs.unobserve(entry.target);\n        }\n      });\n    }, { threshold: 0.15, rootMargin: '0px 0px -60px 0px' });\n    revealEls.forEach(function(el) { revealObs.observe(el); });\n  })();\n\n  // \u2500\u2500 DOM Elements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  var chatToggle = document.getElementById('pviChatToggle');\n  var chatPanel = document.getElementById('pviChatPanel');\n  var chatClose = document.getElementById('pviChatClose');\n  var chatBody = document.getElementById('pviChatBody');\n  var chatMessages = document.getElementById('pviChatMessages');\n  var chatTextarea = document.getElementById('pviChatTextarea');\n  var chatSend = document.getElementById('pviChatSend');\n  var micBtn = document.getElementById('pviMicBtn');\n  var suggestions = document.getElementById('pviSuggestions');\n  var modeToggle = document.getElementById('pviModeToggle');\n  var modeToggleLabel = document.getElementById('pviModeToggleLabel');\n  var voiceSelect = document.getElementById('pviVoiceSelect');\n  var speedSelect = document.getElementById('pviSpeedSelect');\n  var langSelect = document.getElementById('pviLangSelect');\n  var welcomeBubble = document.getElementById('pviWelcomeBubble');\n  var hintBubble = document.getElementById('pviHintBubble');\n  var pviCfg = window.__pviChatbot || {};\n  var selectedVoice = (voiceSelect && voiceSelect.value) || pviCfg.defaultVoice || 'alloy';\n  var selectedSpeed = (speedSelect && parseFloat(speedSelect.value)) || pviCfg.defaultSpeed || 1.0;\n  if (langSelect) langSelect.value = currentLang;\n\n  // Human-readable label for the CURRENT page \u2014 product, collection, or page.\n  // Used so a returning customer is greeted with \"I see you're now checking\n  // <page>\" on ANY page type, never the first-visit hello again.\n  // SEO \"G\" page variants (G1/G2/G3...) are the SAME product listed on multiple\n  // pages for SEO \u2014 strip the suffix so the customer never sees \"Minimod 2 G3\"\n  // and asks what the difference is vs the \"regular\" one.\n  function pviStripSeoG(tl) {\n    var c = String(tl || '').replace(/[\\s\\-\u2013\u2014(]*\\bG\\d+\\)?\\s*$/i, '').replace(/\\s+$/, '');\n    return c || tl;\n  }\n\n  function pviCurrentPageLabel() {\n    if (product.title) return pviStripSeoG(product.title);\n    if (pageCtx.page_type === 'collection') return (pageCtx.page_title || pviCollectionName(pageCtx.collection_handle)) + ' collection';\n    if (pageCtx.page_type === 'page' && pageCtx.page_title) return pageCtx.page_title + ' page';\n    if (pageCtx.page_type === 'index') return 'Homepage';\n    return '';\n  }\n\n  // Default persona from the page context (only when nothing was restored):\n  // support page \u2192 John, EVERYTHING else \u2192 Lisa. NO Dana on the web chat\n  // (user decree Jul 30 2026 evening) \u2014 Dana is phone-only; Lisa answers\n  // neutral pages directly and transfers to John/Mary when needed.\n  if (!pviActivePersona) {\n    var _pviSup = (pageCtx.page_handle || '').indexOf('tech-support') !== -1 || /tech-support/i.test(location.pathname);\n    pviActivePersona = _pviSup ? 'support' : 'sales';\n  }\n\n  function renderWelcomeAndChips() {\n    var s = t();\n    if (hintBubble) hintBubble.innerHTML = s.voiceHint;\n    if (welcomeBubble) {\n      var pageLabel = pviCurrentPageLabel();\n      if (hasInteracted) {\n        // Returning session: NEVER repeat the first-visit hello. Acknowledge\n        // the new page (product, collection, or any page) and ask what they need.\n        if (pageLabel) {\n          welcomeBubble.innerHTML = s.welcomeReturn\n            ? s.welcomeReturn.replace('{product}', pageLabel)\n            : ('I see you\\'re now checking <strong>' + pageLabel + '</strong>. Any specific question I can help you with?');\n        } else {\n          // Rare page types (blog/cart/search) \u2014 no label, just keep helping\n          welcomeBubble.innerHTML = s.welcomeBack || 'What else can I help you with?';\n        }\n      } else if (product.title) {\n        welcomeBubble.innerHTML = s.welcomeProduct.replace('{product}', pviStripSeoG(product.title));\n      } else {\n        welcomeBubble.innerHTML = s.welcome;\n      }\n    }\n    var sugg = document.getElementById('pviSuggestions');\n    if (sugg) {\n      sugg.innerHTML = '';\n      var labels = product.title ? s.chips : s.chipsNoProduct;\n      var prompts = product.title ? s.chipPrompts : s.chipPromptsNoProduct;\n      // Department each no-product chip PROMISES, index-aligned across ALL\n      // languages: Customer Service / Login Reset / Check an Order \u2192 Mary,\n      // Technical Support \u2192 John, Get a Quote / Help Me Choose \u2192 Lisa.\n      // Sent as a one-shot persona_hint so routing never depends on the\n      // worker regex-guessing localized chip text (Aug 5 incident: the Login\n      // Reset chip landed on John, who submitted it as a quote request).\n      var chipPersonas = product.title ? [] : ['service', 'service', 'service', 'support', 'sales', 'sales'];\n      for (var i = 0; i < labels.length; i++) {\n        var btn = document.createElement('button');\n        btn.className = 'pvi-chat-chip';\n        btn.setAttribute('data-prompt', (prompts[i] || labels[i]).replace('{product}', product.title || ''));\n        if (chipPersonas[i]) btn.setAttribute('data-persona', chipPersonas[i]);\n        btn.textContent = labels[i];\n        sugg.appendChild(btn);\n      }\n      // \"Talk to a specialist\" phone chip REMOVED (user request Aug 5 2026):\n      // the shortcut list now covers all departments; pviCheckHours() safely\n      // no-ops when the #pviTalkHuman element is absent.\n\n      // Large language-switch pill at the bottom (toggles current \u2194 Spanish, or back to English when in Spanish)\n      if (s.langSwitchTo && s.langSwitchTo !== currentLang) {\n        var langPill = document.createElement('button');\n        langPill.className = 'pvi-chat-chip pvi-chat-chip-lang';\n        langPill.setAttribute('data-lang', s.langSwitchTo);\n        langPill.innerHTML = '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\"/><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"/></svg> ' + s.langSwitch;\n        sugg.appendChild(langPill);\n      }\n    }\n    if (chatTextarea) chatTextarea.placeholder = currentMode === 'voice' ? t().listening : t().placeholder;\n    var label = document.getElementById('pviModeToggleLabel');\n    if (label) label.textContent = currentMode === 'voice' ? t().textMode : t().voiceMode;\n    document.documentElement.setAttribute('data-pvi-lang', currentLang);\n  }\n  renderWelcomeAndChips();\n\n  // Show tech support page link when landing on a support page (especially after\n  // auto-navigate). Deferred via setTimeout so voiceSupportPages (declared later\n  // in this file) is initialized, and deduped so the same link never repeats.\n  if (hasInteracted && chosenPersona === 'support' && /tech-support/i.test(location.pathname)) {\n    setTimeout(function() {\n      var supportPageMatch = null;\n      var pathLower = location.pathname.toLowerCase();\n      for (var spl = 0; spl < voiceSupportPages.length; spl++) {\n        if (pathLower.indexOf(voiceSupportPages[spl].url.replace('/pages/', '').toLowerCase()) !== -1) {\n          supportPageMatch = voiceSupportPages[spl];\n          break;\n        }\n      }\n      if (supportPageMatch && !pviLinkShown(supportPageMatch.url)) {\n        pviMarkLinkShown(supportPageMatch.url);\n        appendMessage('ai', '<div style=\"font-size:0.9em;margin-top:6px;padding:8px 12px;background:#f0f7ff;border-left:3px solid #0071E3;border-radius:4px\">' +\n          '<strong>\\uD83D\\uDEE0\\uFE0F Tech Support Page</strong> \\u2014 manuals, quick start guides & software updates:<br>' +\n          '<a href=\"' + supportPageMatch.url + '\" style=\"color:#0071E3;text-decoration:underline;font-weight:600\">' + supportPageMatch.label + '</a></div>');\n        scrollToBottom();\n      }\n    }, 500);\n  }\n\n  // \u2500\u2500 Panel Toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  if (chatToggle && chatPanel) {\n    chatToggle.addEventListener('click', function() {\n      chatPanel.classList.toggle('open');\n      pviCheckHours();\n      pviSaveSession();\n      if (chatPanel.classList.contains('open') && chatTextarea) {\n        setTimeout(function() { chatTextarea.focus(); }, 300);\n      }\n    });\n  }\n  if (chatClose && chatPanel) {\n    chatClose.addEventListener('click', function() {\n      chatPanel.classList.remove('open');\n      // Close (X) = conversation ended \u2192 email the complete transcript NOW.\n      // Flush before setMode('text') so the VOICE/TEXT channel label stays correct.\n      if (chatHistory.length >= 2) { try { pviPushTranscript(true, true); } catch (_) {} }\n      if (currentMode === 'voice') setMode('text');\n      pviSaveSession();\n    });\n  }\n\n  // \u2500\u2500 Hide (keep chat + voice alive) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // The \"\u2013\" button simply HIDES the whole panel without closing the chat or\n  // stopping the voice call. To bring it back, the customer taps the same ASK AI\n  // button they already use to open it \u2014 no extra bars or controls. The voice\n  // call keeps running in the background while the panel is hidden.\n  var chatMinimize = document.getElementById('pviChatMinimize');\n  if (chatMinimize && chatPanel) {\n    chatMinimize.addEventListener('click', function(e) {\n      e.stopPropagation();\n      chatPanel.classList.remove('open');\n      pviSaveSession();\n    });\n  }\n\n  // \u2500\u2500 Restore prior conversation + reopen the chat on the new page \u2500\u2500\u2500\u2500\n  // The chat \"follows\" the customer: if it was open when they navigated, it\n  // reopens here, re-renders the whole conversation, and if they were in a\n  // voice call it auto-resumes voice (which speaks the new page's greeting).\n  (function pviRestoreConversation() {\n    if (!chatMessages) return;\n    if (chatHistory && chatHistory.length) {\n      chatHistory.forEach(function(m) {\n        if (m && m.role === 'user') appendMessage('user', pviEscapeHtml(m.content));\n        else if (m && m.content) appendMessage('ai', formatMessage(m.content));\n      });\n      if (suggestions) suggestions.style.display = 'none';\n    }\n    // NOTE: Do NOT add chatPanel.classList.add('open') here.\n    // The panel stays hidden on page navigation so customers can browse while voice continues.\n    // Customer taps the \"Ask AI\" button to re-show it. (Shopify project requirement, Jun 2026)\n    //\n    // VOICE RESUME is driven by the voice call state, NOT the panel state:\n    // an active call must survive navigation even if the panel was hidden\n    // (minimized) when the page changed.\n    if (pviRestored && pviRestored.mode === 'voice') {\n      pviCheckHours();\n      // Resume the call on this page. Desktop browsers (esp. Windows) can briefly\n      // hold the mic device through a navigation, so the first getUserMedia may\n      // throw NotReadableError \u2014 retry a few times instead of dropping to text.\n      // Mobile releases the device fast and succeeds on the first try (unchanged).\n      setTimeout(function() { pviResumeVoiceAfterNav(0); }, 800);\n    } else if (pviRestored && pviRestored.open) {\n      pviCheckHours();\n    }\n  })();\n\n  // Voice resume across page navigation, with retry. Keeps voice ON (matching the\n  // iPhone behavior) on PC by surviving the brief post-navigation \"mic busy\"\n  // window instead of falling straight back to text on the first failure.\n  // On mobile the first attempt succeeds, so the retry branch never runs.\n  function pviResumeVoiceAfterNav(attempt) {\n    currentMode = 'voice';\n    if (typeof updateModeToggleUI === 'function') updateModeToggleUI();\n    if (micBtn) micBtn.style.display = 'flex';\n    if (suggestions) suggestions.style.display = 'none';\n    try { stopAudio(); } catch (_) {}\n    pviSaveSession();\n    if (rtPC) return; // call already up \u2014 nothing to do\n    startRealtimeVoice().catch(function() {\n      if (attempt < 3) {\n        console.log('[PVI Voice] Resume attempt ' + (attempt + 1) + ' failed \u2014 retrying');\n        setTimeout(function() { pviResumeVoiceAfterNav(attempt + 1); }, 700 + attempt * 800);\n      } else {\n        console.log('[PVI Voice] Resume failed after retries \u2014 falling back to text');\n        setMode('text');\n      }\n    });\n  }\n\n  // \u2500\u2500 Destination greeting after a deliberate mode switch (TEXT) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // Unifies text with voice + phone: when the AI switched the customer to a\n  // tech-support page, the reload used to leave the thread silent and the panel\n  // hidden. Now we reopen the panel and post a product-confirming greeting so\n  // the conversation continues (\"Here is the <product> tech support page \u2014 how\n  // can I help you with it?\"). Localized; falls back to English.\n  var PVI_SWITCH_GREETINGS = {\n    en: { support: 'Here is the <strong>{product}</strong> tech support page \\u2014 I\\u2019ve got the manuals loaded. How can I help you with it?', sales: 'Here is the <strong>{product}</strong> page. What would you like to know about it?' },\n    es: { support: 'Aqu\\u00ed tienes la p\\u00e1gina de soporte t\\u00e9cnico de <strong>{product}</strong> \\u2014 ya tengo los manuales cargados. \\u00bfEn qu\\u00e9 puedo ayudarte con este?', sales: 'Aqu\\u00ed tienes la p\\u00e1gina de <strong>{product}</strong>. \\u00bfQu\\u00e9 te gustar\\u00eda saber sobre este?' },\n    it: { support: 'Ecco la pagina di assistenza tecnica di <strong>{product}</strong> \\u2014 ho gi\\u00e0 caricato i manuali. Come posso aiutarti con questo?', sales: 'Ecco la pagina di <strong>{product}</strong>. Cosa vuoi sapere su questo prodotto?' },\n    fr: { support: 'Voici la page d\\u2019assistance technique du <strong>{product}</strong> \\u2014 j\\u2019ai les manuels charg\\u00e9s. Comment puis-je vous aider avec celui-ci ?', sales: 'Voici la page du <strong>{product}</strong>. Que souhaitez-vous savoir \\u00e0 son sujet ?' },\n    de: { support: 'Hier ist die technische Support-Seite f\\u00fcr <strong>{product}</strong> \\u2014 die Handb\\u00fccher sind geladen. Wie kann ich Ihnen damit helfen?', sales: 'Hier ist die Seite zu <strong>{product}</strong>. Was m\\u00f6chten Sie dar\\u00fcber wissen?' },\n    pt: { support: 'Aqui est\\u00e1 a p\\u00e1gina de suporte t\\u00e9cnico do <strong>{product}</strong> \\u2014 j\\u00e1 carreguei os manuais. Como posso ajudar com ele?', sales: 'Aqui est\\u00e1 a p\\u00e1gina do <strong>{product}</strong>. O que gostaria de saber sobre ele?' },\n    ja: { support: '<strong>{product}</strong> \\u306e\\u30c6\\u30af\\u30cb\\u30ab\\u30eb\\u30b5\\u30dd\\u30fc\\u30c8\\u30da\\u30fc\\u30b8\\u3067\\u3059\\u3002\\u30de\\u30cb\\u30e5\\u30a2\\u30eb\\u3082\\u8aad\\u307f\\u8fbc\\u307f\\u6e08\\u307f\\u3067\\u3059\\u3002\\u3069\\u306e\\u3088\\u3046\\u306b\\u304a\\u624b\\u4f1d\\u3044\\u3057\\u307e\\u3057\\u3087\\u3046\\u304b\\uff1f', sales: '<strong>{product}</strong> \\u306e\\u30da\\u30fc\\u30b8\\u3067\\u3059\\u3002\\u4f55\\u304c\\u3054\\u8cea\\u554f\\u3067\\u3057\\u3087\\u3046\\u304b\\uff1f' },\n    zh: { support: '\\u8fd9\\u662f <strong>{product}</strong> \\u7684\\u6280\\u672f\\u652f\\u6301\\u9875\\u9762\\uff0c\\u624b\\u518c\\u5df2\\u52a0\\u8f7d\\u3002\\u6211\\u80fd\\u5e2e\\u4f60\\u505a\\u4ec0\\u4e48\\uff1f', sales: '\\u8fd9\\u662f <strong>{product}</strong> \\u9875\\u9762\\u3002\\u4f60\\u60f3\\u4e86\\u89e3\\u4ec0\\u4e48\\uff1f' },\n    ko: { support: '<strong>{product}</strong> \\uae30\\uc220 \\uc9c0\\uc6d0 \\ud398\\uc774\\uc9c0\\uc785\\ub2c8\\ub2e4. \\uba54\\ub274\\uc5bc\\uc744 \\ubd88\\ub7ec\\uc654\\uc2b5\\ub2c8\\ub2e4. \\ubb34\\uc5c7\\uc744 \\ub3c4\\uc640\\ub4dc\\ub9b4\\uae4c\\uc694?', sales: '<strong>{product}</strong> \\ud398\\uc774\\uc9c0\\uc785\\ub2c8\\ub2e4. \\ubb34\\uc5c7\\uc774 \\uad81\\uae08\\ud558\\uc138\\uc694?' },\n    ru: { support: '\\u042d\\u0442\\u043e \\u0441\\u0442\\u0440\\u0430\\u043d\\u0438\\u0446\\u0430 \\u0442\\u0435\\u0445\\u043f\\u043e\\u0434\\u0434\\u0435\\u0440\\u0436\\u043a\\u0438 <strong>{product}</strong> \\u2014 \\u0440\\u0443\\u043a\\u043e\\u0432\\u043e\\u0434\\u0441\\u0442\\u0432\\u0430 \\u0437\\u0430\\u0433\\u0440\\u0443\\u0436\\u0435\\u043d\\u044b. \\u0427\\u0435\\u043c \\u043f\\u043e\\u043c\\u043e\\u0447\\u044c?', sales: '\\u042d\\u0442\\u043e \\u0441\\u0442\\u0440\\u0430\\u043d\\u0438\\u0446\\u0430 <strong>{product}</strong>. \\u0427\\u0442\\u043e \\u0432\\u044b \\u0445\\u043e\\u0442\\u0438\\u0442\\u0435 \\u0443\\u0437\\u043d\\u0430\\u0442\\u044c?' },\n    ar: { support: '\\u0647\\u0630\\u0647 \\u0635\\u0641\\u062d\\u0629 \\u0627\\u0644\\u062f\\u0639\\u0645 \\u0627\\u0644\\u0641\\u0646\\u064a \\u0644\\u0640 <strong>{product}</strong> \\u2014 \\u0627\\u0644\\u0623\\u062f\\u0644\\u0629 \\u062c\\u0627\\u0647\\u0632\\u0629. \\u0643\\u064a\\u0641 \\u064a\\u0645\\u0643\\u0646\\u0646\\u064a \\u0645\\u0633\\u0627\\u0639\\u062f\\u062a\\u0643\\u061f', sales: '\\u0647\\u0630\\u0647 \\u0635\\u0641\\u062d\\u0629 <strong>{product}</strong>. \\u0645\\u0627\\u0630\\u0627 \\u062a\\u0648\\u062f \\u0623\\u0646 \\u062a\\u0639\\u0631\\u0641 \\u0639\\u0646\\u0647\\u061f' },\n    nl: { support: 'Hier is de technische-supportpagina van <strong>{product}</strong> \\u2014 de handleidingen staan klaar. Waarmee kan ik je helpen?', sales: 'Hier is de pagina van <strong>{product}</strong>. Wat wil je erover weten?' }\n  };\n  (function pviSwitchGreeting() {\n    if (!pviPendingSwitch) return;\n    var ps = pviPendingSwitch;\n    pviPendingSwitch = null; // consume once\n    pviSaveSession();\n    if (!ps.ts || (Date.now() - ps.ts) > 60000) return; // stale guard\n    // Only greet once we actually landed on the intended page.\n    if (ps.url) {\n      var slug = ps.url.replace('/pages/', '').toLowerCase();\n      if (location.pathname.toLowerCase().indexOf(slug) === -1) return;\n    }\n    var pack = PVI_SWITCH_GREETINGS[currentLang] || PVI_SWITCH_GREETINGS.en;\n    var tmpl = (ps.persona === 'support') ? pack.support : pack.sales;\n    var prod = pviEscapeHtml(ps.product || 'this product');\n    var msg = tmpl.replace('{product}', prod);\n    // Reopen the panel \u2014 a deliberate switch is the exception to the\n    // stay-hidden-on-navigation rule.\n    if (chatPanel) { chatPanel.classList.add('open'); pviCheckHours(); }\n    setTimeout(function() {\n      appendMessage('ai', msg);\n      chatHistory.push({ role: 'assistant', content: msg.replace(/<[^>]+>/g, '') });\n      hasInteracted = true;\n      pviSaveSession();\n      scrollToBottom();\n    }, 350);\n  })();\n\n  // Save right before navigation (same-tab links, nav menu, reload, tab close)\n  window.addEventListener('beforeunload', function() {\n    pviSaveSession();\n    // The conversation is ending (tab/widget close). Flush a FINAL transcript\n    // now (keepalive) so the worker emails the complete chat immediately \u2014 for\n    // BOTH voice and text. Guarded to a real exchange so we never email stubs.\n    if (chatHistory.length >= 2) { try { pviPushTranscript(true, true); } catch (_) {} }\n  });\n  window.addEventListener('pagehide', pviSaveSession);\n  document.addEventListener('visibilitychange', function() {\n    if (document.visibilityState === 'hidden') pviSaveSession();\n  });\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // AUDIO \u2014 persistent element, ordered slots, play token\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  var persistentAudio = null;\n  var audioPlaying = false;\n  var playToken = 0;\n  // Ordered slot system: TTS fetches happen in parallel, play in order\n  var ttsSlots = [];      // index \u2192 'pending' | Blob | 'failed' | null (consumed)\n  var nextSlotIdx = 0;    // next slot to allocate\n  var nextPlaySlot = 0;   // next slot to play\n\n  function ensureAudio() {\n    if (persistentAudio) return;\n    persistentAudio = new Audio();\n    persistentAudio.volume = 1.0;\n    persistentAudio.preload = 'auto';\n    // iOS unlock: play silently inside user gesture\n    persistentAudio.muted = true;\n    persistentAudio.src = 'data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjI5LjEwMAAAAAAAAAAAAAAA//tQwAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAACAAACcQCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgP////////////////////////////////////////////////////////////////8AAAAATGF2YzU4LjU0AAAAAAAAAAAAAAAAJAQAAAAAAAAAAnGTPq3MAAAAAAAAAAAAAAAAAAAA//sQwAADwAABpAAAACAAADSAAAAETEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV';\n    persistentAudio.play().then(function() {\n      persistentAudio.pause();\n      persistentAudio.currentTime = 0;\n      persistentAudio.muted = false;\n    }).catch(function() { persistentAudio.muted = false; });\n  }\n\n  function stopAudio() {\n    playToken++;\n    ttsSlots = [];\n    nextSlotIdx = 0;\n    nextPlaySlot = 0;\n    audioPlaying = false;\n    if (persistentAudio) {\n      try { persistentAudio.pause(); persistentAudio.currentTime = 0; } catch(e) {}\n    }\n    if (chatTextarea && currentMode === 'voice') chatTextarea.value = '';\n  }\n\n  function drainSlots() {\n    if (audioPlaying || !persistentAudio) return;\n    // Walk from nextPlaySlot, skip failed, stop at pending\n    while (nextPlaySlot < nextSlotIdx) {\n      var s = ttsSlots[nextPlaySlot];\n      if (s === 'pending') return; // wait for it\n      if (s === 'failed' || s === null) { nextPlaySlot++; continue; }\n      // Found a blob \u2014 play it\n      ttsSlots[nextPlaySlot] = null;\n      nextPlaySlot++;\n      playClip(s);\n      return;\n    }\n    // Queue empty AND not playing \u2192 restart mic if voice mode\n    if (!audioPlaying && currentMode === 'voice' && !micUserStopped) {\n      setTimeout(function() {\n        if (!audioPlaying && currentMode === 'voice' && !micUserStopped) {\n          if (chatTextarea) chatTextarea.value = '';\n          startMic();\n        }\n      }, 250);\n    }\n  }\n\n  function playClip(blob) {\n    audioPlaying = true;\n    var myToken = playToken;\n\n    // Always stop mic while TTS plays \u2014 prevents speaker audio feeding back\n    // into recognition and causing a barge-in loop. User can tap mic to barge in.\n    if (recognition && micActive) {\n      try { recognition.stop(); } catch(e) {}\n    }\n\n    var url = URL.createObjectURL(blob);\n    persistentAudio.volume = 1.0;\n    persistentAudio.muted = false;\n    persistentAudio.src = url;\n\n    var done = false;\n    function finishClip() {\n      if (done) return;\n      done = true;\n      URL.revokeObjectURL(url);\n      if (myToken !== playToken) { audioPlaying = false; return; }\n      audioPlaying = false;\n      drainSlots();\n    }\n\n    // Safety: if audio doesn't fire ended/error within 30s, unstick\n    var clipTimer = setTimeout(function() { finishClip(); }, 30000);\n    persistentAudio.onended = function() { clearTimeout(clipTimer); finishClip(); };\n    persistentAudio.onerror = function() { clearTimeout(clipTimer); finishClip(); };\n    persistentAudio.play().catch(function() { clearTimeout(clipTimer); finishClip(); });\n  }\n\n  function queueTTS(text) {\n    var clean = text.replace(/<[^>]+>/g, '').replace(/<!--.*?-->/g, '').trim();\n    if (!clean) return;\n    var slot = nextSlotIdx++;\n    ttsSlots[slot] = 'pending';\n    var myToken = playToken;\n\n    fetch(API_BASE + '/api/tts', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ text: clean })\n    }).then(function(r) {\n      if (!r.ok) throw new Error('tts');\n      return r.blob();\n    }).then(function(blob) {\n      if (myToken !== playToken) return; // stopAudio called, discard\n      ttsSlots[slot] = blob;\n      drainSlots();\n    }).catch(function() {\n      if (myToken === playToken) {\n        ttsSlots[slot] = 'failed';\n        drainSlots();\n      }\n    });\n  }\n\n  function speakText(text, onDone) {\n    queueTTS(text);\n    if (onDone) {\n      var iv = setInterval(function() {\n        if (!audioPlaying && nextPlaySlot >= nextSlotIdx) { clearInterval(iv); onDone(); }\n      }, 300);\n    }\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // SPEECH RECOGNITION \u2014 continuous, never stops in voice mode\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;\n  var recognition = null;\n  var micActive = false;\n  var micUserStopped = false; // user intentionally stopped mic\n\n  function hasMic() { return !!SpeechRecognition; }\n\n  function initRecognition() {\n    if (!SpeechRecognition) return;\n    recognition = new SpeechRecognition();\n    // iOS Safari has known issues with continuous=true; use false + auto-restart\n    var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);\n    recognition.continuous = !isIOS;\n    recognition.interimResults = true;\n    recognition.lang = navigator.language || 'en-US';\n    recognition.maxAlternatives = 1;\n\n    recognition.onstart = function() {\n      micActive = true;\n      if (micBtn) micBtn.classList.add('pvi-mic-active');\n      if (chatTextarea) chatTextarea.placeholder = 'Speak now...';\n    };\n\n    recognition.onresult = function(event) {\n      var final = '';\n      var interim = '';\n      for (var i = event.resultIndex; i < event.results.length; i++) {\n        if (event.results[i].isFinal) {\n          final += event.results[i][0].transcript;\n        } else {\n          interim += event.results[i][0].transcript;\n        }\n      }\n\n      if (chatTextarea) chatTextarea.value = final || interim;\n\n      // Barge-in: stop audio as soon as 2+ words detected\n      var wordCount = (interim || final).trim().split(/\\s+/).length;\n      if (wordCount >= 2 && audioPlaying) {\n        stopAudio();\n      }\n\n      if (final) {\n        var text = final.trim();\n        if (text.length > 0) {\n          stopAudio();\n          if (chatTextarea) chatTextarea.value = '';\n          setTimeout(function() { sendMessage(text); }, 100);\n        }\n      }\n    };\n\n    recognition.onerror = function(e) {\n      if (e.error === 'no-speech' || e.error === 'aborted') return;\n      console.warn('Speech error:', e.error);\n    };\n\n    recognition.onend = function() {\n      micActive = false;\n      if (micBtn) micBtn.classList.remove('pvi-mic-active');\n      // Only auto-restart if: voice mode, user didn't stop it, AI isn't talking\n      var queueDone = nextPlaySlot >= nextSlotIdx;\n      if (currentMode === 'voice' && !micUserStopped && !audioPlaying && queueDone) {\n        setTimeout(function() {\n          if (currentMode === 'voice' && !micUserStopped && !audioPlaying) startMic();\n        }, 300);\n      }\n    };\n  }\n\n  function requestMicEarly() { return Promise.resolve(); }\n\n  function startMic() {\n    if (!recognition) initRecognition();\n    if (!recognition) return;\n    if (micActive) return;\n    micUserStopped = false;\n    try { recognition.start(); } catch(e) {\n      // Already running \u2014 that's fine\n    }\n  }\n\n  function stopMic() {\n    micUserStopped = true;\n    if (recognition && micActive) {\n      try { recognition.stop(); } catch(e) {}\n    }\n    micActive = false;\n    if (micBtn) micBtn.classList.remove('pvi-mic-active');\n  }\n\n  function releaseMic() {\n    stopMic();\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // REALTIME VOICE (OpenAI WebRTC Realtime API)\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  var rtPC = null;            // RTCPeerConnection\n  var rtMicStream = null;     // local mic MediaStream\n  var rtAudioEl = null;       // <audio> for AI voice playback\n  var rtDataChannel = null;   // events channel\n  var rtConnected = false;\n  var rtPendingGreeting = null; // greeting function to call on session.created\n  var rtAISpeaking = false;     // true while AI is GENERATING (false at response.done)\n  // TRUE while audio is actually PLAYING through the speaker. response.done fires\n  // when GENERATION finishes \u2014 the audio keeps playing for seconds after. Deferred\n  // actions (persona restart, page navigation) must check THIS, not rtAISpeaking,\n  // or they cut the AI mid-sentence (Jul 30: John truncated on the transfer to Mary).\n  var rtAudioPlaying = false;\n  var rtMicHoldUntilDone = false; // keep mic MUTED for a programmatic greeting/\n  // confirmation the customer did NOT prompt (initial greeting, mode-switch\n  // confirmation, doc-loaded greeting). Right after a (re)connect the browser's\n  // echo canceller hasn't locked on yet, so a live mic hears the AI's own\n  // greeting, the server VAD fires, interrupt_response truncates it, and\n  // create_response starts the SAME greeting over \u2014 the \"Hi\u2026 Hi\u2026\" self-interrupt.\n  // For real answers (which the customer DID prompt) the mic stays live so\n  // barge-in keeps working.\n  var rtLastResponseDone = 0;   // timestamp of last response.done (cooldown guard)\n  var rtLastLinksInjected = '';  // (legacy) last transcript we injected links for\n  var rtLastLinkKey = '';        // dedup: last SET of link URLs injected (no sequential dupes)\n  var rtResponseCount = 0;      // count AI responses \u2014 skip link injection on greeting (first response)\n  var rtDocModelLoaded = null;   // model key for which docs are already loaded (avoid redundant fetches)\n  var rtDocLoading = false;      // true while a doc fetch is in flight\n  var rtStarting = false;        // true while startRealtimeVoice() is in flight (prevents double-start)\n  var rtStartedAt = 0;           // timestamp when voice session started (prevents early reconnect)\n\n  // \u2500\u2500 ECHO-SAFE MODE (car Bluetooth / speakerphone self-barge-in guard) \u2500\u2500\n  // On car BT and speakerphones the browser's echo canceller often FAILS\n  // (high-latency audio path, car does its own bad AEC), so the AI's own voice\n  // re-enters the mic, trips the server VAD, and interrupt_response truncates\n  // the AI mid-sentence \u2014 over and over (\"speaks, stops, restarts\"). We detect\n  // that loop: a REAL barge-in is followed by a real user transcript within a\n  // few seconds; an ECHO barge-in produces nothing (or a 1-word Whisper\n  // hallucination like \"you\"). Three phantom barge-ins = echo-safe mode ON for\n  // the rest of the session: the mic is muted while the AI speaks (no barge-in)\n  // and re-opens when the audio playback actually finishes.\n  var rtEchoSafe = false;        // true \u2192 mute mic during AI speech (no barge-in)\n  var rtEchoStrikes = 0;         // phantom barge-ins seen so far\n  var rtLastBargeAt = 0;         // timestamp of the last barge-in while AI spoke\n  var rtBargeGotTranscript = true; // did a real transcript follow the last barge-in?\n\n  function pviEchoStrike() {\n    // Called 4s after a barge-in if no real user transcript arrived \u2014 that\n    // interruption was the AI's own echo, not the customer.\n    if (rtBargeGotTranscript) return;\n    rtEchoStrikes++;\n    console.log('[PVI Voice] Phantom barge-in (no user speech followed) \u2014 strike ' + rtEchoStrikes + '/3');\n    if (rtEchoStrikes >= 3 && !rtEchoSafe) {\n      rtEchoSafe = true;\n      console.log('[PVI Voice] ECHO-SAFE MODE ON \u2014 speaker echo is self-interrupting the AI (car BT/speakerphone). Mic now mutes while the AI speaks; it reopens after each answer.');\n      // If the AI is speaking right now, mute immediately.\n      if (rtAISpeaking && rtMicStream) {\n        rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = false; });\n      }\n    }\n  }\n  \n\n  // Product links to auto-inject in voice mode when the AI mentions them\n  // URLs are relative so they work on any domain (new., www., bare)\n  var voiceAutoLinks = [\n    // RF Modulators\n    { keywords: ['minimod 2', 'minimod-2', 'mini mod 2', 'minimod 2 plus', 'minimod-2+', 'minimod two'],\n      label: 'VeCOAX MINIMOD-2+', url: '/products/vecoax-minimod-2-plus' },\n    { keywords: ['minimod 4k', 'minimod-4k', 'mini mod 4k', '4k modulator', '4k model', '4k version', '4k ultra'],\n      label: 'VeCOAX MINIMOD-4K', url: '/products/vecoax-minimod-4k' },\n    { keywords: ['ultra plus', 'ultra+', 'vecoax ultra', '4 channel modulator', '8 channel modulator', '4-channel modulator', '8-channel modulator'],\n      label: 'VeCOAX ULTRA+', url: '/products/vecoax-ultra' },\n    { keywords: ['micromod', 'micro mod', 'micromod 3', 'sdi modulator'],\n      label: 'VeCOAX MICROMOD-3 SDI', url: '/products/vecoax-micromod-3-sdi' },\n    { keywords: ['blade', 'miniblade', 'mini blade', 'blade kit', 'blade rack', '10 unit', 'ten unit'],\n      label: 'VeCOAX MINI BLADE', url: '/products/vecoax-mini-blade' },\n    { keywords: ['rack panel', 'rack mount', '19 inch', '19-inch', '1ru', '1 ru', 'rack space', 'panel alone'],\n      label: '1RU Rack Panel', url: '/products/1ru-rack-panel-hdmi-modulator' },\n    { keywords: ['ip mod', 'ipmod', 'ip modulator', 'ip to coax', 'ip to rf'],\n      label: 'IP-MOD 4X IP to Coax RF Modulator', url: '/products/ip-mod-4x-ip-to-coax-rf-modulator' },\n    { keywords: ['rack 2', 'rack-2', 'bundle with 2', 'two channel bundle', '2 channel bundle'],\n      label: 'VeCOAX RACK-2', url: '/products/vecoax-rack-2' },\n    { keywords: ['rack 4', 'rack-4', 'bundle with 4', 'four channel bundle', '4 channel bundle'],\n      label: 'VeCOAX RACK-4', url: '/products/vecoax-rack-4' },\n    { keywords: ['3 pack', '3-pack', 'three pack'],\n      label: 'MINIMOD-2+ 3-Pack', url: '/products/vecoax-minimod-2-plus-3-pack' },\n    { keywords: ['4 pack', '4-pack', 'four pack'],\n      label: 'MINIMOD-2+ 4-Pack', url: '/products/vecoax-minimod-2-plus-4-pack' },\n    // IPTV Encoders\n    { keywords: ['vecaster hd h.264', 'vecaster hd h264', 'vecaster hd encoder', 'hd h264 encoder', 'hd h.264 encoder'],\n      label: 'VeCASTER HD H.264', url: '/products/vecaster-hd-h264-hdmi-iptv-encoder' },\n    { keywords: ['vecaster hd hevc', 'vecaster hd h.265', 'hd hevc encoder'],\n      label: 'VeCASTER HD HEVC', url: '/products/vecaster-hd-hevc-hdmi-iptv-encoder' },\n    { keywords: ['vecaster 4k hevc', 'vecaster 4k encoder', '4k hevc encoder', '4k iptv encoder'],\n      label: 'VeCASTER 4K HEVC', url: '/products/vecaster-4k-hevc-hdmi-iptv-encoder' },\n    { keywords: ['vecaster 4k pro', '4k pro encoder', '12g sdi', '4k60 encoder'],\n      label: 'VeCASTER 4K-PRO', url: '/products/vecaster-4k-pro-12g-sdi-hdmi-iptv-encoder' },\n    { keywords: ['vecaster sdi hevc', 'vecaster hd sdi', 'sdi hevc encoder', 'sdi encoder'],\n      label: 'VeCASTER HD SDI HEVC', url: '/products/vecaster-hd-sdi-hevc-iptv-encoder' },\n    { keywords: ['vecaster pro hdmi', 'ndi encoder hdmi', 'srt encoder hdmi', 'vecaster pro encoder'],\n      label: 'VeCASTER PRO HDMI', url: '/products/vecaster-pro-hdmi-ndi-srt-iptv-encoder' },\n    { keywords: ['vecaster pro sdi', 'ndi encoder sdi', 'srt encoder sdi'],\n      label: 'VeCASTER PRO SDI', url: '/products/vecaster-pro-sdi-ndi-srt-iptv-encoder' },\n    // Multichannel Encoders\n    { keywords: ['vecoder hd h264', 'vecoder hd h.264', 'vecoder hd encoder', 'multichannel encoder hd'],\n      label: 'VeCODER HD H.264', url: '/products/vecoder-hd-h264-multichannel-iptv-encoder' },\n    { keywords: ['vecoder hd hevc', 'vecoder hd h.265', 'multichannel hevc'],\n      label: 'VeCODER HD HEVC', url: '/products/vecoder-hd-hevc-multichannel-iptv-encoder' },\n    { keywords: ['vecoder 4k', 'multichannel 4k encoder'],\n      label: 'VeCODER 4K', url: '/products/vecoder-4k-h264-multichannel-iptv-encoder' },\n    { keywords: ['vecoder sdi', 'multichannel sdi encoder'],\n      label: 'VeCODER SDI', url: '/products/vecoder-sdi-multichannel-iptv-encoder' },\n    // IPTV Decoders\n    { keywords: ['ipdecoder', 'ip decoder', 'budget decoder', 'cheap decoder'],\n      label: 'IPdecoder HD 4K', url: '/products/ipdecoder-hd-4k-ip-to-hdmi-decoder' },\n    { keywords: ['veplayer 4k', 'veplayer decoder', 'iptv decoder'],\n      label: 'VePLAYER 4K', url: '/products/veplayer-professional-hd-4k-iptv-decoder-hevc-h-265-h-264-api-telnet-remote-control' },\n    { keywords: ['veplayer pro', 'ndi decoder', 'srt decoder'],\n      label: 'VePLAYER PRO', url: '/products/veplayer-pro-ip-video-decoder-ndi' },\n    { keywords: ['veplayer quad', 'quad decoder', '4 stream decoder', 'four stream decoder'],\n      label: 'VePLAYER-HDMI-QUAD', url: '/products/veplayer-hdmi-quad-professional-hd-4k-iptv-decode-4-streams' },\n    { keywords: ['veplayer sdi', 'broadcast decoder'],\n      label: 'VePLAYER-SDI', url: '/products/veplayer-sdi-broadcast-professional-3g-hd-sdi-video-decoder' },\n    { keywords: ['veplayer ultra', 'ultra 4 rack', 'rack decoder'],\n      label: 'VePLAYER ULTRA-4 RACK', url: '/products/veplayer-ultra-4-rack-ip-video-decoder' },\n    // INFINIUM Modular System\n    { keywords: ['infinium chassis', 'infinium 4k chassis', 'infinium headend', 'modular chassis', 'infinium system', 'infinium 4k'],\n      label: 'INFINIUM 4K Chassis', url: '/products/infinium-4k-hd-video-distribution-chassis' },\n    { keywords: ['infinium h.264', 'infinium h264', 'infinium 1080p h.264', '1080p h.264 card', '1080 h264 card'],\n      label: 'INFINIUM 1080p H.264 Card', url: '/products/infinium-encoder-card-1080p-h264-hdmi' },\n    { keywords: ['infinium hevc card', 'infinium 1080p hevc', 'infinium 1080 hevc', '1080p hevc card'],\n      label: 'INFINIUM 1080p HEVC Card', url: '/products/infinium-encoder-card-1080p-h265-hdmi' },\n    { keywords: ['infinium 4k30', 'infinium 4k hevc', '4k30 card', '4k encoder card'],\n      label: 'INFINIUM 4K30 HEVC Card', url: '/products/infinium-encoder-card-4k-hevc-hdmi' },\n    { keywords: ['infinium 4k60', 'infinium broadcast', 'infinium 12g', 'infinium flagship', '4k60 card', '4k60 broadcast'],\n      label: 'INFINIUM 4K60 Broadcast Card', url: '/products/infinium-encoder-card-4k-60-hevc-hdmi' },\n    { keywords: ['infinium sdi', 'infinium 3g-sdi', 'infinium 3g sdi', 'sdi hevc card', '3g-sdi card'],\n      label: 'INFINIUM 3G-SDI HEVC Card', url: '/products/infinium-encoder-card-3g-sdi-hevc' },\n    { keywords: ['infinium coax', 'infinium rf', 'infinium modulator', '4k over coax module', '4k-over-coax', 'rf module', 'coax module', 'infinium rf module'],\n      label: 'INFINIUM 4K-Over-Coax RF Module', url: '/products/infinium-4k-over-coax-out-rf-modulator-card' },\n    // AV over IP (VuMATRIX). TX and RX BOTH carry the generic terms, so any\n    // mention of \"vumatrix\" surfaces BOTH the transmitter and the receiver\n    // (the system needs both). Specific terms still work too.\n    { keywords: ['vumatrix', 'vu matrix', 'video matrix', 'hdmi matrix', 'av over ip', 'hdmi over ip', 'vumatrix transmitter', 'vumatrix tx', 'vumatrix fx tx'],\n      label: 'VuMATRIX FX Transmitter', url: '/products/vumatrix-fx-transmitter-4k-hdmi-over-ip-extender' },\n    { keywords: ['vumatrix', 'vu matrix', 'video matrix', 'hdmi matrix', 'av over ip', 'hdmi over ip', 'vumatrix receiver', 'vumatrix rx', 'vumatrix fx rx'],\n      label: 'VuMATRIX FX Receiver', url: '/products/vumatrix-fx-receiver-4k-hdmi-over-ip-extender' },\n    { keywords: ['2x2', '2 x 2', '2 by 2', 'two by two'],\n      label: 'VuMATRIX 4K Video Wall 2\\u00d72 Kit', url: '/products/vumatrix-4k-video-wall-2x2' },\n    { keywords: ['3x3', '3 x 3', '3 by 3', 'three by three'],\n      label: 'VuMATRIX 4K Video Wall 3\\u00d73 Kit', url: '/products/vumatrix-4k-video-wall-3x3' },\n    { keywords: ['4x4', '4 x 4', '4 by 4', 'four by four'],\n      label: 'VuMATRIX 4K Video Wall 4\\u00d74 Kit', url: '/products/vumatrix-4k-video-wall-4x4' },\n    { keywords: ['video wall', 'videowall', 'video walls'],\n      label: 'VuMATRIX Video Wall Kits', url: '/collections/vumatrix-4k-video-wall-kits' },\n    // Modular\n    { keywords: ['infinium', 'modular system', 'modular chassis', 'headend chassis'],\n      label: 'INFINIUM 4K Chassis', url: '/products/infinium-4k-hd-video-distribution-chassis' },\n    // Accessories\n    { keywords: ['splitter', 'combiner', 'coax splitter', 'coaxial splitter', '13 port', '13-port', 'coax mixer'],\n      label: 'Coax Combiner/Splitter', url: '/products/coax-combiner-splitter' }\n  ];\n\n  // Tech support page links \u2014 injected when AI discusses setup/troubleshooting\n  var voiceSupportPages = [\n    { keywords: ['minimod 2', 'minimod-2', 'mini mod 2', 'minimod 2 plus', 'minimod-2+', 'minimod two'],\n      label: 'MINIMOD-2+ Tech Support', url: '/pages/tech-support-page-vecoax-minimod-2-plus' },\n    { keywords: ['minimod 4k', 'minimod-4k', 'mini mod 4k'],\n      label: 'MINIMOD-4K Tech Support', url: '/pages/tech-support-page-vecoax-minimod-4k' },\n    { keywords: ['rack 2', 'rack-2', 'rack 4', 'rack-4'],\n      label: 'RACK-2/4 Tech Support', url: '/pages/tech-support-for-vecoax-rack-2-rack-4-multi-channel-hdmi-rf-modulators-qam-atsc-isdbt-dvbt-dvbc-for-hdmi-over-coaxial-tv-channels-distribution-extenders' },\n    { keywords: ['blade', 'miniblade', 'mini blade'],\n      label: 'BLADE Tech Support', url: '/pages/tech-support-for-vecoax-blade-multi-channel-hdmi-rf-modulators-qam-atsc-isdbt-dvbt-dvbc-for-hdmi-over-coaxial-tv-channels-distribution-extenders' },\n    { keywords: ['ultra plus', 'ultra+', 'vecoax ultra'],\n      label: 'ULTRA+ Tech Support', url: '/pages/tech-support-for-vecoax-ultra-plus-hdmi-sdi-qam-atsc-dvbt-isdbt-dvbc-digital-television-broadcast-hdtv-rf-encoders-modulators' },\n    { keywords: ['micromod 3 plus', 'micromod-3+', 'micromod 3+', 'micromod three plus'],\n      label: 'MICROMOD-3+ Tech Support', url: '/pages/tech-support-page-vecoax-micromod-3-plus' },\n    { keywords: ['micromod 3 sdi', 'micromod-3 sdi', 'micromod sdi', 'micromod-3+ sdi'],\n      label: 'MICROMOD-3+ SDI Tech Support', url: '/pages/tech-support-page-vecoax-micromod-3-plus-sdi' },\n    { keywords: ['infinium', 'modular chassis', 'headend chassis'],\n      label: 'INFINIUM Tech Support', url: '/pages/tech-support-for-infinium-hd-4k-8k-encoder-modulator-iptv-streaming-video-broadcast-distribution-headend-platform-for-qam-atsc-isdbt-dvbt-dvbc-isdbt-iptv' },\n    { keywords: ['vecaster', 'vecaster hd', 'vecaster 4k', 'vecaster pro', 'vecaster sdi'],\n      label: 'VeCASTER Tech Support', url: '/pages/tech-support-for-vecaster-hdmi-sdi-encoder-for-iptv-live-streaming-hdmi-sdi-h264-h265-hevc-srt-ndi-rtmp-rtsp-m3u' },\n    { keywords: ['vecoder 4k', 'vecoder ultra 4k'],\n      label: 'VeCODER 4K Tech Support', url: '/pages/tech-support-for-vecoder-4k-hdmi-sdi-encoder-for-iptv-live-streaming-hdmi-sdi-h264-h265-hevc-srt-ndi-rtmp-rtsp-m3u' },\n    { keywords: ['vecoder', 'multichannel encoder'],\n      label: 'VeCODER HD Tech Support', url: '/pages/tech-support-for-vecoder-hdmi-sdi-encoder-for-iptv-live-streaming-hdmi-sdi-h264-h265-hevc-srt-ndi-rtmp-rtsp-m3u' },\n    { keywords: ['veplayer', 'veplayer 4k', 'veplayer pro', 'veplayer quad', 'veplayer sdi', 'iptv decoder'],\n      label: 'VePLAYER Tech Support', url: '/pages/tech-support-for-veplayer-hd-4k-iptv-ip-streaming-decoder-for-professional-live-streaming-decoding-of-any-format-including-h264-h265-hevc-ac3-rtmp-rtsp-udp-rtp-rtsp-srt-ndi-and-more' },\n    { keywords: ['veplayer ultra', 'ultra 4 rack', 'veplayer rack'],\n      label: 'VePLAYER ULTRA-4 Tech Support', url: '/pages/tech-support-for-veplayer-4-rack-multi-channel-hd-4k-iptv-ip-streaming-decoder' },\n    { keywords: ['vumatrix', 'vu matrix', 'video matrix', 'hdmi over ip', 'av over ip'],\n      label: 'VuMATRIX Tech Support', url: '/pages/tech-support-for-vumatrix-av-over-ip-4k-hdmi-video-matrix-system-with-video-wall-features' },\n    // IP-MOD 4X has no dedicated support page yet \u2014 the product page (with the\n    // manuals) is the worker's official support destination for it too.\n    { keywords: ['ip mod', 'ipmod', 'ip modulator', 'ip to coax', 'ip to rf'],\n      label: 'IP-MOD 4X Tech Support', url: '/products/ip-mod-4x-ip-to-coax-rf-modulator' }\n  ];\n\n  var SUPPORT_CONTEXT_RE = /\\b(set ?up|setting up|install|configur|troubleshoot|firmware|update|upgrade|factory reset|default ip|ip address|static ip|web interface|login|log ?in|password|credential|no signal|not work|won't|doesn't|broken|channel scan|rescan|vlc|m3u8|rtmp|rtsp|hls|srt|ndi|igmp|multicast|jumbo frame|dhcp|subnet|quick start|manual|documentation|how to connect|how do i|help with my|support)\\b/i;\n  var rtLastSupportLinkUrl = '';\n  var rtLastSalesNavUrl = '';    // last sales page-follow destination (no repeat nav)\n\n  // \u2500\u2500 Product suggestion \"offer\" cards (shop.provideoinstruments.com widget) \u2500\u2500\n  // When we suggest a product we show an EXPLICIT offer \u2014 \"Click here to see\n  // this solution:\" \u2014 with the product link and, when the category is clear,\n  // a \"See all \u2026\" collection link. BOTH open in a NEW TAB on the new www.\n  // website, so the chat stays open and the customer can come back and click\n  // any other link. Nothing ever navigates on its own.\n  var PVI_WWW_BASE = 'https://www.provideoinstruments.com';\n  var PVI_CATEGORY_LINKS = [\n    { label: 'See all HDMI RF Modulators',\n      url: PVI_WWW_BASE + '/collections/hdmi-rf-modulators',\n      urlRe: /minimod|micromod|vecoax-rack|vecoax-ultra|mini-blade|1ru-rack-panel|coax-combiner/i,\n      textRe: /modulator|over coax|coax channel|rf channel|hdmi to coax/i },\n    { label: 'See all IPTV Encoders & Decoders',\n      url: PVI_WWW_BASE + '/collections/video-streaming-encoders-decoders',\n      urlRe: /vecaster|vecoder|veplayer|ipdecoder/i,\n      textRe: /encoder|decoder|iptv|streaming/i },\n    { label: 'See all AV over IP / VuMATRIX',\n      url: PVI_WWW_BASE + '/collections/av-over-ip-vumatrix',\n      urlRe: /vumatrix|video-wall/i,\n      textRe: /\\bmatrix\\b|av over ip|hdmi over ip|video wall/i }\n  ];\n\n  function pviProductAbsUrl(u) {\n    if (!u) return u;\n    if (/^https?:\\/\\//i.test(u)) return u;      // already absolute \u2014 leave as-is\n    if (u.charAt(0) === '/') return PVI_WWW_BASE + u;\n    return u;\n  }\n  // Pick the \"See all\" collection: by the offered product's URL first (most\n  // reliable), then by what the conversation text talks about.\n  function pviCategoryFor(url, text) {\n    var i;\n    for (i = 0; i < PVI_CATEGORY_LINKS.length; i++) {\n      if (PVI_CATEGORY_LINKS[i].urlRe.test(url || '')) return PVI_CATEGORY_LINKS[i];\n    }\n    for (i = 0; i < PVI_CATEGORY_LINKS.length; i++) {\n      if (PVI_CATEGORY_LINKS[i].textRe.test(text || '')) return PVI_CATEGORY_LINKS[i];\n    }\n    return null;\n  }\n  // Render the offer card: product link + optional category \"See all\" link.\n  // Both new-tab. Deduped (consecutive) so the same product isn't re-offered.\n  // In TEXT mode the card also offers a FREE QUOTE \u2014 and that question is\n  // appended to chatHistory as an assistant message, so when the customer\n  // replies \"yes\" the worker AI sees its own question and runs the normal\n  // quote flow (name, email, quantity, configuration). Voice is excluded:\n  // the realtime AI never saw the injected text and would be confused; it\n  // offers quotes by itself in conversation.\n  var PVI_QUOTE_OFFER_TEXT = 'Good news \u2014 this product is in stock, and you can get it as soon as tomorrow if needed. Would you like a free quote for your reference?';\n  // Aug 19 transcript audit \u2014 the injected offer card misfired three ways:\n  // 1. During SUPPORT TRIAGE (\"are you looking for technical support for a unit\n  //    you already own?\") the appended quote question hijacked the customer's\n  //    \"yes\" into the quote-collection flow (Samsung-TVs MINIMOD chat).\n  // 2. After \"that product is discontinued / no catalog match\" the card said\n  //    \"Good news \u2014 this product is in stock!\" (VeCOAX 4K-12 chats).\n  // 3. Right after a quote was already emailed, the nudge created a DUPLICATE\n  //    quote when the customer answered \"Yes please\" (UK VeCASTER chat).\n  // Suppress the whole card when the reply is support triage / a denial, and\n  // suppress the quote line whenever the reply already talks about a quote.\n  var PVI_OFFER_SUPPRESS_REPLY_RE = /technical support|tech support|troubleshoot|support ticket|already own|discontinu|no longer (?:available|offered|carr)|not (?:part|one) of our|no catalog match|couldn'?t find a match|not a product we/i;\n  var PVI_OFFER_SUPPRESS_PROBLEM_RE = /not (?:work|display|show|stream|power|respond)|no (?:signal|video|audio|data|picture|power)|\\berror\\b|issue with|problem with|defective|broken|repair|\\brma\\b|firmware|blinking|frozen|stopped/i;\n  function pviShowProductOffer(label, url, contextText) {\n    if (!label || !url) return false;\n    var abs = pviProductAbsUrl(url);\n    if (pviLinkShown(abs)) return false;\n    pviMarkLinkShown(abs);\n    var isVoice = (currentMode === 'voice');\n    // DEDUPE: the AI's own reply now (mandatorily) ends with the free-quote\n    // offer. If it already asked, do NOT ask again in the card \u2014 two quote\n    // pitches back-to-back reads as a pushy bot that isn't listening\n    // (Jul 13 Palm Springs church transcript).\n    // Broadened (Aug 19): ANY quote talk in the reply \u2014 an offer, a quote just\n    // emailed (\"Your quote L\u2026 has been emailed\"), or the collection questions \u2014\n    // means a second pitch is wrong (it created a duplicate quote on Aug 18).\n    var aiAlreadyOffersQuote = /\\bquote\\b/i.test(contextText || '');\n    var linkStyle = 'color:#0071E3;text-decoration:underline;font-weight:600;display:block;padding:3px 0';\n    // color is EXPLICIT: the shop widget is dark-themed (white text) and the\n    // card background is light \u2014 inherited text would be invisible.\n    var html =\n      '<div class=\"pvi-offer-card\" style=\"margin-top:6px;padding:10px 12px;background:#f0f7ff;border-left:3px solid #0071E3;border-radius:6px;font-size:0.95em;color:#1d1d1f\">' +\n        '<div style=\"margin-bottom:6px;font-weight:600\">Click here to see this solution:</div>' +\n        '<a href=\"' + abs + '\" target=\"_blank\" rel=\"noopener\" style=\"' + linkStyle + '\">\\u2192 ' + pviEscapeHtml(label) + '</a>';\n    var cat = pviCategoryFor(abs, contextText || '');\n    if (cat) {\n      html += '<a href=\"' + cat.url + '\" target=\"_blank\" rel=\"noopener\" style=\"' + linkStyle + '\">\\u2192 ' + pviEscapeHtml(cat.label) + '</a>';\n    }\n    if (!isVoice && !aiAlreadyOffersQuote) {\n      html += '<div style=\"margin-top:8px\">' + pviEscapeHtml(PVI_QUOTE_OFFER_TEXT) + '</div>';\n    }\n    html += '</div>';\n    appendMessage('ai', html);\n    if (!isVoice && !aiAlreadyOffersQuote) {\n      // Make the quote offer part of the AI's history, so a \"yes\" reply is\n      // understood by the worker and starts the quote-gathering flow.\n      chatHistory.push({ role: 'assistant', content: PVI_QUOTE_OFFER_TEXT });\n      pviSaveSession();\n    }\n    scrollToBottom();\n    return true;\n  }\n  // TEXT mode: after an AI reply, offer the most relevant product. Prefer the\n  // product the AI itself linked ([label](/products/handle) \u2014 its chosen \"most\n  // relevant\"); if it linked none, fall back to keyword-scanning the reply.\n  // Never in support context (a product offer is a pre-sales action).\n  // When the card is shown, the AI's generic trailing \"Would you like more\n  // details\u2026?\" question is removed from the bubble \u2014 the card replaces it.\n  function pviTextMaybeOffer(text, bubbleEl) {\n    if (!text) return;\n    var actuallySupport = (chosenPersona === 'support') || (pviCfg.persona === 'support') || /tech-support/i.test(location.pathname);\n    if (actuallySupport) return;\n    // Support triage / discontinued-product replies never get an offer card,\n    // and neither does a conversation where the customer is reporting a\n    // problem with a unit they own (see notes at PVI_OFFER_SUPPRESS_REPLY_RE).\n    if (PVI_OFFER_SUPPRESS_REPLY_RE.test(text)) return;\n    var lastCustomerMsg = '';\n    for (var h = chatHistory.length - 1; h >= 0; h--) {\n      if (chatHistory[h] && chatHistory[h].role === 'user') { lastCustomerMsg = chatHistory[h].content || ''; break; }\n    }\n    if (PVI_OFFER_SUPPRESS_PROBLEM_RE.test(lastCustomerMsg)) return;\n    // Offer the product mentioned EARLIEST in the reply \u2014 that is the AI's\n    // primary recommendation. (A later product may carry the markdown link\n    // while the first mention is only bolded \u2014 text position wins, not link.)\n    var best = null; // { pos, label, url }\n    var m = text.match(/\\[([^\\]]+)\\]\\(([^)]*\\/products\\/[^)\\s]+)\\)/);\n    if (m) best = { pos: text.indexOf(m[0]), label: m[1], url: m[2] };\n    // Keyword scan on 1:1-normalized text (dashes\u2192spaces, same length), so\n    // match positions are directly comparable with the markdown-link position.\n    var normLower = text.toLowerCase().replace(/[\\u2010-\\u2015\\-]/g, ' ');\n    for (var i = 0; i < voiceAutoLinks.length; i++) {\n      var item = voiceAutoLinks[i];\n      for (var k = 0; k < item.keywords.length; k++) {\n        var kw = item.keywords[k].toLowerCase().replace(/[\\u2010-\\u2015\\-]/g, ' ');\n        var pos = normLower.indexOf(kw);\n        if (pos !== -1 && (!best || pos < best.pos)) {\n          best = { pos: pos, label: item.label, url: item.url };\n        }\n      }\n    }\n    var shown = false;\n    if (best) shown = pviShowProductOffer(best.label, best.url, text);\n    if (shown && bubbleEl) {\n      try {\n        bubbleEl.innerHTML = bubbleEl.innerHTML.replace(\n          /(?:<br\\s*\\/?>\\s*)*Would you like (?:more )?(?:details|info|information)[^<]*\\?\\s*$/i, '');\n      } catch (_) {}\n    }\n  }\n\n  function injectVoiceLinks(transcript) {\n    if (!transcript) return;\n    if (rtResponseCount <= 1) return; // never inject links on the greeting\n    var lower = transcript.toLowerCase();\n    // Hyphen/whitespace-insensitive matching. Official product names use hyphens\n    // (\"VeCASTER 4K-PRO\", \"MINIMOD-2+\", \"RACK-2\", \"12G-SDI\") and the voice AI is told\n    // to say the EXACT name, so the transcript reads \"4k-pro\" while the keyword table\n    // uses \"4k pro\" \u2014 a raw substring check silently misses it and the promised link\n    // never appears. Normalize all dashes -> spaces (and collapse spaces) on BOTH sides.\n    var normLower = lower.replace(/[\\u2010-\\u2015\\-]/g, ' ').replace(/\\s+/g, ' ');\n    var kwHit = function (kw) {\n      return normLower.indexOf(kw.replace(/[\\u2010-\\u2015\\-]/g, ' ').replace(/\\s+/g, ' ')) !== -1;\n    };\n    // Only show support links when we're ACTUALLY in support mode (persona=support\n    // or on a tech-support page), NOT based on word matching in the AI response.\n    var actuallySupport = (chosenPersona === 'support') ||\n      (pviCfg.persona === 'support') ||\n      /tech-support/i.test(location.pathname);\n    var isSupportContext = actuallySupport && SUPPORT_CONTEXT_RE.test(lower);\n    // The AI explicitly PROMISED a link (\"let me drop that link\", \"I'll put the\n    // link in the chat\"). A promised link MUST appear \u2014 bypass the dedupe,\n    // otherwise the customer sees a promise and no link.\n    var promisedLink = /\\blink\\b/i.test(lower) &&\n      /(drop|put|post|paste|shar|send|add|placing|here is|here's|in the chat)/i.test(lower);\n\n    if (isSupportContext || (actuallySupport && (pviIsSupportPage || promisedLink))) {\n      // SUPPORT MODE: show tech support page link. Match by AI response keywords\n      // OR by current page URL (if we're already on a support page).\n      var supportMatch = null;\n      // First try matching from the AI response text\n      for (var s = 0; s < voiceSupportPages.length; s++) {\n        var sp = voiceSupportPages[s];\n        for (var sk = 0; sk < sp.keywords.length; sk++) {\n          if (kwHit(sp.keywords[sk])) {\n            supportMatch = sp;\n            break;\n          }\n        }\n        if (supportMatch) break;\n      }\n      // If no match from AI text, try matching by current page URL\n      if (!supportMatch && pviIsSupportPage) {\n        var pathLow = location.pathname.toLowerCase();\n        for (var sp2 = 0; sp2 < voiceSupportPages.length; sp2++) {\n          if (pathLow.indexOf(voiceSupportPages[sp2].url.replace('/pages/', '').toLowerCase()) !== -1) {\n            supportMatch = voiceSupportPages[sp2];\n            break;\n          }\n        }\n      }\n      if (supportMatch && (promisedLink ||\n          (supportMatch.url !== rtLastSupportLinkUrl && !pviLinkShown(supportMatch.url)))) {\n        rtLastSupportLinkUrl = supportMatch.url;\n        pviMarkLinkShown(supportMatch.url);\n        console.log('[PVI Voice] Injecting support page link:', supportMatch.label);\n        appendMessage('ai', '<div style=\"font-size:0.9em;margin-top:6px;padding:8px 12px;background:#f0f7ff;border-left:3px solid #0071E3;border-radius:4px\">' +\n          '<strong>\\uD83D\\uDEE0\\uFE0F Tech Support Page</strong> \\u2014 manuals, quick start guides & software updates:<br>' +\n          '<a href=\"' + supportMatch.url + '\" style=\"color:#0071E3;text-decoration:underline;font-weight:600\">' + supportMatch.label + '</a></div>');\n        scrollToBottom();\n      }\n      return; // do NOT show product page links in support context\n    }\n\n    // PRE-SALES MODE: only show product page links, never support page links\n    var matched = [];\n    var seenUrls = {};\n    voiceAutoLinks.forEach(function(item) {\n      for (var i = 0; i < item.keywords.length; i++) {\n        if (kwHit(item.keywords[i]) && !seenUrls[item.url] &&\n            (promisedLink || !pviLinkShown(item.url))) {\n          seenUrls[item.url] = true;\n          matched.push(item);\n          break;\n        }\n      }\n    });\n    // Ambiguous bare \"VeCASTER Pro\" (no 4K / HDMI / SDI qualifier) matches no specific\n    // model above \u2014 \"Pro\" alone could be the PRO HDMI or the 4K-PRO. If the AI said\n    // \"VeCASTER\" and \"Pro\" but we matched no VeCASTER product, offer the VeCASTER\n    // collection so the customer can choose (instead of a promised link that never shows).\n    var matchedVecaster = matched.some(function(m) { return m.url.indexOf('vecaster') !== -1; });\n    if (!matchedVecaster && /\\bvecaster\\b/.test(normLower) && /\\bpro\\b/.test(normLower)) {\n      var vcUrl = '/collections/vecaster-iptv-encoders';\n      if (!seenUrls[vcUrl] && (promisedLink || !pviLinkShown(vcUrl))) {\n        seenUrls[vcUrl] = true;\n        matched.push({ label: 'VeCASTER IPTV Encoders', url: vcUrl });\n      }\n    }\n    // \u2500\u2500 PAGE-FOLLOW (restored Jul 30 2026, user decree: \"one of the best\n    // functions we had\"; extended to the WIDGET Jul 31) \u2500\u2500 the PAGE follows the\n    // sales voice conversation: when Lisa's reply clearly centers on exactly\n    // ONE product, open its page once her line finishes playing.\n    // - www theme: same-tab navigation (chat + voice survive and resume there).\n    // - WIDGET: pviExecutePendingNav is build-patched to window.__pviNav, which\n    //   opens the www page in a NEW TAB (popup-blocked \u2192 queued for next tap);\n    //   the host page and the live voice call stay untouched.\n    // This scan runs WITHOUT the link-shown dedupe \u2014 a product whose link card\n    // already appeared must still be able to pull the page when the customer\n    // picks it.\n    // NEVER yank the page during active quote/lead collection \u2014 the navigation\n    // restarts the session at the worst moment (Jul 30: navigated ON the quote\n    // recap; even with the transcript handoff, moving the page mid-collection\n    // is disorienting). Markers target COLLECTION questions/recaps, not the\n    // mere word \"quote\" (an info reply offering a free quote must still follow).\n    var pviCollecting = /\\b(recap|shall i (go ahead|send)|what'?s? (the |your )?(best )?(name|email|phone)|name (for|on|i should put on) the quote|how many (units|pieces)|shipping address|billing address|quote number|already in your inbox)\\b/i.test(lower);\n    if (currentMode === 'voice' && !pviCollecting &&\n        !rtPendingNavUrl && !rtPendingPersonaRestart) {\n      var navSeen = {};\n      var navUrls = [];\n      voiceAutoLinks.forEach(function(item) {\n        for (var nk = 0; nk < item.keywords.length; nk++) {\n          if (kwHit(item.keywords[nk])) {\n            if (!navSeen[item.url]) { navSeen[item.url] = true; navUrls.push(item.url); }\n            break;\n          }\n        }\n      });\n      // Exactly ONE product in the reply = unambiguous focus. Two or more =\n      // the customer is still choosing (e.g. \"we have the MINIMOD-2+ and the\n      // MINIMOD-4K\") \u2014 stay put.\n      if (navUrls.length === 1 && navUrls[0] !== rtLastSalesNavUrl &&\n          location.pathname.toLowerCase().indexOf(navUrls[0].toLowerCase()) === -1) {\n        rtLastSalesNavUrl = navUrls[0];\n        console.log('[PVI Voice] Sales page-follow \\u2192', navUrls[0]);\n        pviScheduleNavAfterSpeech(navUrls[0]);\n      }\n    }\n\n    if (matched.length === 0) return;\n    // Offer the SINGLE most relevant product (first match) as an explicit\n    // \"Click here to see this solution\" card, instead of raw link chips.\n    var offer = matched[0];\n    if (offer.url === rtLastLinkKey && !promisedLink) { console.log('[PVI Voice] Skipping duplicate offer'); return; }\n    rtLastLinkKey = offer.url;\n    console.log('[PVI Voice] Offering product:', offer.label);\n    pviShowProductOffer(offer.label, offer.url, transcript);\n  }\n\n  // \u2500\u2500 Shared mode-update handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // The WORKER decides the conversation mode (via the AI's\n  // set_conversation_mode tool). Text receives it as an SSE event,\n  // voice receives it in the /api/voice-tool-call response. The frontend\n  // just applies it: persona, support link, auto-navigation.\n  // \u2500\u2500 Graceful deferred navigation (voice) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // When the voice AI switches support pages, we must NOT cut its speech.\n  // The AI says a short transition line (\"Got it \u2014 let me switch you over\n  // to the X support page\u2026\"); we navigate only when the audio FINISHES\n  // (output_audio_buffer.stopped), with a hard fallback so the customer\n  // is never stranded if the event doesn't arrive.\n  var rtPendingNavUrl = null;\n  var rtPendingNavSetAt = 0;\n  var rtPendingNavTimer = null;\n  function pviExecutePendingNav() {\n    if (!rtPendingNavUrl) return;\n    // Fallback fired mid-speech \u2014 never cut the transition line; wait for its\n    // stopped event with a short re-check net. rtAudioPlaying matters as much as\n    // rtAISpeaking: generation ends (response.done) while audio still plays.\n    if ((rtAISpeaking || rtAudioPlaying) && (Date.now() - rtPendingNavSetAt) < 15000) {\n      if (rtPendingNavTimer) clearTimeout(rtPendingNavTimer);\n      rtPendingNavTimer = setTimeout(pviExecutePendingNav, 2000);\n      return;\n    }\n    var u = rtPendingNavUrl;\n    rtPendingNavUrl = null;\n    if (rtPendingNavTimer) { clearTimeout(rtPendingNavTimer); rtPendingNavTimer = null; }\n    // Flush the transcript NOW (keepalive) before we leave \u2014 otherwise the\n    // pending debounced save is cancelled by the navigation and the AI's turns\n    // for this page are lost. final=true emails the complete transcript \u2014 only\n    // on the www theme, where the page really unloads; on the WIDGET the page\n    // stays alive (new tab) and the call continues, so final would email a\n    // half-finished conversation.\n    try { pviPushTranscript(true, !window.__pviCfg); } catch (_) {}\n    // Mark this as an AI-DRIVEN page switch for the next page's resume greeting\n    // (Zanus kit semantics: 30s validity, consumed on read). AI nav \u2192 walk the\n    // customer into the page in CONTINUATION style; manual nav \u2192 quiet resume.\n    try { sessionStorage.setItem('pvi_ai_nav', JSON.stringify({ at: Date.now(), url: u })); } catch (_) {}\n    console.log('[PVI Voice] Navigating to:', u);\n    window.location.href = u;\n    // WIDGET (Jul 30 2026): on the portable widget the line above is patched to\n    // open a NEW TAB \u2014 THIS page keeps running, so the voice session never gets\n    // the destination-page restart the www theme gets. If the answering persona\n    // changed (e.g. Mary \u2192 John tech support), restart the session in place so\n    // the right colleague actually picks up. On the www theme the page unloads\n    // before this timer fires, so it is a harmless no-op there.\n    setTimeout(function() {\n      if (currentMode === 'voice' && !rtPendingNavUrl && pviActivePersona !== rtSessionPersona) {\n        console.log('[PVI Voice] Widget nav kept page alive \u2014 restarting for persona', pviActivePersona);\n        pviSchedulePersonaRestart();\n        pviExecutePersonaRestart();\n      } else if (currentMode === 'voice' && !rtPendingNavUrl && pviActivePersona === 'sales' &&\n                 rtDataChannel && rtDataChannel.readyState === 'open') {\n        // WIDGET sales page-follow (Jul 31): the teaser rule keeps Lisa's\n        // pre-nav line SHORT (\"let me bring it up for you\") and the full pitch\n        // is supposed to happen on the product page. On www the destination\n        // page's continuation greeting delivers it \u2014 but here the page opened\n        // in a NEW TAB and THIS session keeps running, so nudge Lisa to give\n        // the presentation now. Support navs are excluded: John's doc-reload\n        // flow has its own announce.\n        console.log('[PVI Voice] Widget sales nav \u2014 nudging full presentation');\n        rtMicHoldUntilDone = true;\n        try {\n          rtDataChannel.send(JSON.stringify({\n            type: 'response.create',\n            response: { instructions:\n              'The product page you were bringing up just opened on the customer\\'s screen. ' +\n              'Now give the full presentation: 2-3 confident sentences walking them through the product, ' +\n              'then ONE question to keep the conversation going. Do not mention tabs or windows \u2014 continue naturally.' }\n          }));\n        } catch (err) { console.error('[PVI Voice] Widget nav pitch nudge error:', err); }\n      }\n    }, 1600);\n  }\n  function pviScheduleNavAfterSpeech(url) {\n    rtPendingNavUrl = url;\n    rtPendingNavSetAt = Date.now();\n    if (rtPendingNavTimer) clearTimeout(rtPendingNavTimer);\n    // NO DEAD AIR (user order Jul 30): cap true silence at ~5s (was 14s). If the\n    // transition line is still playing when this fires, execute defers to its end.\n    rtPendingNavTimer = setTimeout(pviExecutePendingNav, 5000);\n  }\n\n  // \u2500\u2500 Live persona transfer on WEB VOICE (Jul 30 2026) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // Dana/John/Mary hand the conversation to another colleague: the current\n  // session speaks ONE short transition line, then we restart the Realtime\n  // session \u2014 the worker picks the new persona's prompt + voice from the\n  // session mode (Mary=marin, Lisa=alloy). Mirrors the phone's AI\u2192AI transfer.\n  var rtPendingPersonaRestart = false;\n  var rtPendingPersonaSetAt = 0;\n  var rtPendingPersonaTimer = null;\n  var rtJustTransferred = false; // greeting: transfer pickup instead of hello\n  var rtSessionPersona = null;   // persona the CURRENT voice session was started as\n  function pviExecutePersonaRestart() {\n    if (!rtPendingPersonaRestart) return;\n    // Fallback fired while the transition line is STILL PLAYING \u2014 that is not\n    // silence, so let it finish (its stopped event restarts); re-check shortly.\n    // rtAudioPlaying is the REAL playback state (rtAISpeaking drops at\n    // response.done while the speaker is still talking \u2014 Jul 30 truncations).\n    if ((rtAISpeaking || rtAudioPlaying) && (Date.now() - rtPendingPersonaSetAt) < 12000) {\n      if (rtPendingPersonaTimer) clearTimeout(rtPendingPersonaTimer);\n      rtPendingPersonaTimer = setTimeout(pviExecutePersonaRestart, 2000);\n      return;\n    }\n    rtPendingPersonaRestart = false;\n    if (rtPendingPersonaTimer) { clearTimeout(rtPendingPersonaTimer); rtPendingPersonaTimer = null; }\n    if (currentMode !== 'voice' || rtPendingNavUrl) return;\n    console.log('[PVI Voice] Persona transfer restart \u2192', pviActivePersona);\n    rtDocInstructions = null;\n    rtDocAnnouncePending = false;\n    rtJustTransferred = true;\n    stopRealtimeVoice();\n    setTimeout(function() {\n      if (currentMode === 'voice') {\n        startRealtimeVoice().catch(function() { setMode('text'); });\n      }\n    }, 300);\n  }\n  function pviSchedulePersonaRestart() {\n    rtPendingPersonaRestart = true;\n    rtPendingPersonaSetAt = Date.now();\n    if (rtPendingPersonaTimer) clearTimeout(rtPendingPersonaTimer);\n    // NO DEAD AIR (user order Jul 30): cap true silence at ~3s (was 9s). If the\n    // transition line is still playing when this fires, execute defers to its end.\n    rtPendingPersonaTimer = setTimeout(pviExecutePersonaRestart, 3000);\n  }\n\n  function pviApplyModeUpdate(mu, navDelayMs) {\n    if (!mu || !mu.mode) return;\n    console.log('[PVI] Mode update from worker:', mu.mode, mu.modelKey || '', mu.personaName || '');\n    var prevPersona = pviActivePersona;\n    var voiceActiveNow = currentMode === 'voice' && rtDataChannel && rtDataChannel.readyState === 'open';\n    if (mu.mode === 'service') {\n      // Transfer to Mary (customer service): label change + voice restart.\n      pviActivePersona = 'service';\n      pviRetagStreamingLabel();\n      pviSaveSession();\n      if (voiceActiveNow && prevPersona !== 'service') pviSchedulePersonaRestart();\n      return;\n    }\n    if (mu.mode === 'support') {\n      pviActivePersona = 'support';\n      pviRetagStreamingLabel();\n      chosenPersona = 'support';\n      if (mu.displayName) supportProduct = mu.displayName;\n      pviSaveSession();\n      if (mu.supportUrl) {\n        var alreadyOn = location.pathname.indexOf(mu.supportUrl.replace('/pages/', '')) !== -1;\n        if (!pviLinkShown(mu.supportUrl)) {\n          pviMarkLinkShown(mu.supportUrl);\n          appendMessage('ai', '<div style=\"font-size:0.9em;margin-top:6px;padding:8px 12px;background:#f0f7ff;border-left:3px solid #0071E3;border-radius:4px\">' +\n            '<strong>\\uD83D\\uDEE0\\uFE0F Tech Support' + (mu.displayName ? ' \u2014 ' + pviEscapeHtml(mu.displayName) : '') + '</strong><br>' +\n            (alreadyOn ? '' : 'Taking you to the support page with all the manuals and documentation... ') +\n            '<a href=\"' + mu.supportUrl + '\" style=\"color:#0071E3;text-decoration:underline;font-weight:600\">Open the tech support page</a></div>');\n          scrollToBottom();\n        }\n        if (!alreadyOn) {\n          var voiceActive = currentMode === 'voice' && rtDataChannel && rtDataChannel.readyState === 'open';\n          if (voiceActive) {\n            // Wait for the AI's spoken transition to finish, THEN switch\n            pviScheduleNavAfterSpeech(mu.supportUrl);\n          } else {\n            // TEXT navigation: remember the switch so the destination page greets\n            // with a product-confirming line and reopens the panel (voice has its\n            // own destination greeting via pviBuildVoiceGreeting, so flag text only).\n            pviPendingSwitch = { persona: 'support', product: mu.displayName || '', url: mu.supportUrl, ts: Date.now() };\n            pviSaveSession();\n            setTimeout(function() { window.location.href = mu.supportUrl; }, navDelayMs || 1600);\n          }\n        } else if (voiceActiveNow && prevPersona !== 'support') {\n          // Already on the right page, but another colleague's voice is live\n          // (Lisa/Dana/Mary) \u2014 restart so John (support) picks up.\n          pviSchedulePersonaRestart();\n        }\n      } else if (voiceActiveNow && prevPersona !== 'support') {\n        // Transfer to support with NO page navigation (model not yet known, or\n        // no support page): restart so John picks up \u2014 from ANY colleague,\n        // including Lisa (Jul 30: only Dana/Mary restarted, so a sales voice\n        // kept talking after announcing the support transfer).\n        pviSchedulePersonaRestart();\n      }\n    } else {\n      chosenPersona = 'info';\n      pviActivePersona = 'sales';\n      pviRetagStreamingLabel();\n      pviSaveSession();\n      // Live transfer to Lisa (sales) on web voice: whenever the live voice is\n      // a DIFFERENT colleague (Dana, Mary, or John) \u2014 restart so Lisa picks up.\n      // Jul 30: support was missing from this list, so John said \"one moment\n      // while I get Lisa\" and nobody ever picked up (dead air, then John kept\n      // talking and deflected the customer to the phone number).\n      if (voiceActiveNow && prevPersona !== 'sales') {\n        pviSchedulePersonaRestart();\n      }\n    }\n  }\n\n  // \u2500\u2500 Voice transcript snapshots \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // Voice conversations never hit /api/chat, so the worker would have no\n  // transcript to email. Push a snapshot after each AI reply; the worker\n  // cron emails ONE complete transcript per session once it goes quiet.\n  var pviTranscriptTimer = null;\n  // immediate = POST now (no debounce). isFinal = the conversation ENDED \u2192 the\n  // worker emails the complete transcript right away (no 5-min cron wait). We\n  // always send the full chatHistory so a final flush is one atomic store+send\n  // (no race with a concurrent save). keepalive lets the POST survive a page\n  // navigation / tab close that used to cancel it and drop the AI's replies.\n  function pviPushTranscript(immediate, isFinal) {\n    if (pviTranscriptTimer) { clearTimeout(pviTranscriptTimer); pviTranscriptTimer = null; }\n    var doPost = function() {\n      if (!chatHistory.length) return;\n      fetch(API_BASE + '/api/save-transcript', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          session_id: sessionId,\n          channel: currentMode === 'voice' ? 'VOICE' : 'TEXT',\n          messages: chatHistory.slice(-80),\n          product: (product && product.title) || '',\n          language: currentLang,\n          final: !!isFinal\n        }),\n        keepalive: true\n      }).catch(function() {});\n    };\n    if (immediate) { doPost(); return; }\n    pviTranscriptTimer = setTimeout(doPost, 2500);\n  }\n  var rtCurrentUserText = ''; // current user transcript being built\n  var rtCurrentAITextEl = null;\n  var rtCurrentAIText = '';\n\n  function ensureRTAudioEl() {\n    if (rtAudioEl) return rtAudioEl;\n    rtAudioEl = document.createElement('audio');\n    rtAudioEl.autoplay = true;\n    rtAudioEl.playsInline = true;\n    rtAudioEl.setAttribute('playsinline', '');\n    rtAudioEl.setAttribute('webkit-playsinline', '');\n    rtAudioEl.style.display = 'none';\n    document.body.appendChild(rtAudioEl);\n\n    // iOS audio session resilience \u2014 pressing volume buttons / locking screen / changing\n    // route can briefly suspend audio. Resume automatically.\n    var resumePlay = function() {\n      if (rtAudioEl && rtAudioEl.srcObject) {\n        rtAudioEl.play().catch(function() {});\n      }\n    };\n    // Safety net for auto-resume after page navigation: if the browser blocks\n    // audio-output autoplay on the fresh page (no user gesture yet), resume the\n    // AI voice on the customer's first tap/click.\n    var pviUnlockAudio = function() {\n      if (rtAudioEl && rtAudioEl.srcObject && rtAudioEl.paused) {\n        rtAudioEl.play().catch(function() {});\n      }\n    };\n    document.addEventListener('pointerdown', pviUnlockAudio, { passive: true });\n    document.addEventListener('touchstart', pviUnlockAudio, { passive: true });\n\n    rtAudioEl.addEventListener('pause', function() {\n      if (rtConnected && rtAudioEl.srcObject) {\n        setTimeout(resumePlay, 100);\n      }\n    });\n    rtAudioEl.addEventListener('suspend', resumePlay);\n    rtAudioEl.addEventListener('stalled', resumePlay);\n    rtAudioEl.addEventListener('waiting', resumePlay);\n    return rtAudioEl;\n  }\n\n  // iOS \u2014 when user returns to tab after pressing volume buttons or unlocking,\n  // re-check connection and resume audio. Reconnect if WebRTC is dead.\n  document.addEventListener('visibilitychange', function() {\n    if (document.visibilityState !== 'visible') return;\n    if (currentMode !== 'voice') return;\n    if (rtAudioEl && rtAudioEl.paused && rtAudioEl.srcObject) {\n      rtAudioEl.play().catch(function() {});\n    }\n    if (rtPC && (rtPC.connectionState === 'failed' || rtPC.connectionState === 'disconnected' || rtPC.connectionState === 'closed')) {\n      if (Date.now() - rtStartedAt < 5000) return; // too soon after start \u2014 not a real disconnect\n      console.log('[PVI Voice] Connection died while backgrounded \u2014 reconnecting');\n      stopRealtimeVoice();\n      setTimeout(function() {\n        startRealtimeVoice().catch(function() { setMode('text'); });\n      }, 200);\n    }\n  });\n\n  // Release the microphone the instant we start leaving the page. Desktop (esp.\n  // Windows) can otherwise hold the device open through the navigation, making the\n  // NEXT page's getUserMedia fail with NotReadableError \u2014 the reason voice failed\n  // to auto-resume on PC while iPhone (faster device release) worked. Guarded with\n  // event.persisted so it never interferes with a bfcache restore.\n  window.addEventListener('pagehide', function(e) {\n    if (e && e.persisted) return;\n    if (currentMode !== 'voice') return;\n    try { if (rtMicStream) rtMicStream.getTracks().forEach(function(tr) { tr.stop(); }); } catch (_) {}\n    try { if (rtPC) rtPC.close(); } catch (_) {}\n  });\n\n  // Build an explicit page-aware greeting instruction. The realtime model does\n  // NOT reliably follow \"say the greeting in your system instructions\", so we\n  // give it the exact content to say based on the current page.\n  var pviIsSupportPage = (pageCtx.page_handle || '').indexOf('tech-support') !== -1;\n  // Resolve the support page slug to a SHORT product name. Never feed the raw\n  // slug to the voice \u2014 \"tech-support-for-vumatrix-av-over-ip-...\" humanized\n  // starts with \"For ...\" and the voice literally says \"is this for your FOUR\".\n  function pviSupportShortName(handle) {\n    var h = String(handle || '').toLowerCase();\n    var rules = [\n      [/minimod-2-plus/, 'VeCOAX MINIMOD 2+'],\n      [/minimod-4k/, 'VeCOAX MINIMOD 4K'],\n      [/minimod-2/, 'VeCOAX MINIMOD 2'],\n      [/micromod-3-plus-sdi/, 'VeCOAX MICROMOD 3+ SDI'],\n      [/micromod-3-plus/, 'VeCOAX MICROMOD 3+'],\n      [/micromod-3-sdi/, 'VeCOAX MICROMOD 3 SDI'],\n      [/micromod-3/, 'VeCOAX MICROMOD 3'],\n      [/ultra-plus/, 'VeCOAX ULTRA+'],\n      [/vecoax-ultra/, 'VeCOAX ULTRA'],\n      [/rack-2|rack-4/, 'VeCOAX RACK 2/4'],\n      [/blade/, 'VeCOAX BLADE'],\n      [/infinium/, 'INFINIUM'],\n      [/vecaster/, 'VeCASTER'],\n      [/vecoder-4k/, 'VeCODER 4K'],\n      [/vecoder/, 'VeCODER'],\n      [/veplayer-4|veplayer-rack/, 'VePLAYER RACK'],\n      [/veplayer/, 'VePLAYER'],\n      [/vumatrix/, 'VuMATRIX'],\n      [/vecoax-network/, 'VeCOAX NETWORK'],\n      [/pixel/, 'VeCOAX PIXEL'],\n      [/pro4/, 'VeCOAX PRO4']\n    ];\n    for (var i = 0; i < rules.length; i++) {\n      if (rules[i][0].test(h)) return rules[i][1];\n    }\n    // Fallback: strip \"tech-support\" / \"page\" / \"for\" prefixes before humanizing\n    var rest = h.replace(/^tech-support/, '').replace(/^[-_]+/, '')\n      .replace(/^(page|for)[-_]+/, '').replace(/^(page|for)[-_]+/, '');\n    return pviHumanize(rest);\n  }\n  var pviSupportPageProduct = pviIsSupportPage ? pviSupportShortName(pageCtx.page_handle) : '';\n\n  function pviBuildVoiceGreeting() {\n    var langName = (I18N[currentLang] && I18N[currentLang].name) || 'English';\n    var resuming = chatHistory && chatHistory.length > 0;\n    var productName = (product && product.title) ? product.title\n      : (pageCtx && pageCtx.page_type === 'product' && pageCtx.page_handle ? pviHumanize(pageCtx.page_handle) : '');\n    var who;\n\n    // Mary (customer service) \u2014 order status + tracking, any page\n    if (pviActivePersona === 'service') {\n      who = resuming\n        ? 'You are mid-conversation as the customer service colleague. Do NOT re-introduce yourself; just continue helping with the order.'\n        : 'Warmly greet the customer: introduce yourself by first name \u2014 your name is Mary, say exactly \"this is Mary\" \u2014 as customer service and offer to check an order status or email tracking. Ask for their order number. Two sentences max, then listen.';\n      return who + ' Speak in ' + langName + '.';\n    }\n\n    if (resuming) {\n      // MID-SESSION PAGE CHANGE: the customer navigated to a new page during a conversation.\n      // Do NOT re-introduce yourself, do NOT re-ask \"info or support?\". Just acknowledge the new page briefly.\n      // CRITICAL (Jul 30 amnesia bug): the restarted Realtime session has NO\n      // memory \u2014 the page-follow navigated right on Lisa's quote recap and the\n      // new session re-asked the quantity AND re-verified the email. Hand it\n      // the recent transcript, exactly like the live-transfer pickup does.\n      var resumeRecent = '';\n      for (var rh = Math.max(0, chatHistory.length - 8); rh < chatHistory.length; rh++) {\n        resumeRecent += (chatHistory[rh].role === 'user' ? 'Customer: ' : 'You (earlier): ') +\n          String(chatHistory[rh].content || '').replace(/<[^>]+>/g, '').slice(0, 300) + '\\n';\n      }\n      var resumeCtx = '\\nRecent conversation from BEFORE the page change \u2014 this is YOUR OWN ongoing conversation, continue it seamlessly:\\n' +\n        resumeRecent +\n        '\\nNEVER re-ask anything already answered above (name, email, quantity, product, details) \u2014 it is all still valid. ' +\n        'If a quote or request was in progress, pick it up EXACTLY where it left off. ';\n      // AI-DRIVEN switch vs manual navigation (Zanus kit semantics, Jul 31):\n      // when WE moved the page, the customer expects the same voice to keep\n      // talking \u2014 CONTINUATION style, never announce the switch, never\n      // \"I see you are looking at\". Flag is 30s valid, consumed on read.\n      var pviAiNav = null;\n      try {\n        pviAiNav = JSON.parse(sessionStorage.getItem('pvi_ai_nav') || 'null');\n        sessionStorage.removeItem('pvi_ai_nav');\n      } catch (_) {}\n      if (!(pviAiNav && (Date.now() - pviAiNav.at) < 30000)) pviAiNav = null;\n      if (pviIsSupportPage) {\n        var spName = pviSupportPageProduct || 'this product';\n        who = pviAiNav\n          ? ('You just switched the customer\\'s page to the ' + spName + ' tech support page \u2014 it is NOW on their screen. ' + resumeCtx +\n            'Continue talking as if you never stopped, in CONTINUATION style \u2014 e.g. \"So \u2014 here we are on the ' + spName + ' support page. ...\" ' +\n            'NEVER say \"I see you are looking at\", never mention that the page switched or that you switched it, never re-greet. ' +\n            'If they ALREADY described a problem above, address THAT problem right now; otherwise ask ONE short question about their issue.')\n          : ('The customer just switched to tech support and landed on the ' + spName + ' support page. ' + resumeCtx +\n            'In ONE short line, confirm the product and continue \u2014 say something like \"Here is the ' + spName + ' tech support page. How can I help you with it?\" ' +\n            'If they ALREADY described a problem above, acknowledge the product and address THAT problem instead of re-asking. ' +\n            'Do NOT re-introduce yourself. Do NOT repeat any previous greeting. Then listen.');\n      } else if (productName) {\n        who = pviAiNav\n          ? ('You just switched the customer\\'s page to the ' + productName + ' page \u2014 it is NOW on their screen. ' + resumeCtx +\n            'Continue talking as if you never stopped, in CONTINUATION style \u2014 e.g. \"So \u2014 this is the ' + productName + '. ...\" \u2014 2-3 confident sentences walking them into what they are seeing, then ONE question to keep the conversation going. ' +\n            'NEVER say \"I see you are looking at\", never mention that the page switched or that you switched it, never re-greet.')\n          : ('The customer just navigated to the ' + productName + ' page. ' + resumeCtx +\n            'Briefly acknowledge the page in ONE sentence \u2014 like \"I see you\\'re now looking at the ' + productName + '.\" \u2014 then continue the pending conversation from above (e.g. finish the quote recap and ask for the final YES). ' +\n            'Do NOT re-introduce yourself. Do NOT re-ask \"info or support?\". Do NOT repeat any previous greeting.');\n      } else {\n        who = 'The customer navigated to a new page. ' + resumeCtx +\n          'Do NOT say anything unless they speak first. Stay silent and listen; when they speak, continue the conversation above.';\n      }\n    } else if (pviIsSupportPage) {\n      var spName2 = pviSupportPageProduct || 'this product';\n      who = 'Warmly greet the customer. You can see they are on the tech support page for ' + spName2 + '. ' +\n        'Ask: \"Is this for your ' + spName2 + ', or do you need help with a different product?\" ' +\n        'Keep it brief \u2014 two sentences max, then listen.';\n    } else if (productName) {\n      who = 'Warmly greet the customer. You can see they are looking at our ' + productName + '. ' +\n        'Ask: \"Would you like some information about this product, or do you need tech support on a unit you already have?\" ' +\n        'Keep it brief \u2014 two sentences max, then listen.';\n    } else if (pageCtx && pageCtx.page_type === 'collection') {\n      who = 'Warmly greet the customer and mention you can see they are browsing our ' + (pageCtx.page_title || 'products') + '. ' +\n        'Ask how you can help \u2014 whether they need product info or tech support. Two sentences max, then listen.';\n    } else {\n      // Neutral page (homepage, contact, cart...) \u2014 Lisa answers directly.\n      // NO Dana on the web (Jul 30 2026). The name is pinned here because the\n      // model once invented \"Alex\" when told only \"introduce yourself\".\n      who = 'Warmly greet the customer, welcome them to Pro Video Instruments. Introduce yourself by first name \u2014 your name is Lisa, say exactly \"this is Lisa\". ' +\n        'Ask if they are looking for product information or need tech support on a unit they already have. Two sentences max, then listen.';\n    }\n\n    return who + ' Speak in ' + langName + '.';\n  }\n\n  // getUserMedia with a short retry. After a page navigation the previous page's\n  // microphone may not be fully released yet (common on Windows desktop), causing\n  // a transient NotReadableError/AbortError. Retrying briefly is what lets the PC\n  // auto-resume voice the same way mobile already does. Permission denials are NOT\n  // retried (no point) so we still fail fast when the user truly blocks the mic.\n  function pviGetMic(attempt) {\n    return navigator.mediaDevices.getUserMedia({\n      audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }\n    }).catch(function(err) {\n      var n = err && err.name;\n      var transient = n === 'NotReadableError' || n === 'AbortError' || n === 'NotFoundError';\n      if (transient && attempt < 3) {\n        console.log('[PVI Voice] Mic busy (' + n + ') \u2014 retry ' + (attempt + 1));\n        return new Promise(function(res) { setTimeout(res, 350 + attempt * 350); })\n          .then(function() { return pviGetMic(attempt + 1); });\n      }\n      throw err;\n    });\n  }\n\n  function startRealtimeVoice() {\n    if (rtStarting) {\n      console.log('[PVI Voice] Already starting \u2014 skipping duplicate');\n      return Promise.resolve();\n    }\n    rtStarting = true;\n    rtResponseCount = 0;\n    rtLastLinksInjected = '';\n    rtLastLinkKey = '';\n    if (!rtDocInstructions) rtDocModelLoaded = null;\n    rtDocLoading = false;\n    return new Promise(function(resolve, reject) {\n      var micStream = null;\n\n      pviGetMic(0).then(function(stream) {\n        micStream = stream;\n        rtMicStream = stream;\n\n        // Create peer connection\n        rtPC = new RTCPeerConnection();\n        ensureRTAudioEl();\n\n        rtPC.ontrack = function(e) {\n          if (rtAudioEl && e.streams && e.streams[0]) {\n            rtAudioEl.srcObject = e.streams[0];\n            rtAudioEl.play().catch(function() {});\n          }\n        };\n\n        rtPC.onconnectionstatechange = function() {\n          console.log('[PVI Voice] PC state:', rtPC && rtPC.connectionState);\n          if (!rtPC) return;\n          var s = rtPC.connectionState;\n          if (s === 'failed' || s === 'disconnected') {\n            if (currentMode === 'voice') {\n              setTimeout(function() {\n                if (currentMode !== 'voice') return;\n                if (rtPC && rtPC.connectionState === 'connected') return;\n                if (Date.now() - rtStartedAt < 5000) return; // too soon \u2014 not a real disconnect\n                console.log('[PVI Voice] Reconnecting after disconnect');\n                stopRealtimeVoice();\n                setTimeout(function() {\n                  startRealtimeVoice().catch(function() { setMode('text'); });\n                }, 200);\n              }, 1500);\n            }\n          }\n        };\n\n        // Add mic track (starts muted to prevent echo during greeting)\n        stream.getTracks().forEach(function(track) {\n          track.enabled = false;\n          rtPC.addTrack(track, stream);\n        });\n\n        // Data channel\n        rtDataChannel = rtPC.createDataChannel('oai-events');\n        rtDataChannel.onmessage = handleRealtimeEvent;\n        rtDataChannel.onopen = function() {\n          rtConnected = true;\n          rtStarting = false;\n          rtStartedAt = Date.now();\n          rtSessionPersona = pviActivePersona; // who answers THIS session\n          rtAISpeaking = true;\n          if (chatTextarea) chatTextarea.placeholder = t().listening;\n          if (micBtn) micBtn.classList.add('pvi-mic-active');\n          console.log('[PVI Voice] Connected \u2014 sending greeting');\n          try {\n            var greetingInstr;\n            var wasTransfer = rtJustTransferred;\n            if (rtJustTransferred) {\n              // LIVE TRANSFER PICKUP: the new persona has NO session history \u2014\n              // hand it the recent transcript so it continues seamlessly.\n              rtJustTransferred = false;\n              var xferLangName = (I18N[currentLang] && I18N[currentLang].name) || 'English';\n              var xferRecent = '';\n              for (var xh = Math.max(0, chatHistory.length - 6); xh < chatHistory.length; xh++) {\n                xferRecent += (chatHistory[xh].role === 'user' ? 'Customer: ' : 'Colleague: ') +\n                  String(chatHistory[xh].content || '').replace(/<[^>]+>/g, '').slice(0, 300) + '\\n';\n              }\n              // PIN THE NAME. \"Introduce yourself by first name\" alone makes the\n              // model INVENT one (\"Emma\", Jul 30 9:43 PM session \u2014 same class as\n              // the \"Alex\" bug). Every greeting that introduces a persona must\n              // state the exact name to say.\n              var xferName = pviActivePersona === 'support' ? 'John'\n                : pviActivePersona === 'service' ? 'Mary' : 'Lisa';\n              greetingInstr = 'LIVE TRANSFER: a colleague just handed this customer to you. Recent conversation:\\n' +\n                xferRecent +\n                '\\nYour name is ' + xferName + ' \u2014 introduce yourself in ONE short warm line saying exactly \"this is ' + xferName + '\" (NEVER any other name) and continue seamlessly from the customer\\'s pending request above \u2014 never re-ask anything they already said. Speak in ' + xferLangName + '.';\n            } else if (rtDocInstructions) {\n              // MEMORY, not amnesia: this restart happens MID-conversation (a\n              // procedure may be in progress). \"Ask what they need help with\"\n              // here made John forget a reset he was walking the customer\n              // through (Jul 31 VuMATRIX session). Embed the recent turns and\n              // continue the pending request instead.\n              var docRecent = '';\n              for (var dh = Math.max(0, chatHistory.length - 6); dh < chatHistory.length; dh++) {\n                docRecent += (chatHistory[dh].role === 'user' ? 'Customer: ' : 'You (earlier): ') +\n                  String(chatHistory[dh].content || '').replace(/<[^>]+>/g, '').slice(0, 300) + '\\n';\n              }\n              greetingInstr = 'You just loaded the full technical documentation for the product being discussed. ' +\n                'Recent conversation (YOUR own ongoing conversation \u2014 continue it):\\n' + docRecent +\n                '\\nIf a question or procedure is pending above, continue it RIGHT NOW from the documentation \u2014 pick up exactly where it left off, never re-ask what they need or anything already answered. ' +\n                'If nothing is pending, say ONE short line that the documentation is ready. Do not greet again.';\n            } else {\n              greetingInstr = pviBuildVoiceGreeting();\n            }\n            // This greeting was not prompted by the customer speaking \u2014 hold the\n            // mic muted for its whole duration so the AI can't hear/interrupt\n            // itself (the \"Hi\u2026 Hi\u2026\" restart) before echo cancellation locks on.\n            rtMicHoldUntilDone = true;\n            var pviSendGreeting = function() {\n              try {\n                rtDataChannel.send(JSON.stringify({\n                  type: 'response.create',\n                  response: { instructions: greetingInstr }\n                }));\n              } catch(err2) { console.error('[PVI Voice] Greeting send error:', err2); }\n            };\n            if (wasTransfer) {\n              // TRANSFER PICKUP: let the audio path settle first \u2014 requesting the\n              // greeting the instant the channel opens ate John's first words\n              // (Jul 30 stutter report). ~700ms is imperceptible after a transfer.\n              setTimeout(pviSendGreeting, 700);\n            } else {\n              pviSendGreeting();\n            }\n          } catch(err) { console.error('[PVI Voice] Greeting error:', err); }\n        };\n\n        // Create offer\n        return rtPC.createOffer();\n      }).then(function(offer) {\n        return rtPC.setLocalDescription(offer).then(function() { return offer; });\n      }).then(function(offer) {\n        // Single round trip: send SDP + params to our worker\n        var fd = new FormData();\n        fd.append('sdp', offer.sdp);\n        var callParams = {\n          product_title: product.title || '',\n          product_handle: product.handle || '',\n          page_type: pageCtx.page_type || '',\n          page_title: pageCtx.page_title || '',\n          collection_handle: pageCtx.collection_handle || '',\n          page_handle: pageCtx.page_handle || '',\n          voice: selectedVoice,\n          speed: selectedSpeed,\n          // Persona is sent ONLY when explicitly established \u2014 never a default\n          // guess. The worker state machine owns the mode. 'service' rides on\n          // pviActivePersona (set by a mode_update transfer to Mary).\n          persona: pviActivePersona === 'service' ? 'service'\n            : (chosenPersona === 'support' ? 'support' : (chosenPersona === 'info' ? 'sales' : (pviCfg.persona === 'support' ? 'support' : ''))),\n          session_id: sessionId,\n          // Mid-session reconnect (page navigation): the worker strips the\n          // \"Hi!\" opening greeting from the voice prompt so the AI never\n          // re-introduces itself like a tape machine.\n          has_interacted: chatHistory.length > 0,\n          language: currentLang\n        };\n        if (rtDocInstructions) {\n          callParams.enriched_instructions = rtDocInstructions;\n        }\n        fd.append('params', JSON.stringify(callParams));\n        return fetch(API_BASE + '/api/realtime-call', { method: 'POST', body: fd });\n      }).then(function(r) {\n        if (!r.ok) return r.text().then(function(txt) { throw new Error('Call failed: ' + txt); });\n        return r.text();\n      }).then(function(answerSdp) {\n        return rtPC.setRemoteDescription({ type: 'answer', sdp: answerSdp });\n      }).then(resolve).catch(function(err) {\n        rtStarting = false;\n        console.error('Realtime voice error:', err);\n        appendMessage('ai', '<em>Voice mode unavailable. Please use text mode.</em>');\n        reject(err);\n      });\n    });\n  }\n\n  function stopRealtimeVoice() {\n    rtConnected = false;\n    rtStarting = false;\n    if (rtDataChannel) {\n      try { rtDataChannel.close(); } catch(e) {}\n      rtDataChannel = null;\n    }\n    if (rtPC) {\n      try { rtPC.close(); } catch(e) {}\n      rtPC = null;\n    }\n    if (rtMicStream) {\n      rtMicStream.getTracks().forEach(function(track) { track.stop(); });\n      rtMicStream = null;\n    }\n    if (rtAudioEl) {\n      try { rtAudioEl.pause(); rtAudioEl.srcObject = null; } catch(e) {}\n    }\n    if (micBtn) micBtn.classList.remove('pvi-mic-active');\n    if (chatTextarea) chatTextarea.placeholder = t().placeholder;\n  }\n\n  // Product model names the user might say by voice \u2014 used to trigger doc loading.\n  var VOICE_MODEL_KEYWORDS = [\n    'vecaster', 'vecoder', 'veplayer', 'vumatrix', 'infinium',\n    'minimod', 'micromod', 'ultra plus', 'ultra+', 'rack 2', 'rack 4', 'blade',\n    'ip mod', 'ip-mod', 'ipmod', 'ip modulator', 'pixel'\n  ];\n  \n\n  var rtDocInstructions = null; // enriched prompt from /api/voice-support-docs\n\n  // Which product keyword was detected in the current transcript\n  function pviDetectMentionedModel(lower) {\n    for (var i = 0; i < VOICE_MODEL_KEYWORDS.length; i++) {\n      if (lower.indexOf(VOICE_MODEL_KEYWORDS[i]) !== -1) return VOICE_MODEL_KEYWORDS[i];\n    }\n    return null;\n  }\n\n  // Track the \"current product context\" so we can detect switches\n  var rtCurrentProductKeyword = null;\n\n  // Doc-reload restart coordination \u2014 see pviVoiceMaybeLoadDocs\n  var rtDocRestartPending = false;\n  var rtDocAnnouncePending = false;\n  var rtToolCallInFlight = false;\n  // Docs were swapped into the LIVE session (no restart) \u2014 tell the AI to\n  // continue the pending question with them, without re-greeting the customer.\n  function pviDoDocAnnounce() {\n    rtDocAnnouncePending = false;\n    if (currentMode !== 'voice' || !rtDataChannel || rtDataChannel.readyState !== 'open') return;\n    rtMicHoldUntilDone = true;\n    try {\n      rtDataChannel.send(JSON.stringify({\n        type: 'response.create',\n        response: { instructions:\n          'You just received the FULL technical documentation for the product being discussed. ' +\n          'If the customer has a pending question or a procedure in progress (reset, setup, troubleshooting), continue it RIGHT NOW from the documentation \u2014 pick up exactly where you left off, never re-ask what they need. ' +\n          'If nothing is pending, say ONE short line that you have the full documentation ready. Do not greet again.' }\n      }));\n    } catch (err) { console.error('[PVI Voice] Doc announce error:', err); }\n  }\n  function pviDoDocRestart() {\n    // A live persona transfer is about to restart the session anyway \u2014 the doc\n    // enrichment can re-trigger afterwards on the new session if still needed.\n    if (rtPendingPersonaRestart) return;\n    rtDocRestartPending = false;\n    rtDocAnnouncePending = false;\n    if (rtPendingNavUrl || currentMode !== 'voice') return;\n    console.log('[PVI Voice] Restarting Realtime with enriched prompt');\n    stopRealtimeVoice();\n    setTimeout(function() {\n      if (currentMode === 'voice') {\n        startRealtimeVoice().catch(function() { setMode('text'); });\n      }\n    }, 300);\n  }\n\n  // Buy/price vs support intent \u2014 mirrors the worker's BUY_INTENT_RE /\n  // SUPPORT_INTENT_RE so voice flips support\u2192sales the same way text does.\n  var PVI_VOICE_BUY_RE = /(\\b(want|like|need|looking|trying|wish|planning|hoping)\\s+to\\s+(buy|purchase|order|acquire)\\b|\\b(buy|purchase|order)\\s+(a|an|one|some|the|new|another)\\b|\\bplace\\s+an?\\s+order\\b|\\bhow\\s+much\\b|\\bwhat'?s?\\s+the\\s+(price|cost|msrp)\\b|\\bprice\\b|\\bpriced\\b|\\bpricing\\b|\\bquote\\b)/i;\n  var PVI_VOICE_SUPPORT_RE = /\\b(tech\\s*support|troubleshoot|stopped\\s+working|not\\s+working|doesn'?t\\s+work|won'?t\\s+(work|turn|power)|is\\s+broken|factory\\s+reset|firmware|no\\s+signal|ip\\s*address|log\\s?in|password|web\\s*interface)\\b/i;\n  function pviVoiceMaybeLoadDocs(transcript) {\n    if (!transcript || rtDocLoading) return;\n    // Mary (customer service) never troubleshoots \u2014 the doc loader must NOT\n    // hijack her session into tech support just because the customer mentions\n    // a product name (Jul 30 2026 shop-widget call: Mary was mid-tracking and a\n    // doc restart yanked the call to John). A REAL transfer to support sets\n    // pviActivePersona='support' first, after which docs load normally.\n    if (pviActivePersona === 'service' || pviActivePersona === 'reception') return;\n    var lower = transcript.toLowerCase();\n\n    // Check current transcript AND recent chat history for model + support context\n    var recentContext = '';\n    for (var rc = Math.max(0, chatHistory.length - 6); rc < chatHistory.length; rc++) {\n      recentContext += ' ' + (chatHistory[rc].content || '');\n    }\n    var combinedLower = (recentContext + ' ' + transcript).toLowerCase();\n\n    var mentionedModel = pviDetectMentionedModel(lower) || pviDetectMentionedModel(combinedLower);\n    if (!mentionedModel) return;\n\n    // Detect if this is a DIFFERENT product than what we're currently discussing\n    var isSwitchingProduct = rtCurrentProductKeyword && mentionedModel !== rtCurrentProductKeyword;\n\n    if (isSwitchingProduct) {\n      console.log('[PVI Voice] Product switch: ' + rtCurrentProductKeyword + ' \u2192 ' + mentionedModel + ' \u2014 letting AI re-qualify');\n      rtCurrentProductKeyword = mentionedModel;\n      return;\n    }\n\n    // PRICE / BUY intent in the customer's latest words is a SALES signal \u2014 NOT\n    // a request for support manuals. Do NOT load docs (which would re-pin the\n    // session to support and inject the wrong content). Let the worker flip the\n    // session to sales and let the AI answer with the price.\n    if (PVI_VOICE_BUY_RE.test(lower) && !PVI_VOICE_SUPPORT_RE.test(lower)) {\n      console.log('[PVI Voice] Buy/price intent \u2014 skipping support doc load (sales signal)');\n      rtCurrentProductKeyword = mentionedModel;\n      return;\n    }\n\n    // STRICT: docs are loaded ONLY when the session is confirmed in support\n    // mode (worker state via set_conversation_mode, or a tech-support page).\n    // No keyword guessing \u2014 a sales question about multicast must NOT load\n    // support docs. The worker enforces this too.\n    var hasSupportContext = pviIsSupportPage || chosenPersona === 'support';\n\n    if (rtDocModelLoaded && rtDocModelLoaded === mentionedModel) return;\n\n    if (!hasSupportContext) {\n      rtCurrentProductKeyword = mentionedModel;\n      return;\n    }\n\n    // On a support page, the worker already baked this page's docs into the\n    // session prompt at connection time \u2014 restarting to \"load\" them again\n    // would only interrupt the conversation for nothing.\n    if (pviIsSupportPage) {\n      var kwSlug = mentionedModel.replace(/[ +]+/g, '-');\n      if (location.pathname.toLowerCase().indexOf(kwSlug) !== -1) {\n        rtCurrentProductKeyword = mentionedModel;\n        return;\n      }\n    }\n\n    rtCurrentProductKeyword = mentionedModel;\n    var allText = chatHistory.map(function(m) { return m.content || ''; }).join(' ');\n    rtDocLoading = true;\n    console.log('[PVI Voice] Product + support context confirmed \u2014 fetching KV docs...');\n\n    fetch(API_BASE + '/api/voice-support-docs', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        transcript: allText,\n        product_title: (product && product.title) || '',\n        product_handle: (product && product.handle) || '',\n        page_type: pageCtx.page_type || '',\n        page_title: pageCtx.page_title || '',\n        collection_handle: pageCtx.collection_handle || '',\n        page_handle: pageCtx.page_handle || '',\n        session_id: sessionId,\n        latest_user: transcript,\n        persona: chosenPersona === 'support' ? 'support' : '',\n        language: currentLang\n      })\n    }).then(function(resp) { return resp.json(); })\n      .then(function(data) {\n        rtDocLoading = false;\n        // Worker flipped us to sales (price/buy intent) \u2014 drop support context so\n        // we don't keep trying to load manuals; the AI now answers with pricing.\n        if (data && data.mode === 'sales') {\n          chosenPersona = 'sales';\n          rtDocModelLoaded = null;\n          rtDocInstructions = null;\n          try { pviSaveSession(); } catch (_) {}\n          return;\n        }\n        if (!data || !data.modelKey || !data.instructions) return;\n        if (rtDocModelLoaded === data.modelKey) {\n          if (rtDataChannel && rtDataChannel.readyState === 'open') {\n            rtDataChannel.send(JSON.stringify({\n              type: 'session.update',\n              session: { instructions: data.instructions }\n            }));\n          }\n          return;\n        }\n        rtDocModelLoaded = data.modelKey;\n        rtDocInstructions = data.instructions;\n        // NEVER restart the session mid-speech or while a page switch is\n        // pending \u2014 tearing down WebRTC while the AI is talking (e.g. right\n        // after a set_conversation_mode tool call) sounds like a CRASH.\n        if (rtPendingNavUrl) {\n          console.log('[PVI Voice] Docs ready but navigation pending \u2014 new page will load them');\n          return;\n        }\n        // Session already speaking AS JOHN (support persona = same voice): no\n        // restart needed \u2014 swap the instructions into the LIVE session and\n        // continue. The old teardown+restart here caused ~5-10s of dead air\n        // plus an amnesia greeting (\"what do you need help with?\" while a\n        // reset procedure was mid-step \u2014 Jul 31 VuMATRIX session). A restart\n        // is only required when the VOICE must change (e.g. Lisa's session\n        // loading support docs = John must pick up).\n        if (rtSessionPersona === 'support' && rtDataChannel && rtDataChannel.readyState === 'open') {\n          console.log('[PVI Voice] Docs ready \u2014 session already support, swapping in place (no restart)');\n          try {\n            rtDataChannel.send(JSON.stringify({\n              type: 'session.update',\n              session: { instructions: data.instructions }\n            }));\n          } catch (err) { console.error('[PVI Voice] Doc session.update error:', err); }\n          if (rtAISpeaking || rtAudioPlaying || rtToolCallInFlight) {\n            rtDocAnnouncePending = true;\n          } else {\n            pviDoDocAnnounce();\n          }\n          return;\n        }\n        if (rtAISpeaking || rtAudioPlaying || rtToolCallInFlight) {\n          console.log('[PVI Voice] Docs ready \u2014 deferring restart until AI finishes speaking');\n          rtDocRestartPending = true;\n          return;\n        }\n        pviDoDocRestart();\n      }).catch(function(err) {\n        rtDocLoading = false;\n        console.error('[PVI Voice] Doc fetch failed:', err);\n      });\n  }\n\n  function handleRealtimeEvent(e) {\n    var event;\n    try { event = JSON.parse(e.data); } catch(_) { return; }\n\n    switch (event.type) {\n      case 'session.created':\n      case 'session.updated':\n        console.log('[PVI Voice]', event.type);\n        break;\n\n      case 'response.created':\n        rtAISpeaking = true;\n        if (rtMicHoldUntilDone) {\n          // Programmatic greeting/confirmation the customer did NOT prompt. Keep\n          // the mic MUTED for the whole response so the AI never hears its own\n          // greeting (residual speaker echo) and interrupts + repeats itself.\n          // Barge-in is irrelevant here \u2014 the customer just triggered this and\n          // it's one short line. The mic re-opens on response.done.\n          if (rtMicStream) {\n            rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = false; });\n          }\n          console.log('[PVI Voice] Greeting/confirmation \u2014 mic held muted (no self-interrupt)');\n        } else if (rtEchoSafe) {\n          // ECHO-SAFE MODE: speaker echo was self-interrupting the AI (car\n          // Bluetooth / speakerphone). Mute the mic for the whole answer; it\n          // reopens when the audio playback finishes (output_audio_buffer.stopped).\n          if (rtMicStream) {\n            rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = false; });\n          }\n          console.log('[PVI Voice] AI speaking \u2014 mic muted (echo-safe mode)');\n        } else {\n          // Real answer: mic STAYS LIVE while the AI speaks so the customer can\n          // interrupt (barge-in). Echo is handled by the mic's echo cancellation\n          // + server VAD; the server's interrupt_response truncates the AI the\n          // moment genuine speech is detected.\n          if (rtMicStream) {\n            rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = true; });\n          }\n          console.log('[PVI Voice] AI speaking \u2014 mic live for barge-in');\n        }\n        break;\n\n      case 'output_audio_buffer.started':\n        rtAudioPlaying = true;\n        break;\n\n      case 'output_audio_buffer.cleared':\n        // Barge-in: the customer interrupted and the buffered audio was dropped.\n        rtAudioPlaying = false;\n        break;\n\n      case 'output_audio_buffer.stopped':\n        rtAudioPlaying = false;\n        // ECHO-SAFE MODE: audio playback truly finished \u2014 now it is safe to\n        // reopen the mic (response.done fires while audio may still be playing,\n        // which is exactly when echo re-triggers the loop).\n        if (rtEchoSafe) {\n          setTimeout(function() {\n            try { rtDataChannel.send(JSON.stringify({ type: 'input_audio_buffer.clear' })); } catch(ex) {}\n            if (rtMicStream && !rtAISpeaking) {\n              rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = true; });\n              console.log('[PVI Voice] Mic back ON (echo-safe, audio finished)');\n            }\n          }, 800);\n        }\n        // AI audio playback finished. If a page switch is pending, go now \u2014\n        // but only if the transition sentence had time to play (>2s), so we\n        // don't navigate on the tail end of the PREVIOUS response's audio.\n        if (rtPendingNavUrl && (Date.now() - rtPendingNavSetAt) > 2000) {\n          setTimeout(pviExecutePendingNav, 350);\n          break;\n        }\n        // Live persona transfer: the transition line finished playing \u2014 bring\n        // the new colleague (voice) on the line now.\n        if (rtPendingPersonaRestart && (Date.now() - rtPendingPersonaSetAt) > 1500) {\n          setTimeout(pviExecutePersonaRestart, 350);\n          break;\n        }\n        // EARLY stop (= tail of the PREVIOUS reply; the transition line has not\n        // played yet): re-arm a SHORT net so we never sit in silence. If the\n        // transition line starts, its own stopped event restarts; if nothing\n        // plays, the new colleague picks up in ~1.8s.\n        if (rtPendingPersonaRestart) {\n          if (rtPendingPersonaTimer) clearTimeout(rtPendingPersonaTimer);\n          rtPendingPersonaTimer = setTimeout(pviExecutePersonaRestart, 1800);\n          break;\n        }\n        rtToolCallInFlight = false;\n        // Audio is idle \u2014 safe to apply a deferred doc-reload restart now\n        if (rtDocRestartPending && !rtPendingNavUrl) {\n          setTimeout(function() {\n            if (rtDocRestartPending && !rtAISpeaking && !rtPendingNavUrl) pviDoDocRestart();\n          }, 500);\n        }\n        // Docs were swapped in place while the AI was talking \u2014 continue the\n        // pending question now that the audio is idle.\n        if (rtDocAnnouncePending && !rtPendingNavUrl) {\n          setTimeout(function() {\n            if (rtDocAnnouncePending && !rtAISpeaking && !rtAudioPlaying) pviDoDocAnnounce();\n          }, 500);\n        }\n        break;\n\n      case 'response.done':\n        rtAISpeaking = false;\n        var wasGreetingOrConfirm = rtMicHoldUntilDone;\n        rtMicHoldUntilDone = false;\n        rtLastResponseDone = Date.now();\n        rtResponseCount++;\n        // AUTHORITATIVE capture of the AI's spoken turn.\n        if (event.response && event.response.output) {\n          var rtDoneText = '';\n          event.response.output.forEach(function(item) {\n            if (item.content) {\n              item.content.forEach(function(part) {\n                if (part.transcript) rtDoneText += part.transcript;\n                else if (part.type === 'text' && part.text) rtDoneText += part.text;\n              });\n            }\n          });\n          rtDoneText = (rtDoneText || '').trim();\n          if (rtDoneText) {\n            injectVoiceLinks(rtDoneText);\n            var rtLastMsg = chatHistory.length ? chatHistory[chatHistory.length - 1] : null;\n            if (!(rtLastMsg && rtLastMsg.role === 'assistant' && rtLastMsg.content === rtDoneText)) {\n              chatHistory.push({ role: 'assistant', content: rtDoneText });\n              pviVoiceCheckQuoteConfirm(rtDoneText);\n              pviSaveSession();\n            }\n            pviPushTranscript(true, false);\n          }\n        }\n        try { rtDataChannel.send(JSON.stringify({ type: 'input_audio_buffer.clear' })); } catch(ex) {}\n        // After a GREETING or programmatic confirmation the customer did NOT prompt,\n        // wait longer (2.5s) before unmuting the mic so the browser's echo canceller\n        // can fully lock on. At 1s, residual speaker audio leaks into the mic \u2192 the\n        // server VAD fires \u2192 Whisper hallucinated \"support\" / \"minimod\" from noise \u2192\n        // the AI jumped to tech support unprompted. For REAL answers (customer-prompted)\n        // 1s is fine because echo cancellation is already running and the customer\n        // expects the mic to be responsive.\n        // ECHO-SAFE MODE: do NOT unmute here \u2014 response.done fires while the\n        // audio is often STILL PLAYING, and unmuting into playing audio is what\n        // feeds the echo loop. output_audio_buffer.stopped reopens the mic, with\n        // an 8s safety net below in case that event never arrives.\n        if (rtEchoSafe) {\n          setTimeout(function() {\n            if (rtMicStream && !rtAISpeaking) {\n              rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = true; });\n            }\n          }, 8000);\n          break;\n        }\n        var micDelay = wasGreetingOrConfirm ? 2500 : 1000;\n        setTimeout(function() {\n          try { rtDataChannel.send(JSON.stringify({ type: 'input_audio_buffer.clear' })); } catch(ex) {}\n          if (rtMicStream) {\n            rtMicStream.getAudioTracks().forEach(function(track) { track.enabled = true; });\n          }\n          // Extra buffer clear 500ms after unmute \u2014 catches any residual audio that\n          // leaked in the instant the mic tracks were re-enabled.\n          setTimeout(function() {\n            try { rtDataChannel.send(JSON.stringify({ type: 'input_audio_buffer.clear' })); } catch(ex) {}\n          }, 500);\n          console.log('[PVI Voice] Mic back ON (delay=' + micDelay + 'ms)');\n        }, micDelay);\n        break;\n\n      case 'conversation.item.input_audio_transcription.completed':\n        if (event.transcript) {\n          // Echo-loop detection: a REAL transcript (more than a couple words of\n          // actual speech) within 6s of a barge-in proves it was the customer.\n          // Whisper renders speaker echo as empty or a 1-word hallucination\n          // (\"you\", \"thank you\") \u2014 those do NOT clear the strike.\n          if (!rtBargeGotTranscript && (Date.now() - rtLastBargeAt) < 6000 &&\n              event.transcript.trim().length >= 10) {\n            rtBargeGotTranscript = true;\n          }\n          // Reject echo: only during/after the GREETING (rtResponseCount <= 1).\n          // Whisper transcription is SLOWER than response generation, so by the\n          // time the user's real words arrive as a transcript the AI is already\n          // speaking its reply (rtAISpeaking = true). We must NOT drop those \u2014\n          // they are the user's actual words, not echo. Echo is only a risk\n          // right after the greeting when the echo canceller hasn't locked on.\n          // Aug 11 2026 fix: the window used to drop EVERYTHING \u2014 a customer who\n          // answered during/right after the greeting vanished from the transcript\n          // (the AI still heard them server-side, so the log showed the AI\n          // \"talking to itself\"). Echo has a signature: a SHORT Whisper\n          // hallucination (\"you\", \"thank you\") or a fragment of the AI's OWN\n          // words picked up from the speaker. Only drop THOSE \u2014 keep real speech.\n          if (rtResponseCount <= 1) {\n            var echoWindow = 2500;\n            if (rtAISpeaking || (Date.now() - rtLastResponseDone < echoWindow)) {\n              var pviNormEcho = function (s) {\n                return String(s || '').toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').replace(/\\s+/g, ' ').trim();\n              };\n              var echoT = pviNormEcho(event.transcript);\n              var aiRecentSpeech = pviNormEcho(rtCurrentAIText);\n              for (var ehi = chatHistory.length - 1; ehi >= 0 && ehi >= chatHistory.length - 3; ehi--) {\n                if (chatHistory[ehi].role === 'assistant') aiRecentSpeech += ' ' + pviNormEcho(chatHistory[ehi].content);\n              }\n              var pviIsEcho = echoT.length < 12 || aiRecentSpeech.indexOf(echoT) !== -1;\n              if (pviIsEcho) {\n                console.log('[PVI Voice] Ignoring echo transcript (greeting window):', event.transcript);\n                break;\n              }\n              console.log('[PVI Voice] Keeping real customer speech in greeting window:', event.transcript);\n            }\n          }\n          appendMessage('user', event.transcript);\n          chatHistory.push({ role: 'user', content: event.transcript });\n\n          // NO keyword guessing here. The conversation mode is decided by the\n          // worker state machine: the AI asks the qualification question and\n          // calls set_conversation_mode \u2014 handled in the function_call branch\n          // below, which also performs the support-page auto-navigation.\n          pviSaveSession();\n          pviPushTranscript();\n          pviVoiceMaybeLoadDocs(event.transcript);\n        }\n        break;\n\n      case 'response.audio_transcript.delta':\n        if (!rtCurrentAITextEl) {\n          rtCurrentAITextEl = appendMessage('ai', '');\n          rtCurrentAIText = '';\n        }\n        rtCurrentAIText += event.delta || '';\n        if (rtCurrentAITextEl) rtCurrentAITextEl.innerHTML = formatMessage(rtCurrentAIText);\n        scrollToBottom();\n        break;\n\n      case 'response.audio_transcript.done':\n        if (rtCurrentAIText) {\n          chatHistory.push({ role: 'assistant', content: rtCurrentAIText });\n          pviSaveSession();\n          injectVoiceLinks(rtCurrentAIText);\n          pviVoiceCheckQuoteConfirm(rtCurrentAIText);\n          pviPushTranscript();\n        }\n        rtCurrentAITextEl = null;\n        rtCurrentAIText = '';\n        break;\n\n      case 'input_audio_buffer.speech_started':\n        if (suggestions) suggestions.style.display = 'none';\n        if (rtAISpeaking) {\n          // BARGE-IN: the customer started talking while the AI was speaking.\n          // Mark the AI as no longer holding the floor so their upcoming transcript\n          // is accepted (not treated as echo), and let the server's interrupt_response\n          // truncate the AI's audio. We do NOT pause the audio element here \u2014 the\n          // live WebRTC track goes silent on its own when the server truncates, and\n          // pausing would fight the iOS audio auto-resume listener.\n          console.log('[PVI Voice] Barge-in \u2014 customer interrupted, yielding the floor');\n          rtAISpeaking = false;\n          rtCurrentAITextEl = null;\n          rtCurrentAIText = '';\n          // Echo-loop detection: if no real user transcript follows this\n          // interruption within 4s, it was the AI's own voice echoing back.\n          rtLastBargeAt = Date.now();\n          rtBargeGotTranscript = false;\n          setTimeout(pviEchoStrike, 4000);\n        } else {\n          console.log('[PVI Voice] User speaking');\n        }\n        break;\n\n      case 'response.function_call_arguments.done':\n        // The voice AI called a function. Two kinds:\n        //   set_conversation_mode \u2192 worker updates session state, frontend\n        //     applies persona/link/navigation. NOT a lead.\n        //   quote/ticket/callback \u2192 lead capture (KV save + email).\n        console.log('[PVI Voice] Function call:', event.name, event.arguments);\n        // Block doc-reload session restarts until the post-tool spoken response\n        // finishes (cleared on output_audio_buffer.stopped, 20s safety net).\n        rtToolCallInFlight = true;\n        setTimeout(function() { rtToolCallInFlight = false; }, 20000);\n        // Mark lead tools submitted IMMEDIATELY to prevent pviVoiceCheckQuoteConfirm duplicates.\n        // Knowledge/service retrievals and Mary's service actions (lookup_manual, lookup_order,\n        // email_tracking, check_stock, lookup_customer, check_reseller_discount, reset_customer_login,\n        // verify_order_for_address_change, update_shipping_address) are NOT lead submissions \u2014 never flag them.\n        if (event.name !== 'set_conversation_mode' && event.name !== 'lookup_manual' &&\n            event.name !== 'lookup_order' && event.name !== 'email_tracking' && event.name !== 'check_stock' &&\n            event.name !== 'lookup_customer' && event.name !== 'check_reseller_discount' && event.name !== 'reset_customer_login' &&\n            event.name !== 'verify_order_for_address_change' && event.name !== 'update_shipping_address') pviVoiceQuoteSubmitted = true;\n        try {\n          var toolArgs = JSON.parse(event.arguments);\n          var toolPayload = {\n            tool_name: event.name,\n            arguments: toolArgs,\n            product_title: (product && product.title) || '',\n            product_handle: (product && product.handle) || '',\n            page_handle: pageCtx.page_handle || '',\n            session_id: sessionId,\n            transcript: chatHistory.slice(-20),\n            language: currentLang\n          };\n          fetch(API_BASE + '/api/voice-tool-call', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(toolPayload)\n          }).then(function(resp) { return resp.json(); }).then(function(data) {\n            console.log('[PVI Voice] Tool call processed:', data.success, data.key || data.mode_update);\n            // Worker REJECTED the lead (missing name/email/phone): nothing was\n            // submitted, so clear the submitted flag \u2014 a later, complete call\n            // must still go through.\n            var rejected = (data.success === false);\n            if (rejected && event.name !== 'set_conversation_mode' && event.name !== 'lookup_manual') pviVoiceQuoteSubmitted = false;\n            // Send the tool result back to the Realtime API so the AI can confirm\n            // (or, on rejection, collect the missing details and retry).\n            if (rtDataChannel && rtDataChannel.readyState === 'open') {\n              rtDataChannel.send(JSON.stringify({\n                type: 'conversation.item.create',\n                item: {\n                  type: 'function_call_output',\n                  call_id: event.call_id,\n                  output: JSON.stringify({ success: !rejected, message: data.result || (rejected ? 'Not submitted.' : 'Submitted successfully.') })\n                }\n              }));\n              // The spoken confirmation for a mode switch or a lead submission is\n              // a short, programmatic line the customer did NOT prompt \u2014 hold the\n              // mic so the AI doesn't hear/interrupt itself (the \"Hi\u2026 Hi\u2026\" bug\n              // when switching to support). lookup_manual / lookup_order /\n              // email_tracking / lookup_customer / reset_customer_login / the address-change\n              // tools are real answers, so they keep the mic live for barge-in.\n              if (event.name !== 'lookup_manual' && event.name !== 'lookup_order' && event.name !== 'email_tracking' &&\n                  event.name !== 'lookup_customer' && event.name !== 'check_reseller_discount' && event.name !== 'reset_customer_login' &&\n                  event.name !== 'verify_order_for_address_change' && event.name !== 'update_shipping_address') rtMicHoldUntilDone = true;\n              rtDataChannel.send(JSON.stringify({ type: 'response.create' }));\n            }\n            // Mode switch: apply persona + support link + auto-navigation.\n            // Longer delay so the AI's spoken confirmation can start first;\n            // the voice session restarts on the support page with the right docs.\n            if (data.mode_update) {\n              pviApplyModeUpdate(data.mode_update, 2500);\n            }\n          }).catch(function(err) {\n            console.error('[PVI Voice] Tool call error:', err);\n            if (rtDataChannel && rtDataChannel.readyState === 'open') {\n              rtDataChannel.send(JSON.stringify({\n                type: 'conversation.item.create',\n                item: {\n                  type: 'function_call_output',\n                  call_id: event.call_id,\n                  output: JSON.stringify({ success: false, message: 'Submission failed, please try again.' })\n                }\n              }));\n              if (event.name !== 'lookup_manual') rtMicHoldUntilDone = true;\n              rtDataChannel.send(JSON.stringify({ type: 'response.create' }));\n            }\n          });\n        } catch(parseErr) {\n          console.error('[PVI Voice] Tool call parse error:', parseErr);\n        }\n        break;\n\n      case 'response.cancelled':\n        console.log('[PVI Voice] Response cancelled (likely barge-in)');\n        rtCurrentAITextEl = null;\n        rtCurrentAIText = '';\n        break;\n\n      case 'error':\n        console.error('[PVI Voice] Realtime error:', event);\n        break;\n    }\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // MODE TOGGLE\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  function updateModeToggleUI() {\n    if (!modeToggle || !modeToggleLabel) return;\n    if (currentMode === 'voice') {\n      modeToggle.classList.remove('pvi-mode-text');\n      modeToggle.classList.add('pvi-mode-voice');\n      modeToggleLabel.textContent = t().textMode;\n    } else {\n      modeToggle.classList.remove('pvi-mode-voice');\n      modeToggle.classList.add('pvi-mode-text');\n      modeToggleLabel.textContent = t().voiceMode;\n    }\n  }\n\n  function setMode(mode) {\n    currentMode = mode;\n    updateModeToggleUI();\n    if (mode === 'voice') {\n      if (micBtn) micBtn.style.display = 'flex';\n      if (suggestions) suggestions.style.display = 'none';\n      stopAudio();\n      if (!rtPC) {\n        startRealtimeVoice().catch(function() { setMode('text'); });\n      }\n    } else {\n      if (micBtn) micBtn.style.display = 'none';\n      if (chatTextarea) chatTextarea.placeholder = t().placeholder;\n      stopAudio();\n      stopRealtimeVoice();\n    }\n    pviSaveSession();\n  }\n\n  if (modeToggle) {\n    modeToggle.addEventListener('click', function() {\n      setMode(currentMode === 'voice' ? 'text' : 'voice');\n    });\n  }\n\n  if (voiceSelect) {\n    voiceSelect.addEventListener('change', function() {\n      selectedVoice = voiceSelect.value;\n      if (currentMode === 'voice' && rtPC) {\n        stopRealtimeVoice();\n        setTimeout(function() {\n          startRealtimeVoice().catch(function() { setMode('text'); });\n        }, 200);\n      }\n    });\n  }\n\n  if (speedSelect) {\n    speedSelect.addEventListener('change', function() {\n      selectedSpeed = parseFloat(speedSelect.value) || 1.0;\n      if (currentMode === 'voice' && rtPC) {\n        stopRealtimeVoice();\n        setTimeout(function() {\n          startRealtimeVoice().catch(function() { setMode('text'); });\n        }, 200);\n      }\n    });\n  }\n\n  function switchLanguage(newLang) {\n    if (!I18N[newLang] || newLang === currentLang) return;\n    currentLang = newLang;\n    try { localStorage.setItem('pvi-lang', newLang); } catch(_) {}\n    if (langSelect) langSelect.value = newLang;\n    renderWelcomeAndChips();\n    if (currentMode === 'voice' && rtPC) {\n      stopRealtimeVoice();\n      setTimeout(function() {\n        startRealtimeVoice().catch(function() { setMode('text'); });\n      }, 200);\n    }\n  }\n\n  if (langSelect) {\n    langSelect.addEventListener('change', function() {\n      switchLanguage(langSelect.value);\n    });\n  }\n\n  // Mic button: in voice mode, toggle to mute/unmute the local mic track\n  if (micBtn) {\n    micBtn.addEventListener('click', function() {\n      if (currentMode !== 'voice') return;\n      if (!rtMicStream) return;\n      var tracks = rtMicStream.getAudioTracks();\n      if (!tracks.length) return;\n      var enabled = !tracks[0].enabled;\n      tracks.forEach(function(track) { track.enabled = enabled; });\n      if (enabled) {\n        micBtn.classList.add('pvi-mic-active');\n        if (chatTextarea) chatTextarea.placeholder = t().listening;\n      } else {\n        micBtn.classList.remove('pvi-mic-active');\n        if (chatTextarea) chatTextarea.placeholder = '\\uD83D\\uDD07 ' + t().placeholder;\n      }\n    });\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // AUTO-EXPAND TEXTAREA\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  if (chatTextarea) {\n    chatTextarea.addEventListener('input', function() {\n      chatTextarea.style.height = 'auto';\n      chatTextarea.style.height = Math.min(chatTextarea.scrollHeight, 120) + 'px';\n    });\n    chatTextarea.addEventListener('keydown', function(e) {\n      if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }\n    });\n  }\n  if (chatSend) {\n    chatSend.addEventListener('click', function() { sendMessage(); });\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // SUGGESTION CHIPS\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  if (suggestions) {\n    suggestions.addEventListener('click', function(e) {\n      var chip = e.target.closest && e.target.closest('.pvi-chat-chip');\n      if (!chip) return;\n      if (chip.tagName === 'A') return;\n      var lang = chip.getAttribute('data-lang');\n      if (lang) {\n        e.preventDefault();\n        switchLanguage(lang);\n        return;\n      }\n      var prompt = chip.getAttribute('data-prompt');\n      if (prompt) {\n        e.preventDefault();\n        // One-shot department hint riding on THIS message only (consumed by\n        // the next /api/chat call) \u2014 deterministic routing in every language.\n        pviChipPersonaHint = chip.getAttribute('data-persona') || '';\n        sendMessage(prompt);\n      }\n    });\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // SEND MESSAGE\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  var activeAbort = null;\n\n  function sendMessage(forcedText) {\n    var text = forcedText || (chatTextarea ? chatTextarea.value.trim() : '');\n    if (!text) return;\n\n    chatGeneration++;\n    stopAudio();\n    if (activeAbort) { try { activeAbort.abort(); } catch(e) {} }\n    isStreaming = false;\n\n    if (chatTextarea && !forcedText) { chatTextarea.value = ''; chatTextarea.style.height = 'auto'; }\n    if (suggestions) suggestions.style.display = 'none';\n\n    appendMessage('user', text);\n    chatHistory.push({ role: 'user', content: text });\n\n    // NO keyword guessing here. The worker state machine decides the mode:\n    // the AI asks the qualification question when needed and calls\n    // set_conversation_mode \u2014 delivered back as a mode_update SSE event\n    // (handled in streamChat), which applies persona + link + navigation.\n    pviSaveSession();\n    streamChat();\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // APPEND MESSAGE\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  function appendMessage(role, content) {\n    if (!chatMessages) return null;\n    // Persona name label \u2014 printed once whenever the answering colleague\n    // CHANGES (Dana \u2192 Lisa/John/Mary), like a real office chat handoff.\n    if (role !== 'user' && pviActivePersona && PVI_PERSONA_LABELS[pviActivePersona] && pviActivePersona !== pviLastLabeledPersona) {\n      var plbl = document.createElement('div');\n      plbl.className = 'pvi-chat-persona-label';\n      plbl.style.cssText = 'font-size:11px;font-weight:600;color:#8a8a8a;margin:10px 0 2px 40px;';\n      plbl.textContent = PVI_PERSONA_LABELS[pviActivePersona];\n      chatMessages.appendChild(plbl);\n      pviLastLabeledPersona = pviActivePersona;\n    }\n    var div = document.createElement('div');\n    div.className = 'pvi-chat-msg pvi-chat-msg-' + (role === 'user' ? 'user' : 'ai');\n    if (role !== 'user') {\n      var av = document.createElement('div');\n      av.className = 'pvi-chat-msg-avatar';\n      av.innerHTML = '<span class=\"pvi-ai-sparkle\">\u2726</span>';\n      div.appendChild(av);\n    }\n    var bubble = document.createElement('div');\n    bubble.className = 'pvi-chat-msg-bubble';\n    bubble.innerHTML = content;\n    div.appendChild(bubble);\n    chatMessages.appendChild(div);\n    scrollToBottom();\n    return bubble;\n  }\n  // Retro-label the IN-FLIGHT streaming bubble after a mid-stream persona\n  // switch (store-project bug report Jul 30 2026): the worker's mode_update\n  // arrives WHILE the AI reply is streaming, i.e. after appendMessage already\n  // stamped the label from the OLD persona \u2014 so Mary's first message rendered\n  // under \"Dana \u00B7 Reception\". Swap (or insert) the label on the last AI bubble.\n  function pviRetagStreamingLabel() {\n    if (!isStreaming || !chatMessages) return;\n    var lbl = pviActivePersona && PVI_PERSONA_LABELS[pviActivePersona];\n    if (!lbl || pviLastLabeledPersona === pviActivePersona) return;\n    var msgs = chatMessages.querySelectorAll('.pvi-chat-msg-ai');\n    var target = msgs.length ? msgs[msgs.length - 1] : null;\n    if (!target) return;\n    var prev = target.previousElementSibling;\n    if (prev && prev.className === 'pvi-chat-persona-label') {\n      prev.textContent = lbl;\n    } else {\n      var el = document.createElement('div');\n      el.className = 'pvi-chat-persona-label';\n      el.style.cssText = 'font-size:11px;font-weight:600;color:#8a8a8a;margin:10px 0 2px 40px;';\n      el.textContent = lbl;\n      chatMessages.insertBefore(el, target);\n    }\n    pviLastLabeledPersona = pviActivePersona;\n  }\n  var _streamScrollDone = false;\n  function scrollToBottom() { if (chatBody) chatBody.scrollTop = chatBody.scrollHeight; }\n  function scrollPinAIResponse(msgDiv) {\n    if (!chatBody || !msgDiv || _streamScrollDone) return;\n    _streamScrollDone = true;\n    var offset = msgDiv.offsetTop - chatBody.offsetTop - 40;\n    chatBody.scrollTop = Math.max(0, offset);\n  }\n\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  // STREAM CHAT + SENTENCE-BY-SENTENCE TTS\n  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n  function streamChat() {\n    isStreaming = true;\n    updateSendButton(true);\n\n    _streamScrollDone = false;\n    var bubble = appendMessage('ai', '<span class=\"pvi-typing\"><span></span><span></span><span></span></span>');\n    var aiBubbleDiv = bubble ? bubble.parentElement : null;\n    var firstChunk = true;\n    var fullText = '';\n    var spokenTo = 0;\n    var myGen = chatGeneration;\n    var abortCtrl = new AbortController();\n    activeAbort = abortCtrl;\n\n    function stale() { return myGen !== chatGeneration; }\n\n    function trySpeakSentences() {\n      if (currentMode !== 'voice' || stale()) return;\n      while (true) {\n        var rest = fullText.slice(spokenTo);\n        var m = rest.match(/^(.*?[.!?])\\s/);\n        if (!m) break;\n        var sentence = m[1].trim();\n        if (sentence.length >= 3) {\n          queueTTS(sentence);\n        }\n        spokenTo += m[0].length;\n      }\n    }\n\n    function speakRemaining() {\n      if (currentMode !== 'voice' || stale()) return;\n      var rest = fullText.slice(spokenTo).trim();\n      if (rest.length >= 2) {\n        queueTTS(rest);\n        spokenTo = fullText.length;\n      }\n    }\n\n    // Consume the one-shot chip department hint (set by the chip click\n    // handler) \u2014 it must ride on exactly this request and never linger.\n    var chipHint = pviChipPersonaHint || '';\n    pviChipPersonaHint = '';\n\n    fetch(API_BASE + '/api/chat', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      signal: abortCtrl.signal,\n      body: JSON.stringify({\n        messages: chatHistory.slice(-20),\n        product_title: product.title || '',\n        product_handle: product.handle || '',\n        page_type: pageCtx.page_type || '',\n        page_title: pageCtx.page_title || '',\n        collection_handle: pageCtx.collection_handle || '',\n        page_handle: pageCtx.page_handle || '',\n        session_id: sessionId,\n        // Persona only when EXPLICITLY established \u2014 otherwise the worker\n        // keeps the mode 'unset' and the AI qualifies the customer first.\n        // 'service' rides on pviActivePersona (mode_update transfer to Mary).\n        persona: pviActivePersona === 'service' ? 'service'\n          : (chosenPersona === 'support' ? 'support' : (chosenPersona === 'info' ? 'sales' : (pviCfg.persona === 'support' ? 'support' : ''))),\n        // One-shot chip department (set by the chip click handler, consumed\n        // here) \u2014 the worker routes on it deterministically in any language.\n        persona_hint: chipHint,\n        has_interacted: hasInteracted,\n        language: currentLang\n      })\n    }).then(function(resp) {\n      if (!resp.ok) return resp.json().then(function(e) { throw new Error(e.error || 'fail'); });\n\n      var reader = resp.body.getReader();\n      var decoder = new TextDecoder();\n      var buf = '';\n\n      function read() {\n        reader.read().then(function(r) {\n          if (r.done) { finish(); return; }\n          buf += decoder.decode(r.value, { stream: true });\n          var lines = buf.split('\\n');\n          buf = lines.pop() || '';\n          for (var i = 0; i < lines.length; i++) {\n            var ln = lines[i].trim();\n            if (!ln.startsWith('data: ')) continue;\n            var d = ln.slice(6).trim();\n            if (d === '[DONE]') { finish(); return; }\n            try {\n              var p = JSON.parse(d);\n              if (p.content) {\n                fullText += p.content;\n                if (bubble) bubble.innerHTML = formatMessage(pviMaybeHidePrices(fullText));\n                if (firstChunk) { firstChunk = false; scrollPinAIResponse(aiBubbleDiv); }\n                trySpeakSentences();\n              }\n              if (p.mode_update) {\n                // Worker confirmed a mode switch (AI called set_conversation_mode)\n                pviApplyModeUpdate(p.mode_update, 1800);\n              }\n            } catch(e) {}\n          }\n          read();\n        }).catch(function() { finish(); });\n      }\n      read();\n    }).catch(function(err) {\n      if (err.name === 'AbortError') return;\n      if (bubble && !stale()) {\n        bubble.innerHTML = 'Connection issue. Call <a href=\"tel:4077206101\" style=\"color:#0071E3\">407-720-6101</a>';\n      }\n      isStreaming = false; activeAbort = null; updateSendButton(false);\n    });\n\n    function finish() {\n      isStreaming = false; activeAbort = null; updateSendButton(false);\n      if (fullText && !stale()) {\n        chatHistory.push({ role: 'assistant', content: fullText });\n        pviSaveSession();\n        speakRemaining();\n        pviCheckForLeadTag(fullText);\n        // Text mode only \u2014 voice offers are handled by injectVoiceLinks.\n        if (currentMode !== 'voice') pviTextMaybeOffer(fullText, bubble);\n      }\n    }\n  }\n\n  // \u2500\u2500 Price suppression (shop widget, TEXT chat only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // Showing prices upfront bounces customers. Prices are shown ONLY if the\n  // customer asked for them (any of their messages mentions price/cost/etc.).\n  // Otherwise $-amounts are removed from the DISPLAYED reply \u2014 the customer\n  // clicks the offered link and sees pricing on the product page.\n  // (Voice is untouched: we cannot unspeak what the AI says aloud.)\n  var PVI_PRICE_ASK_RE = /\\b(price|prices|priced|pricing|cost|costs|how much|quote|quotation|estimate|discount|cheap|cheaper|expensive|affordable|budget|deal|deals|msrp|dollars?)\\b|\\$|how many dollars|cu[a\u00E1]nto cuesta|quanto costa/i;\n  function pviCustomerAskedPrice() {\n    for (var i = 0; i < chatHistory.length; i++) {\n      if (chatHistory[i].role === 'user' && PVI_PRICE_ASK_RE.test(chatHistory[i].content || '')) return true;\n    }\n    return false;\n  }\n  function pviMaybeHidePrices(t) {\n    if (!t || t.indexOf('$') === -1) return t;\n    if (currentMode === 'voice') return t;      // spoken audio can't be filtered\n    if (pviCustomerAskedPrice()) return t;\n    // 1) Drop parentheticals that contain a $amount: \"(4-channel: $2,449, ...)\"\n    var s = t.replace(/\\s*\\([^()]*\\$\\s?\\d[^()]*\\)/g, '');\n    // 2) Drop any remaining sentence/line fragment containing a $amount.\n    s = s.replace(/[^.!?\\n]*\\$\\s?\\d[\\d,]*(?:\\.\\d+)?[^.!?\\n]*[.!?]?/g, '');\n    // 3) Tidy up: bullets left empty, doubled blank lines/spaces.\n    s = s.replace(/^[ \\t]*[-\\u2022*][ \\t]*$/gm, '');\n    s = s.replace(/\\n{3,}/g, '\\n\\n').replace(/[ \\t]{2,}/g, ' ');\n    return s;\n  }\n\n  function formatMessage(t) {\n    var s = t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n    s = s.replace(/\\*\\*(.*?)\\*\\*/g,'<strong>$1</strong>');\n    // PRODUCT links are presented via the explicit offer card (shop widget), so\n    // render a product NAME as bold text here \u2014 never an inline link. (Non-product\n    // links like /pages/ or /collections/ stay clickable, handled below.)\n    s = s.replace(/\\[([^\\]]+)\\]\\([^)]*\\/products\\/[^)]*\\)/g, '<strong>$1</strong>');\n    s = s.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g, function(_,txt,url) {\n      var isPVI = /provideoinstruments\\.com/i.test(url);\n      return '<a href=\"' + url + '\"' + (isPVI ? '' : ' target=\"_blank\" rel=\"noopener\"') + ' style=\"color:#0071E3;text-decoration:underline\">' + txt + '</a>';\n    });\n    s = s.replace(/\\[([^\\]]+)\\]\\((\\/[^)]+)\\)/g,'<a href=\"$2\" style=\"color:#0071E3;text-decoration:underline\">$1</a>');\n    s = s.replace(/(^|[\\s>])(https?:\\/\\/[^\\s<\"']+)/g, function(m,pre,url) {\n      var isPVI = /provideoinstruments\\.com/i.test(url);\n      return pre + '<a href=\"' + url + '\"' + (isPVI ? '' : ' target=\"_blank\" rel=\"noopener\"') + ' style=\"color:#0071E3;text-decoration:underline\">' + url + '</a>';\n    });\n    s = s.replace(/\\n\\n/g,'<br><br>').replace(/\\n/g,'<br>');\n    return s;\n  }\n  function updateSendButton(on) { if (chatSend) { chatSend.disabled = on; chatSend.style.opacity = on ? '0.4' : '1'; } }\n\n  // \u2500\u2500 Business Hours \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  function pviCheckHours() {\n    var btn = document.getElementById('pviTalkHuman');\n    if (!btn) return;\n    var now = new Date();\n    var ct = new Date(now.toLocaleString('en-US', { timeZone: 'America/Chicago' }));\n    btn.style.display = (ct.getDay() >= 1 && ct.getDay() <= 5 && ct.getHours() >= 9 && ct.getHours() < 17) ? 'flex' : 'none';\n  }\n  window.pviCheckHours = pviCheckHours;\n  pviCheckHours();\n\n  // \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  setMode('text');\n\n})();\n\n// \u2500\u2500 YouTube Video Embed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction pviLoadVideo(sectionId, youtubeId, startAt) {\n  var c = document.getElementById('pviVideoContainer-' + sectionId);\n  if (!c) return;\n  var imgs = c.querySelectorAll('img, div[onclick]');\n  imgs.forEach(function(el) { el.style.display = 'none'; });\n  var iframe = document.createElement('iframe');\n  iframe.id = 'pvi-video-' + sectionId;\n  iframe.src = 'https://www.youtube-nocookie.com/embed/' + youtubeId +\n    '?rel=0&modestbranding=1&autoplay=1' + (startAt > 0 ? '&start=' + startAt : '');\n  iframe.title = 'Product Video';\n  iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';\n  iframe.allowFullscreen = true;\n  iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:none';\n  c.appendChild(iframe);\n}\nfunction pviSeekVideo(sectionId, youtubeId, seconds) {\n  var iframe = document.getElementById('pvi-video-' + sectionId);\n  if (iframe) {\n    iframe.src = 'https://www.youtube-nocookie.com/embed/' + youtubeId + '?rel=0&modestbranding=1&autoplay=1&start=' + seconds;\n  } else { pviLoadVideo(sectionId, youtubeId, seconds); }\n  var el = document.getElementById('pviVideoContainer-' + sectionId);\n  if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n}\n\n/** Add 1 of variant to cart and open the quick cart drawer (+1 each click if already in cart). */\nfunction pviAddToCart(variantId) {\n  var cart = document.querySelector('cart-drawer');\n  if (!variantId) {\n    var mainForm = document.querySelector('#MainProduct product-form input[name=\"id\"], #MainProduct input[name=\"id\"]');\n    var fallback = document.querySelector('product-form input[name=\"id\"]');\n    var input = mainForm || fallback;\n    if (!input || !input.value) return;\n    variantId = parseInt(input.value, 10);\n  }\n  if (!variantId) return;\n\n  var sections = '';\n  if (cart && typeof cart.getSectionsToRender === 'function') {\n    sections = cart.getSectionsToRender().map(function(s) { return s.id; }).join(',');\n  }\n\n  var payload = { items: [{ id: variantId, quantity: 1 }] };\n  if (sections) {\n    payload.sections = sections;\n    payload.sections_url = window.location.pathname;\n  }\n\n  var addUrl = (window.routes && window.routes.cart_add_url)\n    ? window.routes.cart_add_url.replace(/\\/cart\\/add\\/?$/, '/cart/add.js')\n    : '/cart/add.js';\n\n  return fetch(addUrl, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n    body: JSON.stringify(payload),\n  })\n    .then(function(r) { return r.json(); })\n    .then(function(response) {\n      if (response.status) {\n        console.error(response.description || response.message);\n        return;\n      }\n      if (cart && response.sections) {\n        if (cart.classList.contains('is-empty')) cart.classList.remove('is-empty');\n        cart.renderContents(response);\n      } else if (cart) {\n        cart.open();\n      } else if (window.routes && window.routes.cart_url) {\n        window.location.href = window.routes.cart_url;\n      }\n    })\n    .catch(function(err) {\n      console.error(err);\n      if (window.routes && window.routes.cart_url) window.location.href = window.routes.cart_url;\n    });\n}\n\ndocument.addEventListener('click', function(e) {\n  var buyBtn = e.target.closest('.pvi-pill-buy');\n  if (!buyBtn || buyBtn.closest('product-form')) return;\n  e.preventDefault();\n  pviAddToCart(buyBtn.getAttribute('data-pvi-buy-variant'));\n});\n\nfunction pviPatchUltraAccessories() {\n  if (window.location.pathname.indexOf('/products/vecoax-ultra') === -1) return;\n  var section = document.querySelector('.pvi-accessories-wrapper');\n  if (!section || section.getAttribute('data-pvi-ultra-accessories') === '1') return;\n  var grid = section.querySelector('.pvi-accessories-grid');\n  if (!grid) return;\n\n  var subtitle = section.querySelector('.pvi-section-subtitle');\n  if (subtitle) {\n    subtitle.textContent = 'Everything you need to combine ULTRA+ units and add 4-channel, 8-channel, 4K, rack-mounted HD, and SDI sources.';\n  }\n\n  var cards = [\n    {\n      title: 'Coax Combiner / Splitter (13-Port)',\n      description: 'Combine ULTRA+ units with other modulators onto one clean coax distribution line.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/vecoax-rack-4-4-channel-hdmi-rf-modulator-16-related-coax-combiner-splitter-13-port-tv-coax.png?v=1780520501',\n      url: '/products/coax-combiner-splitter',\n      variant: '44216996724772',\n      price: '$95.00',\n      compare: '$105.00',\n      save: '$10.00'\n    },\n    {\n      title: 'VeCOAX ULTRA+ 4 Channel',\n      description: 'Four-channel HDMI RF modulator chassis for hotels, venues, bars, schools, and commercial coax systems.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/vecoax-ultra-plus-8-channel-hdmi-rf-modulator-rack-mount-coax-distribution.jpg?v=1781876837',\n      url: '/products/vecoax-ultra?variant=44217077465124',\n      variant: '44217077465124',\n      price: '$2,449.00',\n      compare: '$2,890.00',\n      save: '$441.00'\n    },\n    {\n      title: 'VeCOAX ULTRA+ 8 Channel',\n      description: 'Eight-channel HDMI RF modulator chassis for larger multi-source TV distribution over existing coax.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/vecoax-ultra-plus-8-channel-hdmi-rf-modulator-rack-mount-coax-distribution.jpg?v=1781876837',\n      url: '/products/vecoax-ultra?variant=44217077530660',\n      variant: '44217077530660',\n      price: '$3,950.00',\n      compare: '$5,290.00',\n      save: '$1,340.00'\n    },\n    {\n      title: 'MINIMOD-4K Ultra HD',\n      description: 'Add a true 4K HDMI channel alongside your ULTRA+ HD channels.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/vecoax-minimod-4k-uhd-hdmi-rf-modulator-main-features.jpg?v=1780497829',\n      url: '/products/vecoax-minimod-4k',\n      variant: '44217054101540',\n      price: '$1,495.00',\n      compare: '$1,950.00',\n      save: '$455.00'\n    },\n    {\n      title: 'MINIMOD 2+ HDMI Modulator',\n      description: 'Add one independent HD HDMI channel anywhere in the same coax system.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/hdmi-modulator-front-panel.jpg?v=1780088931',\n      url: '/products/vecoax-minimod-2-plus',\n      variant: '44177637408804',\n      price: '$445.00',\n      compare: '$495.00',\n      save: '$50.00'\n    },\n    {\n      title: '1RU Rack Panel for MINIMOD Units',\n      description: 'Mount up to four MINIMOD modulators in one clean 1RU rack space with front-panel access.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/rack-panel-1ru-hdmi-modulator.png?v=1780164344',\n      url: '/products/1ru-rack-panel-hdmi-modulator',\n      variant: '44217002754084',\n      price: '$99.00',\n      compare: '$105.00',\n      save: '$6.00'\n    },\n    {\n      title: 'MICROMOD-3 SDI',\n      description: 'Add 3G-SDI, HDMI, AV, closed captions, and analog inputs as another RF TV channel.',\n      image: 'https://cdn.shopify.com/s/files/1/0788/6359/7604/files/vecoax-micromod-3-sdi-rf-modulator-01-micromod-3-plus-sdi-front.png?v=1780173968',\n      url: '/products/vecoax-micromod-3-sdi',\n      variant: '44217313230884',\n      price: '$995.00',\n      compare: '$1,295.00',\n      save: '$300.00'\n    }\n  ];\n\n  grid.innerHTML = cards.map(function(card, index) {\n    return [\n      '<div class=\"pvi-accessory-card\">',\n      '<a href=\"' + card.url + '\" class=\"pvi-accessory-image-wrap\">',\n      '<img src=\"' + card.image + '\" alt=\"' + card.title.replace(/\"/g, '&quot;') + '\" class=\"pvi-accessory-image\" loading=\"lazy\" width=\"600\" height=\"434\">',\n      '</a>',\n      '<h3>' + card.title + '</h3>',\n      '<p>' + card.description + '</p>',\n      '<div class=\"pvi-accessory-price\">',\n      '<span class=\"pvi-accessory-price-was\">' + card.compare + '</span>',\n      '<span class=\"pvi-accessory-price-now\">' + card.price + '</span>',\n      '<span class=\"pvi-you-save\">You save ' + card.save + '</span>',\n      '</div>',\n      '<div class=\"pvi-accessory-buttons\">',\n      '<button type=\"button\" class=\"pvi-btn-add-cart\" data-pvi-ultra-accessory-variant=\"' + card.variant + '\"><span>Add to Cart</span></button>',\n      '<a href=\"' + card.url + '\" class=\"pvi-btn-view\">View &#8594;</a>',\n      '</div>',\n      '</div>'\n    ].join('');\n  }).join('');\n  section.setAttribute('data-pvi-ultra-accessories', '1');\n}\n\ndocument.addEventListener('DOMContentLoaded', pviPatchUltraAccessories);\ndocument.addEventListener('shopify:section:load', pviPatchUltraAccessories);\nsetTimeout(pviPatchUltraAccessories, 500);\n\ndocument.addEventListener('click', function(e) {\n  var btn = e.target.closest('[data-pvi-ultra-accessory-variant]');\n  if (!btn) return;\n  e.preventDefault();\n  pviAddToCart(btn.getAttribute('data-pvi-ultra-accessory-variant'));\n});\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   PVI GLOBAL GALLERY SYNC\n   Keeps the thumbnail strip in sync with the hero slider when you swipe\n   or use the big-picture arrows (Dawn's native slideChanged sync is\n   unreliable with our scroll-snap layout). Runs on every product page.\n   \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n(function () {\n  function initGallery(gallery) {\n    if (!gallery || gallery.__pviGallerySynced) return;\n    var viewer = gallery.querySelector('.pvi-gallery-slider');\n    if (!viewer) return;\n    var slides = Array.prototype.slice.call(viewer.querySelectorAll('.product__media-item'));\n    if (slides.length < 2) { gallery.__pviGallerySynced = true; gallery.setAttribute('data-pvi-synced', '1'); return; }\n\n    var thumbsList = gallery.querySelector('[id^=\"Slider-Thumbnails\"]');\n    var lastIdx = 0;\n\n    /* The hero is a horizontal scroll-snap flex row showing ALL slides, so the\n       row would otherwise grow as tall as the TALLEST slide (a portrait shot),\n       leaving big dead bands above/below the wide landscape hero images.\n       Fix: pin the viewer height to the ACTIVE slide so the frame hugs each\n       image \u2014 no gap above, thumbnails sit right underneath. */\n    function setHeight(slide) {\n      var box = slide.querySelector('.product-media-container') || slide;\n      var h = box.getBoundingClientRect().height;\n      if (h > 0) viewer.style.height = Math.round(h) + 'px';\n    }\n\n    function setActive(idx) {\n      idx = Math.max(0, Math.min(slides.length - 1, idx));\n      var slide = slides[idx];\n      if (!slide) return;\n      lastIdx = idx;\n      var mediaId = slide.getAttribute('data-media-id');\n\n      for (var i = 0; i < slides.length; i++) {\n        slides[i].classList.toggle('is-active', slides[i] === slide);\n      }\n\n      setHeight(slide);\n\n      if (!thumbsList) return;\n      var items = thumbsList.querySelectorAll('[data-target]');\n      for (var j = 0; j < items.length; j++) {\n        var item = items[j];\n        var btn = item.querySelector('button');\n        var on = item.getAttribute('data-target') === mediaId;\n        if (btn) {\n          if (on) { btn.setAttribute('aria-current', 'true'); }\n          else { btn.removeAttribute('aria-current'); }\n        }\n        if (on) {\n          var outLeft = item.offsetLeft < thumbsList.scrollLeft;\n          var outRight = (item.offsetLeft + item.clientWidth) > (thumbsList.scrollLeft + thumbsList.clientWidth);\n          if (outLeft || outRight) {\n            thumbsList.scrollTo({ left: Math.max(0, item.offsetLeft - 8), behavior: 'smooth' });\n          }\n        }\n      }\n    }\n\n    var raf;\n    viewer.addEventListener('scroll', function () {\n      if (raf) cancelAnimationFrame(raf);\n      raf = requestAnimationFrame(function () {\n        var w = viewer.clientWidth || 1;\n        setActive(Math.round(viewer.scrollLeft / w));\n      });\n    }, { passive: true });\n\n    // On resize/orientation change: instantly snap to the current active slide\n    // without any visible scrolling animation.\n    var rzr;\n    var suppressScroll = false;\n    viewer.style.scrollSnapType = 'x mandatory';\n    window.addEventListener('resize', function () {\n      if (rzr) cancelAnimationFrame(rzr);\n      rzr = requestAnimationFrame(function () {\n        setHeight(slides[lastIdx] || slides[0]);\n        suppressScroll = true;\n        viewer.style.scrollSnapType = 'none';\n        viewer.style.scrollBehavior = 'auto';\n        viewer.scrollLeft = lastIdx * (viewer.clientWidth || 1);\n        viewer.style.scrollSnapType = 'x mandatory';\n        viewer.style.scrollBehavior = '';\n        setTimeout(function () { suppressScroll = false; }, 50);\n      });\n    }, { passive: true });\n\n    gallery.__pviGallerySynced = true;\n    gallery.setAttribute('data-pvi-synced', '1');\n    // Sync once on init (first non-Google image)\n    setActive(Math.round((viewer.clientWidth ? viewer.scrollLeft / viewer.clientWidth : 0)));\n    // Re-pin height after images finish loading (aspect boxes settle).\n    setTimeout(function () { setHeight(slides[lastIdx] || slides[0]); }, 400);\n    window.addEventListener('load', function () { setHeight(slides[lastIdx] || slides[0]); });\n  }\n\n  function initAll() {\n    var galleries = document.querySelectorAll('.pvi-gallery');\n    for (var i = 0; i < galleries.length; i++) initGallery(galleries[i]);\n  }\n\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', initAll);\n  } else {\n    initAll();\n  }\n  document.addEventListener('shopify:section:load', initAll);\n  // Variant changes make product-info.js re-render & REPLACE the media-gallery\n  // DOM after our first init runs (the replacement has no sync attached, so the\n  // height never gets pinned and the gaps come back \u2014 this raced badly on\n  // mobile). A MutationObserver re-inits whenever a fresh slider is inserted.\n  if (window.MutationObserver) {\n    var mo = new MutationObserver(function () {\n      var pending = document.querySelector('media-gallery.pvi-gallery:not([data-pvi-synced]) .pvi-gallery-slider');\n      if (pending) initAll();\n    });\n    mo.observe(document.documentElement, { childList: true, subtree: true });\n  }\n  // Belt-and-suspenders: re-pin after late renders settle.\n  setTimeout(initAll, 600);\n  setTimeout(initAll, 1600);\n  setTimeout(initAll, 3000);\n})();\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   PVI MOBILE ZOOM: PAN + PINCH + TAP-TO-CLOSE\n   Full touch-driven image viewer for the product media modal on mobile.\n   - One finger: pan (translate)\n   - Two fingers: pinch zoom (scale) + pan\n   - Double-tap: toggle between fit and 2x zoom\n   - Single tap (no drag): close modal\n   Desktop is unaffected (pointer: fine guard).\n   \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n(function () {\n  if (window.matchMedia('(hover: hover) and (pointer: fine)').matches) return;\n\n  var scale = 1, posX = 0, posY = 0;\n  var startX, startY, startDist, startScale, startPosX, startPosY;\n  var media = null, moved = false, tapTime = 0;\n\n  function dist(t) {\n    var dx = t[0].clientX - t[1].clientX;\n    var dy = t[0].clientY - t[1].clientY;\n    return Math.sqrt(dx * dx + dy * dy);\n  }\n\n  function apply() {\n    if (!media) return;\n    media.style.transform = 'translate(' + posX + 'px,' + posY + 'px) scale(' + scale + ')';\n  }\n\n  function reset() { scale = 1; posX = 0; posY = 0; if (media) media.style.transform = ''; media = null; }\n\n  function getMedia(modal) {\n    return modal.querySelector('.product-media-modal__content > .active .media') ||\n           modal.querySelector('.product-media-modal__content > .active');\n  }\n\n  document.addEventListener('touchstart', function (e) {\n    var modal = e.target.closest && e.target.closest('.product-media-modal[open]');\n    if (!modal) return;\n    if (e.target.closest('.product-media-modal__toggle')) return;\n    media = getMedia(modal);\n    if (!media) return;\n    moved = false;\n    var t = e.touches;\n    startPosX = posX; startPosY = posY; startScale = scale;\n    if (t.length === 1) {\n      startX = t[0].clientX; startY = t[0].clientY;\n    } else if (t.length === 2) {\n      startX = (t[0].clientX + t[1].clientX) / 2;\n      startY = (t[0].clientY + t[1].clientY) / 2;\n      startDist = dist(t);\n    }\n  }, { passive: true });\n\n  document.addEventListener('touchmove', function (e) {\n    var modal = e.target.closest && e.target.closest('.product-media-modal[open]');\n    if (!modal || !media) return;\n    e.preventDefault();\n    moved = true;\n    var t = e.touches;\n    if (t.length === 1) {\n      posX = startPosX + (t[0].clientX - startX);\n      posY = startPosY + (t[0].clientY - startY);\n    } else if (t.length === 2) {\n      var d = dist(t);\n      scale = Math.max(0.5, Math.min(5, startScale * (d / startDist)));\n      var cx = (t[0].clientX + t[1].clientX) / 2;\n      var cy = (t[0].clientY + t[1].clientY) / 2;\n      posX = startPosX + (cx - startX);\n      posY = startPosY + (cy - startY);\n    }\n    apply();\n  }, { passive: false });\n\n  document.addEventListener('touchend', function (e) {\n    var modal = e.target.closest && e.target.closest('.product-media-modal[open]');\n    if (!modal) return;\n    if (e.target.closest('.product-media-modal__toggle')) return;\n    if (!moved) {\n      var now = Date.now();\n      if (now - tapTime < 300) {\n        // Double tap: toggle zoom\n        if (scale > 1.2) { scale = 1; posX = 0; posY = 0; }\n        else { scale = 2.5; posX = 0; posY = 0; }\n        apply();\n        tapTime = 0;\n      } else {\n        tapTime = now;\n        // Single tap: close after short delay (if no second tap)\n        setTimeout(function () {\n          if (tapTime === now) {\n            var toggle = modal.querySelector('.product-media-modal__toggle');\n            if (toggle) { reset(); toggle.click(); }\n          }\n        }, 320);\n      }\n    }\n  }, { passive: true });\n\n  // When modal opens in landscape, start at 2x zoom\n  var obs = new MutationObserver(function (muts) {\n    for (var i = 0; i < muts.length; i++) {\n      var m = muts[i];\n      if (m.attributeName === 'open' && m.target.classList.contains('product-media-modal')) {\n        if (m.target.hasAttribute('open')) {\n          media = getMedia(m.target);\n          if (window.innerHeight < 500 && window.innerWidth > window.innerHeight) {\n            scale = 2; posX = 0; posY = 0; apply();\n          } else {\n            scale = 1; posX = 0; posY = 0;\n          }\n        } else {\n          reset();\n        }\n      }\n    }\n  });\n  obs.observe(document.body, { attributes: true, subtree: true, attributeFilter: ['open'] });\n})();\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   PVI ARROW CLICK GUARD\n   Prevents arrow clicks from bubbling UP to the modal-opener, while\n   still letting Dawn's own slider-button handlers fire (they listen\n   directly on the button elements). We attach on the .slider-buttons\n   container in BUBBLE phase so Dawn's button handler fires first,\n   then we kill propagation before it reaches the image/modal-opener.\n   \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n(function () {\n  function guardArrows() {\n    document.querySelectorAll('.pvi-gallery .slider-buttons').forEach(function (btns) {\n      if (btns.dataset.pviGuarded) return;\n      btns.dataset.pviGuarded = '1';\n      btns.addEventListener('click', function (e) {\n        e.stopPropagation();\n      });\n    });\n  }\n  guardArrows();\n  document.addEventListener('DOMContentLoaded', guardArrows);\n  if (window.MutationObserver) {\n    new MutationObserver(guardArrows).observe(document.documentElement, { childList: true, subtree: true });\n  }\n})();\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   PVI DESKTOP ZOOM: MOUSE WHEEL + DRAG-TO-PAN + CLICK-TO-CLOSE\n   Only activates on desktop (pointer: fine). Inside the zoom modal:\n   - Mouse wheel: zoom in/out centered on cursor position\n   - Hold left button + drag: pan around the zoomed image\n   - Click (without dragging): close the modal\n   - X button: also closes\n   \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n(function () {\n  if (!window.matchMedia('(hover: hover) and (pointer: fine)').matches) return;\n\n  var scale = 1, posX = 0, posY = 0;\n  var dragging = false, startX, startY, startPosX, startPosY, hasDragged = false;\n  var img = null;\n  var activeModal = null;\n  var clickedMediaId = '';\n  var enforcerTimer = null;\n\n  /* Track which gallery image the user clicked (before modal opens) */\n  document.addEventListener('click', function (e) {\n    var mediaItem = e.target.closest && e.target.closest('.product__media-item, [data-media-id]');\n    if (mediaItem && !e.target.closest('.product-media-modal')) {\n      clickedMediaId = mediaItem.getAttribute('data-media-id') || '';\n    }\n  }, true);\n\n  /* Single source of truth for which modal item should be shown.\n     Priority: Dawn's own .active (set from the opener's data-media-id),\n     then the gallery's is-active slide, then the last-clicked thumb, then first. */\n  function getActiveItem(modal) {\n    var content = modal.querySelector('.product-media-modal__content');\n    if (!content) return null;\n    var items = content.children;\n    if (!items.length) return null;\n    var i;\n    for (i = 0; i < items.length; i++) {\n      if (items[i].classList.contains('active')) return items[i];\n    }\n    var slide = document.querySelector('.product__media-item.is-active');\n    var slideId = slide ? slide.getAttribute('data-media-id') : '';\n    if (slideId) {\n      for (i = 0; i < items.length; i++) {\n        if (items[i].getAttribute('data-media-id') === slideId) return items[i];\n      }\n    }\n    if (clickedMediaId) {\n      for (i = 0; i < items.length; i++) {\n        if (items[i].getAttribute('data-media-id') === clickedMediaId) return items[i];\n      }\n    }\n    return items[0];\n  }\n\n  function findImg(modal) {\n    var item = getActiveItem(modal);\n    if (item) {\n      // In Dawn's modal, image items ARE the <img> element itself (not a wrapper)\n      if (item.tagName === 'IMG') return item;\n      var im = item.querySelector('img');\n      if (im) return im;\n    }\n    return modal.querySelector('.product-media-modal__content img');\n  }\n\n  function apply() {\n    if (!img) return;\n    img.style.transform = 'translate(' + posX + 'px,' + posY + 'px) scale(' + scale + ')';\n    img.style.transformOrigin = 'center center';\n  }\n\n  function reset() {\n    scale = 1; posX = 0; posY = 0;\n    if (img) { img.style.transform = ''; img.style.cursor = ''; }\n    img = null; dragging = false;\n  }\n\n  function updateCursor() {\n    if (!img) return;\n    img.style.cursor = scale > 1.05 ? 'grab' : 'zoom-in';\n  }\n\n  function enforceOneImage(modal) {\n    var content = modal.querySelector('.product-media-modal__content');\n    if (!content) return;\n    var items = content.children;\n    if (!items.length) return;\n\n    var target = getActiveItem(modal);\n    if (!target) return;\n\n    /* Use setProperty (NOT cssText) so we never wipe the zoom transform that\n       lives on the active <img>. Dawn's image items ARE the <img> elements. */\n    for (var i = 0; i < items.length; i++) {\n      if (items[i] === target) {\n        items[i].style.setProperty('display', 'block', 'important');\n        items[i].style.setProperty('max-width', '80vw');\n        items[i].style.setProperty('max-height', '80vh');\n        items[i].style.setProperty('margin', 'auto');\n        items[i].classList.add('active');\n      } else {\n        items[i].style.setProperty('display', 'none', 'important');\n        items[i].classList.remove('active');\n      }\n    }\n\n    modal.style.overflow = 'hidden';\n    content.style.overflow = 'visible';\n    content.style.display = 'flex';\n    content.style.alignItems = 'center';\n    content.style.justifyContent = 'center';\n    content.style.maxWidth = '80vw';\n    content.style.maxHeight = '80vh';\n\n    var dialog = modal.querySelector('.product-media-modal__dialog');\n    if (dialog) {\n      dialog.style.overflow = 'visible';\n      dialog.style.maxWidth = '80vw';\n      dialog.style.maxHeight = '80vh';\n      dialog.style.margin = 'auto';\n    }\n  }\n\n  function startEnforcer(modal) {\n    enforceOneImage(modal);\n    var runs = 0;\n    if (enforcerTimer) clearInterval(enforcerTimer);\n    enforcerTimer = setInterval(function () {\n      if (!modal.hasAttribute('open') || runs > 20) {\n        clearInterval(enforcerTimer);\n        enforcerTimer = null;\n        return;\n      }\n      enforceOneImage(modal);\n      runs++;\n    }, 50);\n  }\n\n  /* WHEEL ZOOM \u2014 re-finds the active image fresh on every event so it works\n     for ANY image, not just the first. Centered on the cursor. */\n  document.addEventListener('wheel', function (e) {\n    var modal = e.target.closest && e.target.closest('.product-media-modal[open]');\n    if (!modal) return;\n    e.preventDefault();\n    e.stopPropagation();\n\n    img = findImg(modal);\n    if (!img) return;\n\n    var rect = img.getBoundingClientRect();\n    var cx = e.clientX - rect.left - rect.width / 2;\n    var cy = e.clientY - rect.top - rect.height / 2;\n\n    var oldScale = scale;\n    var factor = e.deltaY > 0 ? 0.88 : 1.14;\n    scale = Math.max(0.5, Math.min(8, scale * factor));\n\n    var ratio = scale / oldScale;\n    posX = posX * ratio - cx * (ratio - 1);\n    posY = posY * ratio - cy * (ratio - 1);\n\n    apply();\n    updateCursor();\n  }, { capture: true, passive: false });\n\n  /* DRAG-TO-PAN via POINTER events (mouse only). Dawn closes the modal on\n     `pointerup`, which fires BEFORE mouseup \u2014 so we MUST handle the drag with\n     pointer events and suppress that exact pointerup when a drag happened. */\n  document.addEventListener('pointerdown', function (e) {\n    if (e.pointerType !== 'mouse' || e.button !== 0) return;\n    var modal = e.target.closest && e.target.closest('.product-media-modal[open]');\n    if (!modal) return;\n    if (e.target.closest('.product-media-modal__toggle')) return;\n\n    img = findImg(modal);\n    if (!img) return;\n\n    dragging = true;\n    hasDragged = false;\n    startX = e.clientX;\n    startY = e.clientY;\n    startPosX = posX;\n    startPosY = posY;\n    img.style.cursor = 'grabbing';\n    e.preventDefault();\n  }, true);\n\n  document.addEventListener('pointermove', function (e) {\n    if (!dragging || !img) return;\n    var dx = e.clientX - startX;\n    var dy = e.clientY - startY;\n    if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasDragged = true;\n    posX = startPosX + dx;\n    posY = startPosY + dy;\n    apply();\n  }, true);\n\n  /* Capture-phase pointerup at document level fires BEFORE Dawn's bubble-phase\n     pointerup listener on the modal. If the user dragged, we kill the event so\n     Dawn never runs hide(). A plain click (no drag) is left alone, so Dawn's\n     own pointerup closes the modal \u2014 exactly the behaviour we want. */\n  document.addEventListener('pointerup', function (e) {\n    if (!dragging) return;\n    var didDrag = hasDragged;\n    dragging = false;\n    updateCursor();\n    if (didDrag) {\n      e.stopPropagation();\n      e.stopImmediatePropagation();\n      e.preventDefault();\n    }\n  }, true);\n\n  if (window.MutationObserver) {\n    new MutationObserver(function (muts) {\n      for (var i = 0; i < muts.length; i++) {\n        var m = muts[i];\n        if (m.attributeName === 'open' && m.target.classList.contains('product-media-modal')) {\n          var modalEl = m.target;\n          if (modalEl.hasAttribute('open')) {\n            activeModal = modalEl;\n            startEnforcer(modalEl);\n            scale = 1; posX = 0; posY = 0;\n            setTimeout((function (md) {\n              return function () {\n                img = findImg(md);\n                if (img) { img.style.transform = ''; updateCursor(); }\n              };\n            })(modalEl), 120);\n          } else {\n            if (enforcerTimer) { clearInterval(enforcerTimer); enforcerTimer = null; }\n            reset();\n            activeModal = null;\n          }\n        }\n      }\n    }).observe(document.body, { attributes: true, subtree: true, attributeFilter: ['open'] });\n  }\n})();\n"],
  "mappings": "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",
  "names": ["t"]
}
