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} ${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)} $` 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} ${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%"> </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.