PageSourceSearch

https://fizz.ca/sites/default/files/asset_injector/js/br_baby_wiza…-4c1b271695eca7d8987d117e420eaff7.js?tlvemf

js fizz.ca collected 2026-09-24 21:12:45 UTC 30,847 bytes, 591 lines download raw bytes

1// Deduplicated getContrastTextColor utility (single global definition)
2function getContrastTextColor(bgColor) {
3  bgColor = bgColor.replace('#', '');
4  if (bgColor.length === 3) {
5    bgColor = bgColor.split('').map(x => x + x).join('');
6  }
7  // Default to black if not a valid hex
8  if (!/^([0-9A-Fa-f]{6})$/.test(bgColor)) return '#000';
9  const r = parseInt(bgColor.substr(0,2),16);
10  const g = parseInt(bgColor.substr(2,2),16);
11  const b = parseInt(bgColor.substr(4,2),16);
12  const luminance = (0.299*r + 0.587*g + 0.114*b)/255;
13  return luminance > 0.6 ? '#000' : '#fff';
14}
15
16// Reusable function to create a tag HTML block
17function createTagHtml({
18  id = '',
19  label = '',
20  labelId = '',
21  backgroundColor = 'transparent',
22  textColor,
23  extraClasses = '',
24  visibilityClass = '',
25  style = '',
26  children = ''
27}) {
28
29  // Only use 'hidden' class for visibility control
30  let hiddenClass = '';
31  if (!label && !(id === 'title-tag' && document.getElementById('custom-baby-wizard')?.getAttribute('mobile-wizard') === 'true')) {
32    hiddenClass = 'hidden';
33  }
34  const resolvedTextColor = textColor || getContrastTextColor(backgroundColor.replace(' ',''));
35  const styleAttr = `background-color: ${backgroundColor}; color: ${resolvedTextColor};${style ? ' ' + style : ''}`;
36  return `
37    <div${id ? ` id="${id}"` : ''} class="step-bubble pill mr-2 d-flex align-items-center${extraClasses ? ' ' + extraClasses : ''}${hiddenClass ? ' ' + hiddenClass : ''}" data-bg="${backgroundColor}" data-color="${resolvedTextColor}" style="${styleAttr}">
38      ${labelId ? `<span id="${labelId}" class="${visibilityClass}"><strong>${label}</strong></span>` : label ? `<strong>${label}</strong>` : ''}
39      ${children}
40    </div>
41  `;
42}
43
44function updateMobileNetworkTag(plan) {
45  const mobileContainer = document.getElementById('custom-baby-wizard-mobile');
46  if (!mobileContainer) return;
47  const tagContainer = mobileContainer.querySelector('#title-tag');
48  const networkLabel = mobileContainer.querySelector('#title-tag-network-label');
49  if (tagContainer && plan && plan.network) {
50    tagContainer.classList.remove('bg-5g', 'bg-4g');
51    tagContainer.classList.add(plan.network === '5G' ? 'bg-5g' : 'bg-4g');
52  }
53  if (networkLabel && plan && plan.network) {
54    const strong = networkLabel.querySelector('strong');
55    if (strong) strong.textContent = plan.network;
56  }
57}
58// Province detection utility
59function getCurrentProvince() {
60  const activeItem = document.querySelector(".dizz-dropdown-item.active");
61  const province = activeItem ? activeItem.getAttribute("shortcode").toUpperCase() : "DEFAULT";
62  return province;
63}
64
65// Set global province variable for use in all province-specific logic
66window.selectedProvince = getCurrentProvince();
67
68// 5. Fonctions utilitaires
69function getLanguage() {
70  return document.documentElement.lang === 'fr' ? 'fr' : 'en';
71}
72
73// Helper function to generate HTML for a single mobile option
74function generateMobileOptionHTML(option, lang, currentMobileOptionsState) {
75  let optionHtml = '';
76  const optionId = option.optionTitleMain.en.replace(/\s+/g, '').toLowerCase();
77  const paramName = option.urlParam;
78
79  if (option.type === "boolean") {
80    const isChecked = currentMobileOptionsState[optionId] ? 'checked' : '';
81    let toggleTextContainerContent = '';
82
83    if (option.optionTitleSecondary) {
84      toggleTextContainerContent = `<span class="texts-minutes-bb-wizard">${option.optionTitleMain[lang]}</span><span class="unlimited-bb-wizard">${option.optionTitleSecondary[lang]}</span>`;
85    } else {
86      toggleTextContainerContent = `<span class="unlimited-bb-wizard">${option.optionTitleMain[lang]}</span>`;
87    }
88
89    optionHtml = `
90          <div class="d-flex justify-content-center align-items-center"><div class="toggle-text-container">${toggleTextContainerContent}</div><div class="toggle-container ml-auto align-self-center"><input type="checkbox" name="${optionId}" data-param-name="${paramName}" data="1" id="${optionId}" class="toggle" data-price="${typeof option.priceIfTrue === 'object' ? option.priceIfTrue[(getCurrentProvince() || 'QC').toUpperCase()] : option.priceIfTrue}" ${isChecked}><label for="${optionId}" class="toggle-label" data-off="${option.values[1][lang]}" data-on="${option.values[0][lang]}"></label></div></div>`;
91    if (option.urlParam === "international") {
92  optionHtml += `<div class="selected-country hidden"><ul class="key-features icon-green-check small auto mb-3"><li><strong id="selected-country-text"></strong><a class="country-edit ml-2 baby-wizard-link" href="#" data-modal-id="internationalModal">modifier</a></li></ul></div>`;
93    }
94  } else if (option.type === "radio") {
95    // Determine province (should be set globally or passed in)
96  let province = getCurrentProvince() || 'QC';
97    let valuesArr = option.values;
98    if (option.urlParam === 'coverage') {
99      if (province === 'QC' && option.valuesQC) valuesArr = option.valuesQC;
100      else if (province === 'ON' && option.valuesON) valuesArr = option.valuesON;
101      else if (province === 'AB' && option.valuesAB) valuesArr = option.valuesAB;
102      else if (province === 'BC' && option.valuesBC) valuesArr = option.valuesBC;
103      else if (province === 'MB' && option.valuesMB) valuesArr = option.valuesMB;
104    }
105    const radioButtonsForCoverage = valuesArr.map((value, index) => {
106      let paddingClass = "";
107      if (valuesArr.length > 1) {
108        if (index === 0) {
109          paddingClass = "pr-2";
110        } else if (index === valuesArr.length - 1) {
111          paddingClass = "pl-2";
112        } else {
113          paddingClass = "px-2";
114        }
115      }
116
117      const isChecked = (currentMobileOptionsState[optionId] === value.urlValue) ? 'checked' : '';
118      return `
119              <div class="form-radio-block col ${paddingClass}">
120                <input class="sr-only" type="radio" name="${optionId}" data-param-name="${paramName}" id="${optionId}-${index}" value="${value.urlValue}" data-price="${value.price}" ${isChecked}>
121                <label class="d-flex justify-content-center align-items-center w-100 text-center py-2 h-100" for="${optionId}-${index}">
122                  <h6 class="mb-0">${value[lang]}</h6>
123                </label>
124              </div>
125            `;
126    }).join('');
127    optionHtml = `
128          <div class="mobile-option-group mt-1">
129            <div class="toggle-text-container">
130                <span class="unlimited-bb-wizard">${option.optionTitleMain[lang]}</span>
131            </div>
132            <div class="row mt-2">
133              ${radioButtonsForCoverage}
134            </div>
135          </div>
136        `;
137  }
138
139  return optionHtml;
140}
141
142// Utility to get the #total-price-tag message
143function getTotalPriceTagMessage(province, lang, isYearlySaving, currentSelectionType, buttonConfigs) {
144  // province: e.g. 'QC', 'ON', etc.  // lang: 'en' or 'fr'  // isYearlySaving: boolean, true for yearly savings, false for manual message  // currentSelectionType: e.g. 'tv-internet', 'internet-only', etc.  // buttonConfigs: config object
145  console.log(province, lang, isYearlySaving, currentSelectionType, buttonConfigs);
146  if (isYearlySaving) {
147    return translations.yearlySavingText[lang];
148  }
149  // For mobile-only, prefer province override
150  if (
151    currentSelectionType === 'mobile-only' &&
152    mobilePlansByProvince[province] &&
153    mobilePlansByProvince[province].customPriceTagMessage &&
154    mobilePlansByProvince[province].customPriceTagMessage[lang]
155  ) {
156    return mobilePlansByProvince[province].customPriceTagMessage[lang];
157  }
158  if (
159    buttonConfigs &&
160    buttonConfigs[currentSelectionType] &&
161    buttonConfigs[currentSelectionType].customPriceTagMessage &&
162    buttonConfigs[currentSelectionType].customPriceTagMessage[lang]
163  ) {
164    return buttonConfigs[currentSelectionType].customPriceTagMessage[lang];
165  }
166  return '';
167}
168
169// Helper function to generate HTML for a single mobile option
170function generateMobileOptionHTML(option, lang, currentMobileOptionsState) {
171  let optionHtml = '';
172  const optionId = option.optionTitleMain.en.replace(/\s+/g, '').toLowerCase();
173  const paramName = option.urlParam;
174
175  if (option.type === "boolean") {
176    const isChecked = currentMobileOptionsState[optionId] ? 'checked' : '';
177    let toggleTextContainerContent = '';
178
179    if (option.optionTitleSecondary) {
180      toggleTextContainerContent = `<span class="texts-minutes-bb-wizard">${option.optionTitleMain[lang]}</span><span class="unlimited-bb-wizard">${option.optionTitleSecondary[lang]}</span>`;
181    } else {
182      toggleTextContainerContent = `<span class="unlimited-bb-wizard">${option.optionTitleMain[lang]}</span>`;
183    }
184
185    optionHtml = `
186          <div class="d-flex justify-content-center align-items-center"><div class="toggle-text-container">${toggleTextContainerContent}</div><div class="toggle-container ml-auto align-self-center"><input type="checkbox" name="${optionId}" data-param-name="${paramName}" data="1" id="${optionId}" class="toggle" data-price="${typeof option.priceIfTrue === 'object' ? option.priceIfTrue[(getCurrentProvince() || 'QC').toUpperCase()] : option.priceIfTrue}" ${isChecked}><label for="${optionId}" class="toggle-label" data-off="${option.values[1][lang]}" data-on="${option.values[0][lang]}"></label></div></div>`;
187    if (option.urlParam === "international") {
188  optionHtml += `<div class="selected-country hidden"><ul class="key-features icon-green-check small auto mb-3"><li><strong id="selected-country-text"></strong><a class="country-edit ml-2 baby-wizard-link" href="#" data-modal-id="internationalModal">modifier</a></li></ul></div>`;
189    }
190  } else if (option.type === "radio") {
191    // Determine province (should be set globally or passed in)
192  let province = getCurrentProvince() || 'QC';
193    let valuesArr = option.values;
194    if (option.urlParam === 'coverage') {
195      if (province === 'QC' && option.valuesQC) valuesArr = option.valuesQC;
196      else if (province === 'ON' && option.valuesON) valuesArr = option.valuesON;
197      else if (province === 'AB' && option.valuesAB) valuesArr = option.valuesAB;
198      else if (province === 'BC' && option.valuesBC) valuesArr = option.valuesBC;
199      else if (province === 'MB' && option.valuesMB) valuesArr = option.valuesMB;
200    }
201    const radioButtonsForCoverage = valuesArr.map((value, index) => {
202      let paddingClass = "";
203      if (valuesArr.length > 1) {
204        if (index === 0) {
205          paddingClass = "pr-2";
206        } else if (index === valuesArr.length - 1) {
207          paddingClass = "pl-2";
208        } else {
209          paddingClass = "px-2";
210        }
211      }
212
213      const isChecked = (currentMobileOptionsState[optionId] === value.urlValue) ? 'checked' : '';
214      return `
215              <div class="form-radio-block col ${paddingClass}">
216                <input class="sr-only" type="radio" name="${optionId}" data-param-name="${paramName}" id="${optionId}-${index}" value="${value.urlValue}" data-price="${value.price}" ${isChecked}>
217                <label class="d-flex justify-content-center align-items-center w-100 text-center py-2 h-100" for="${optionId}-${index}">
218                  <h6 class="mb-0">${value[lang]}</h6>
219                </label>
220              </div>
221            `;
222    }).join('');
223    optionHtml = `
224          <div class="mobile-option-group mt-1">
225            <div class="toggle-text-container">
226                <span class="unlimited-bb-wizard">${option.optionTitleMain[lang]}</span>
227            </div>
228            <div class="row mt-2">
229              ${radioButtonsForCoverage}
230            </div>
231          </div>
232        `;
233  }
234
235  return optionHtml;
236}
237
238// Unified plan details section generator
239function getPlanDetailsSectionHTML({
240  containerKey,
241  initialPlan,
242  lang,
243  wizardAttributes,
244  mergedTagHtml = '',
245  translations,
246  svgVisibilityClass = '',
247}) {
248  if (containerKey === 'tv') {
249    const basePlanDetail = initialPlan.planDetailBase ? initialPlan.planDetailBase[lang] : initialPlan.planDetail[lang];
250    const channelsCount = initialPlan.name ? `+ ${initialPlan.name}` : '';
251    const channelType = initialPlan.type ? initialPlan.type[lang] : '';
252    return `<div class="d-flex flex-wrap"><span class="plan-detail-span mr-1">${basePlanDetail}</span><div><span class="plan-detail-span p-0 mr-0">${channelsCount}</span><p class="bb-gb d-inline-block baby-wizard-tv-product mb-0"><strong>${channelType}</strong></p></div></div>`;
253  } else if (containerKey === 'internet') {
254    const planDetailText = `${initialPlan.download} ${initialPlan.unit}`;
255    let uploadDisplay = '';
256    if (typeof initialPlan.upload === 'number' && initialPlan.upload >= 0 && initialPlan.unit) {
257      uploadDisplay = `${translations.uploadText[lang]} ${initialPlan.upload}&nbsp;${initialPlan.unit}`;
258    }
259    return `<div class="d-flex align-items-baseline flex-column flex-xl-row"><div class="d-flex align-items-center"><span class="plan-detail-span brand-text-accent">${planDetailText}</span><svg xmlns="http://www.w3.org/2000/svg" width="15" height="24" fill="none" viewBox="0 0 15 24" class="mr-1 ${svgVisibilityClass}"><path fill="#00D672" fill-rule="evenodd" d="M7.5 14.611H0L7.5 0v9.42954H15L7.5 24v-9.389Z" clip-rule="evenodd"></path></svg></div><p class="bb-gb d-inline-block baby-wizard-internet-product mb-0"><strong>${uploadDisplay}</strong></p></div>`;
260  } else if (containerKey === 'mobile') {
261    const planData = initialPlan.data;
262    const planUnit = initialPlan.unit[lang];
263    let prefixHtml = '';
264    if (wizardAttributes && wizardAttributes.mobileWizard) {
265      const planDetailPrefix = (typeof translations.planDetailPrefix === 'object' && translations.planDetailPrefix[lang]) ? translations.planDetailPrefix[lang] : 'Data';
266      prefixHtml = `<span class="plan-detail-prefix mr-1">${planDetailPrefix}</span>`;
267    }
268    return `<div class="d-flex align-items-baseline flex-column flex-xl-row"><div class="d-flex align-items-center">${mergedTagHtml}${prefixHtml}<div><span class="plan-detail-span mr-1">${String(planData).replace(/\s*GB\s*$/i, '')}</span><p class="bb-gb d-inline-block baby-wizard-mobile-product mb-0"><strong>${planUnit}</strong></p></div></div></div>`;
269  }
270  return '';
271}
272
273function getModemHTML(lang, modemData, currentRebateAmount) {
274  if (!modemData) return '';
275
276  const title = modemData.title[lang];
277  const description = modemData.description[lang];
278  const imageSrc = modemData.image;
279  const altText = modemData.alt;
280  const featuresHTML = modemData.featuresModem.map(feature => `
281    <li class="${feature.icon}">${feature[lang]}</li>
282  `).join('');
283
284  const tagBackgroundColor = modemData.tagBackgroundColor || 'transparent';
285  const tagTextColor = modemData.tagTextColor || '#000';
286  const modemLabel = modemData.tagLabel?.[lang] || '';
287  const tagModemLabelVisibilityClass = (modemLabel === '') ? 'd-none' : '';
288  // Use createTagHtml for modem tag
289  const mergedTagHtml = createTagHtml({
290    id: 'title-tag',
291    label: modemLabel,
292    labelId: 'title-tag-modem-label',
293    backgroundColor: tagBackgroundColor,
294    textColor: tagTextColor,
295    visibilityClass: tagModemLabelVisibilityClass
296  });
297
298  return `<div class="row"><div class="image col-auto"><div class="modem-img"><div class="text-center"><img src="${imageSrc}" alt="${altText}" class="modem-img-size"></div></div></div><div class="content col d-flex align-items-center p-0 px-xl-3"><div class="d-flex flex-column"><div class="d-flex">${mergedTagHtml}<h4 class="title brand-text-accent mb-0">${title}</h4></div><div><div class="description"><strong>${description}</strong></div><ul class="key-features icon-green-check small list-col-2-responsive icon-align-top mt-2 mt-xl-3 mb-0">${featuresHTML}</ul></div></div></div></div>`;
299}
300
301function createRadioButtonsHTML(lang, wizardAttributes) {
302  const buttonConfigs = wizardContainers.find(def => def.buttonConfigs).buttonConfigs;
303  const activeRadioConfigs = [];
304  if (wizardAttributes.tvWizard) {
305    activeRadioConfigs.push({ type: "tv-only", id: "radioTvOnly", label: buttonConfigs["tv-only"].label[lang] });
306  }
307  if (wizardAttributes.internetWizard) {
308    activeRadioConfigs.push({ type: "internet-only", id: "radioInternetOnly", label: buttonConfigs["internet-only"].label[lang] });
309  }
310  if (wizardAttributes.mobileWizard) {
311    activeRadioConfigs.push({ type: "mobile-only", id: "radioMobileOnly", label: buttonConfigs["mobile-only"].label[lang] });
312  }
313  if (wizardAttributes.tvInternetWizard) {
314    let tvInternetLabel = buttonConfigs["tv-internet"].label[lang];
315    if (wizardAttributes.tvInternetWizard && wizardAttributes.tvWizard && !wizardAttributes.internetWizard) {
316      tvInternetLabel = (lang === 'fr' ? 'Télé + Internet' : 'TV + Internet');
317    }
318    activeRadioConfigs.push({ type: "tv-internet", id: "radioTvInternet", label: tvInternetLabel });
319  }
320
321  const activeOptionsCount = activeRadioConfigs.length;
322
323  // Toggle mode
324  if (wizardAttributes.radioStyle === 'toggle') {
325    const toggleItems = activeRadioConfigs.map(config =>
326      `<input type="radio" name="planSelection" data-plan-type="${config.type}" id="${config.id}" value="${config.type}" tabindex="0"><label for="${config.id}"><span>${config.label}</span></label>`
327    ).join('');
328    return `<div id="custom-baby-wizard-radio-btn" class="wizard-plan-toggle"><div class="wizard-plan-toggle-track">${toggleItems}<div class="wizard-plan-toggle-slider"></div></div></div>`;
329  }
330
331  // Radio mode (default)
332  const radioButtons = [];
333  let colClass = "col-6";
334  if (activeOptionsCount === 4) {
335    colClass = "col-3";
336  } else if (activeOptionsCount === 3) {
337    colClass = "col-4";
338  } else if (activeOptionsCount === 2) {
339    colClass = "col-6";
340  } else if (activeOptionsCount === 1) {
341    colClass = "col-12";
342  }
343
344  activeRadioConfigs.forEach((config, index) => {
345    let paddingClass = "";
346    if (activeOptionsCount === 2) {
347      if (index === 0) {
348        paddingClass = "pr-2";
349      } else if (index === 1) {
350        paddingClass = "pl-2";
351      }
352    } else if (activeOptionsCount === 3) {
353      if (index === 0) {
354        paddingClass = "pr-2";
355      } else if (index === 1) {
356        paddingClass = "px-2";
357      } else if (index === 2) {
358        paddingClass = "pl-2";
359      }
360    } else if (activeOptionsCount === 4) {
361      if (index === 0) {
362        paddingClass = "pr-2";
363      } else if (index === 1 || index === 2) {
364        paddingClass = "px-2";
365      } else if (index === 3) {
366        paddingClass = "pl-2";
367      }
368    }
369
370
371    radioButtons.push(`
372      <div class="form-radio-block ${colClass} ${paddingClass}">
373        <input class="sr-only" type="radio" name="planSelection" data-plan-type="${config.type}" id="${config.id}" value="${config.type}" tabindex="0">
374        <label class="align-self-center w-100 text-center py-2" for="${config.id}">
375          <h6 class="mb-0">${config.label}</h6> </label>
376      </div>
377    `);
378  });
379
380  return `<div class="row" id="custom-baby-wizard-radio-btn">${radioButtons.join('')}</div>`;
381}
382
383function createInternetPlanCardsHTML(lang) {
384  const choosePlanText = lang === 'fr' ? 'Choisir' : 'Choose';
385  const ctaLink = 'https://zone.fizz.ca/dce/customer-ui-prod/subscription/activation';
386  let cardsHTML = '';
387  internetPlans.forEach((plan, planIndex) => {
388    const featuresListHTML = (plan.cardFeatures || []).map((feature) => `<li>${feature[lang]}</li>`).join('');
389    const priceDisplay = lang === 'fr'
390      ? `${Math.round(plan.price)}&nbsp;$`
391      : `$${Math.round(plan.price)}`;
392
393    cardsHTML += `
394      <div>
395        <div class="card bg-surface-neutral-default card-md radius-lg border-neutral-on-muted p-0 text-brand-accent">
396          <div class="card-content-wrapper">
397            <div>
398              <div class="speed d-flex flex-row align-items-center">
399                <span class="download text-brand-accent font-bookmark">${plan.download}</span>
400                <div class="ml-1 text-brand-accent">
401                  <div class="unit">${plan.unit}</div>
402                  <div class="upload d-flex flex-row align-items-center">
403                    ${uploadArrowSvg}
404                    <div>${plan.upload}&nbsp;${plan.unit}</div>
405                  </div>
406                </div>
407              </div>
408            </div>
409            <ul class="key-features icon-check green list-vertical">${featuresListHTML}</ul>
410          </div>
411          <hr class="separator-zigzag" style="margin:0">
412          <div class="d-flex align-items-center justify-content-between mt-3">
413            <div>
414              <span class="heading-h3 font-bookmark">${priceDisplay}</span><span>${translations.period[lang]}</span>
415            </div>
416            <div class="d-flex align-items-center gapx-0-75">
417              <div style="text-decoration:underline;">${choosePlanText}</div>
418              <a href="${ctaLink}" class="btn round-sm bg-surface-brand-low icon icon-arrow-next"></a>
419            </div>
420          </div>
421        </div>
422      </div>`;
423  });
424  return cardsHTML;
425}
426
427function getCardHTML(lang, containerKey, initialPlan, currentSelectionType, wizardAttributes, currentMobileOptionsState, isAdjustable = true) {
428  // Add network tag for mobile wizard before plan-detail-span
429  // Merged tag: network and title label in one tag, before the title
430  let mergedTagHtml = '';
431  if (containerKey === 'mobile') {
432    const networkLabel = initialPlan.network;
433    const networkTagColor = networkLabel === '5G' ? '#00D672' : '#1844D1';
434    const modemData = typeof initialPlan.modem === 'string' && Array.isArray(modemDescriptions)
435      ? modemDescriptions.find(m => m.id === initialPlan.modem) || {} : {};
436    const modemLabel = modemData.tagLabel?.[lang] || '';
437    const tagModemLabelVisibilityClass = (modemLabel === '') ? 'd-none' : '';
438    // Use createTagHtml for merged tag
439    mergedTagHtml = createTagHtml({
440      id: 'title-tag',
441      backgroundColor: networkTagColor,
442      textColor: '#fff',
443      style: 'transition: background-color 0.25s linear;',
444      children: `
445        <span id="title-tag-network-label" class="h5 mb-0"><strong>${networkLabel}</strong></span>
446        <span id="title-tag-modem-label" class="${tagModemLabelVisibilityClass}"><strong>${modemLabel}</strong></span>
447      `
448    });
449  }
450  if (!initialPlan) return '';
451  let price = initialPlan.price;
452  let period = translations.period[lang];
453  let cardClass = "bg-surface-neutral-default radius-md p-3";
454  if (containerKey === 'internet' && initialPlan.modem === 'wifi6') {
455    cardClass = "bg-surface-brand-low radius-md p-3";
456  }
457  if (containerKey === 'mobile') {
458    cardClass = "bg-surface-neutral-default radius-md p-3";
459  }
460  let mobilePlansArr = [];
461  if (containerKey === 'mobile') {
462    const province = (getCurrentProvince() || 'QC').toUpperCase();
463    if (typeof mobilePlansByProvince === 'object' && Array.isArray(mobilePlansByProvince[province]?.plans)) {
464      mobilePlansArr = mobilePlansByProvince[province].plans;
465    }
466  } else if (typeof window !== 'undefined' && window.mobilePlansQC) {
467    mobilePlansArr = window.mobilePlansQC;
468  }
469  let isMinusActive;
470  if (currentSelectionType === 'tv-internet') {
471    isMinusActive = true;
472  } else if (containerKey === 'tv') {
473    isMinusActive = tvPlans.indexOf(initialPlan) > 0;
474  } else if (containerKey === 'internet') {
475    isMinusActive = internetPlans.indexOf(initialPlan) > 0;
476  } else if (containerKey === 'mobile') {
477    isMinusActive = Array.isArray(mobilePlansArr) && mobilePlansArr.indexOf(initialPlan) > 0;
478  } else {
479    isMinusActive = false;
480  }
481  const isPlusActive = (containerKey === 'tv' && tvPlans.indexOf(initialPlan) < tvPlans.length - 1) ||
482    (containerKey === 'internet' && internetPlans.indexOf(initialPlan) < internetPlans.length - 1) ||
483    (containerKey === 'mobile' && Array.isArray(mobilePlansArr) && mobilePlansArr.indexOf(initialPlan) < mobilePlansArr.length - 1);
484  const buttonsHTML = isAdjustable ? `<div class="d-flex ml-4"><button class="circle-button circle- baby-wizard-btn-minus ${isMinusActive ? 'active' : ''}" data-action="subtract"><svg class="fi fi-24 fi-"><use href="/images/icons/feather-sprite.svg#minus"></use></svg></button><button class="circle-button circle- baby-wizard-btn-plus ml-2 ${isPlusActive ? 'active' : ''}" data-action="add"><svg class="fi fi-24 fi-"><use href="/images/icons/feather-sprite.svg#plus"></use></svg></button></div>` : '';
485  let cardFeaturesHTML = '';
486  const buttonConfigs = wizardContainers.find(def => def.buttonConfigs).buttonConfigs;
487  let ulClass = '';
488  if (containerKey === 'internet') {
489    let wizardFeaturesToUse = [];
490    if (currentSelectionType === 'internet-only' && Array.isArray(initialPlan.wizardSoloFeatures)) {
491      wizardFeaturesToUse = initialPlan.wizardSoloFeatures;
492      ulClass = "key-features icon-green-check list-col-2 icon-align-top";
493    } else if (currentSelectionType === 'tv-internet' && Array.isArray(initialPlan.wizardDuoFeatures)) {
494      wizardFeaturesToUse = initialPlan.wizardDuoFeatures;
495      ulClass = "key-features icon-green-check list-col-2 icon-align-top";
496    } else {
497      if (wizardAttributes.tvInternetWizard && Array.isArray(initialPlan.wizardDuoFeatures)) {
498        wizardFeaturesToUse = initialPlan.wizardDuoFeatures;
499        ulClass = "key-features icon-green-check list-col-2 icon-align-top";
500      } else if (wizardAttributes.internetWizard && Array.isArray(initialPlan.wizardSoloFeatures)) {
501        wizardFeaturesToUse = initialPlan.wizardSoloFeatures;
502        ulClass = "key-features icon-green-check list-col-2 icon-align-top";
503      }
504    }
505    let wizardFeaturesHtml = wizardFeaturesToUse.map(feature => `<li class="${feature.icon}">${feature[lang]}</li>`).join('');
506    cardFeaturesHTML = wizardFeaturesHtml;
507  } else if (containerKey === 'tv') {
508    if (buttonConfigs["tv-only"] && Array.isArray(buttonConfigs["tv-only"].features)) {
509      cardFeaturesHTML = buttonConfigs["tv-only"].features.map(feature => {
510        let featureText = feature[lang];
511        return `<li class="${feature.icon}">${featureText}</li>`;
512      }).join('');
513      ulClass = buttonConfigs["tv-only"].featuresClass || '';
514    }
515  } else if (containerKey === 'mobile') {
516    cardFeaturesHTML = generalMobileOptions.map(option => generateMobileOptionHTML(option, lang, currentMobileOptionsState)).join('');
517    ulClass = "key-options mb-0";
518  }
519  const svgVisibilityClass = (containerKey === 'internet' && initialPlan.modem === 'wifi6') ? '' : 'hidden';
520  const helpMeChooseLinkHTML = (containerKey === 'internet') ? `<a href="${translations.plansLink[lang]}" id="help-me-choose-link" class="card-plan-link hidden" target="_blank">${translations.helpMeChooseButton[lang]}</a>` : '';
521  // Use unified plan details section
522  const planDetailsSectionHTML = getPlanDetailsSectionHTML({
523    containerKey,
524    initialPlan,
525    lang,
526    wizardAttributes,
527    mergedTagHtml,
528    translations,
529    svgVisibilityClass,
530  });
531  const _inner = `<div class="progress-input-container"><div class="d-flex justify-content-between align-items-baseline">${planDetailsSectionHTML}${helpMeChooseLinkHTML}<div class="plan-price hidden"><h4 class="d-inline-block baby-wizard-${containerKey}-price mb-0">${lang === 'fr' ? '' : '$'}${price}${lang === 'fr' ? '$' : ''}</h4><span class="d-inline-block baby-wizard-${containerKey}-period mb-0"><strong>${period}</strong></span></div></div><div class="d-flex progress-inner justify-content-between mt-3"><div class="progress"><div class="baby-wizard-${containerKey}-progress-bar progress-bar progress-bar-ascending" role="progressbar" style="width: 0%">&nbsp;</div></div>${buttonsHTML}</div></div><div class="row"><div class="col-12"><ul class="${ulClass} pl-0 my-0">
531${cardFeaturesHTML}</ul></div><div class="col-12"></div></div>`;
532  return containerKey === 'mobile' ? `<div class="baby-wizard-content${cardClass ? ' ' + cardClass : ''}">${_inner}</div>` : _inner;
533}
534
535function showBRPromoCopyFeedback(button, feedbackText) {
536  const originalContent = button.innerHTML;
537  button.innerHTML = feedbackText;
538  button.classList.add('copied');
539  setTimeout(() => {
540    button.innerHTML = originalContent;
541    button.classList.remove('copied');
542  }, 2000);
543}
544
545function fallbackCopyBRPromo(code, successText, failText, button) {
546  try {
547    const textarea = document.createElement('textarea');
548    textarea.value = code;
549    textarea.style.position = 'fixed';
550    textarea.style.opacity = '0';
551    document.body.appendChild(textarea);
552    textarea.select();
553    textarea.setSelectionRange(0, 99999);
554    const successful = document.execCommand('copy');
555    document.body.removeChild(textarea);
556    showBRPromoCopyFeedback(button, successful ? successText : failText);
557  } catch (err) {
558    showBRPromoCopyFeedback(button, failText);
559  }
560}
561
562function copyBRPromoCode(code, successText, failText, button) {
563  if (navigator.clipboard && window.isSecureContext) {
564    navigator.clipboard.writeText(code)
565      .then(() => showBRPromoCopyFeedback(button, successText))
566      .catch(() => fallbackCopyBRPromo(code, successText, failText, button));
567  } else {
568    fallbackCopyBRPromo(code, successText, failText, button);
569  }
570}
571
572window.copyBRPromoCode = copyBRPromoCode;
573
574const brPromoIcons = {
575  'picto-gift': `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32">
576    <path style="fill:var(--picto-tint,#f4b3f1)" fill-rule="evenodd" d="M3.94 14.611c-.63 0-1.142.512-1.142 1.143V27.43a3.43 3.43 0 0 0 3.428 3.428h19.547A3.43 3.43 0 0 0 29.2 27.43V15.754c0-.631-.511-1.143-1.143-1.143z" clip-rule="evenodd"/>
577    <path style="fill:currentColor" fill-rule="evenodd" d="M9.718 7.205c.156.212.263.278.299.296q.118.003.231.023h3.575c-.175-.4-.4-.827-.682-1.241-.797-1.176-1.656-1.712-2.376-1.712-.913 0-1.218.27-1.318.389-.13.152-.249.45-.209.933.04.485.233.976.48 1.312m8.459.319h3.574a2 2 0 0 1 .232-.023c.035-.018.143-.084.299-.296.247-.336.439-.827.479-1.312.04-.483-.079-.78-.208-.933-.1-.118-.406-.389-1.318-.389-.72 0-1.58.536-2.377 1.712-.281.414-.507.84-.681 1.24m7.73 0c.14-.437.233-.89.27-1.346.093-1.105-.128-2.398-1.01-3.437-.912-1.074-2.29-1.598-3.932-1.598-2.421 0-4.18 1.69-5.215 3.215L16 4.39l-.021-.03c-1.035-1.527-2.793-3.216-5.214-3.216-1.643 0-3.02.524-3.932 1.598-.882 1.04-1.103 2.332-1.011 3.437.038.455.13.91.27 1.346h-1.52a3.43 3.43 0 0 0-3.43 3.428v1.374a3.43 3.43 0 0 0 3.43 3.428h10v15.103h2.856V15.754h10a3.43 3.43 0 0 0 3.429-3.428v-1.374a3.43 3.43 0 0 0-3.429-3.428z" clip-rule="evenodd"/>
578  </svg>`,
579};
580
581function getPromoIconHtml(iconName, color, tint) {
582  if (brPromoIcons[iconName]) {
583    const style = [
584      color ? `color:${color}` : '',
585      tint  ? `--picto-tint:${tint}` : '',
586    ].filter(Boolean).join(';');
587    return `<span style="display:inline-flex;flex-shrink:0;width:32px;height:32px;${style ? style + ';' : ''}" class="mr-3">${brPromoIcons[iconName]}</span>`;
588  }
589  return `<span class="${iconName} picto-sm mr-3 flex-shrink-0"></span>`;
590}
591

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.