1/* Plastic Surgery Simulator â static site behaviour. No dependencies. */ 2(function () { 3 'use strict'; 4 5 function ready(fn) { 6 if (document.readyState !== 'loading') fn(); 7 else document.addEventListener('DOMContentLoaded', fn); 8 } 9 10 /* Language preference ------------------------------------------------ */ 11 var LANGUAGE_STORAGE_KEY = 'pss.site.language'; 12 var LANGUAGE_HOMES = { 13 en: '/', fr: '/fr/', es: '/es/', 'pt-br': '/pt-br/', ko: '/ko/', 14 ja: '/ja/', it: '/it/', tr: '/tr/', 'zh-hans': '/zh-hans/' 15 }; 16 17 function languageFromPath(pathname) { 18 var match = (pathname || '').toLowerCase().match(/^\/(en|fr|es|pt-br|ko|ja|it|tr|zh-hans)(?:\/|$)/); 19 return match ? match[1] : null; 20 } 21 22 function languageFromBrowser() { 23 var browserLanguage = (navigator.language || 'en').toLowerCase(); 24 var primary = browserLanguage.split('-')[0]; 25 if (primary === 'pt') return 'pt-br'; 26 if (primary === 'zh') return 'zh-hans'; 27 return LANGUAGE_HOMES[primary] ? primary : 'en'; 28 } 29 30 function readLanguagePreference() { 31 try { 32 var value = localStorage.getItem(LANGUAGE_STORAGE_KEY); 33 return LANGUAGE_HOMES[value] ? value : null; 34 } catch (error) { 35 return null; 36 } 37 } 38 39 function rememberLanguage(code) { 40 if (!LANGUAGE_HOMES[code]) return; 41 try { 42 localStorage.setItem(LANGUAGE_STORAGE_KEY, code); 43 } catch (error) { 44 /* Storage may be disabled. Language navigation must still work. */ 45 } 46 } 47 48 function applyInitialLanguagePreference() { 49 var explicitLanguage = languageFromPath(window.location.pathname); 50 if (explicitLanguage) { 51 rememberLanguage(explicitLanguage); 52 return false; 53 } 54 55 /* Only the unlocalized home page is an automatic language gateway. */ 56 if (window.location.pathname !== '/') return false; 57 58 var preferredLanguage = readLanguagePreference(); 59 if (!preferredLanguage) { 60 preferredLanguage = languageFromBrowser(); 61 rememberLanguage(preferredLanguage); 62 } 63 64 if (preferredLanguage !== 'en') { 65 window.location.replace(LANGUAGE_HOMES[preferredLanguage]); 66 return true; 67 } 68 return false; 69 } 70 71 /* Mobile menu ------------------------------------------------------- */ 72 function initMenu() { 73 var burger = document.querySelector('[data-burger]'); 74 var nav = document.querySelector('[data-mobile-nav]'); 75 if (!burger || !nav) return; 76 burger.addEventListener('click', function () { 77 var open = nav.classList.toggle('is-open'); 78 burger.setAttribute('aria-expanded', open ? 'true' : 'false'); 79 }); 80 nav.addEventListener('click', function (e) { 81 if (e.target.closest('a')) { 82 nav.classList.remove('is-open'); 83 burger.setAttribute('aria-expanded', 'false'); 84 } 85 }); 86 document.addEventListener('click', function (e) { 87 if (!nav.classList.contains('is-open')) return; 88 if (nav.contains(e.target) || burger.contains(e.target)) return; 89 nav.classList.remove('is-open'); 90 burger.setAttribute('aria-expanded', 'false'); 91 }); 92 } 93 94 /* Language home-page chooser ---------------------------------------- */ 95 function initLanguageMenus() { 96 var desktop = document.querySelector('[data-language-menu]'); 97 var mobileNav = document.querySelector('[data-mobile-nav]'); 98 var frenchPage = languageFromPath(window.location.pathname) === 'fr'; 99 100 if (!desktop) { 101 var headerInner = document.querySelector('header > div'); 102 if (headerInner) { 103 var template = document.createElement('template'); 104 template.innerHTML = [ 105 '<details class="language-menu language-menu--desktop" data-language-menu>', 106 '<summary aria-label="' + (frenchPage ? 'Changer de langue' : 'Change language') + '"><img src="/assets/flags/' + (frenchPage ? 'icons8-french-flag.svg' : 'icons8-usa.svg') + '" alt="" width="20" height="14"></summary>', 107 '<div class="language-menu-panel" role="navigation" aria-label="' + (frenchPage ? 'L
107angue' : 'Language') + '">', 108 '<a href="/"' + (frenchPage ? '' : ' aria-current="true"') + '><img src="/assets/flags/icons8-usa.svg" alt="" width="20" height="14">English</a>', 109 '<a href="/fr/"' + (frenchPage ? ' aria-current="true"' : '') + '><img src="/assets/flags/icons8-french-flag.svg" alt="" width="20" height="14">Français</a>', 110 '<a href="/es/"><img src="/assets/flags/icons8-spain-flag.svg" alt="" width="20" height="14">Español</a>', 111 '<a href="/pt-br/"><img src="/assets/flags/icons8-brazil.svg" alt="" width="20" height="14">Português (Brasil)</a>', 112 '<a href="/ko/"><img src="/assets/flags/icons8-south-korea.svg" alt="" width="20" height="14">íêµì´</a>', 113 '<a href="/ja/"><img src="/assets/flags/icons8-japan.svg" alt="" width="20" height="14">æ¥æ¬èª</a>', 114 '<a href="/it/"><img src="/assets/flags/icons8-italian-flag.svg" alt="" width="20" height="14">Italiano</a>', 115 '<a href="/tr/"><img src="/assets/flags/icons8-turkey.svg" alt="" width="20" height="14">Türkçe</a>', 116 '<a href="/zh-hans/"><img src="/assets/flags/icons8-china.svg" alt="" width="20" height="14">ç®ä½ä¸æ</a>', 117 '</div></details>' 118 ].join(''); 119 desktop = template.content.firstElementChild; 120 var burger = headerInner.querySelector('[data-burger]'); 121 headerInner.insertBefore(desktop, burger || null); 122 } 123 } 124 125 if (desktop && mobileNav && !mobileNav.querySelector('[data-language-menu-mobile]')) { 126 var mobile = desktop.cloneNode(true); 127 mobile.removeAttribute('data-language-menu'); 128 mobile.setAttribute('data-language-menu-mobile', '1'); 129 mobile.classList.remove('language-menu--desktop'); 130 mobile.classList.add('language-menu-mobile'); 131 mobile.removeAttribute('open'); 132 mobileNav.insertBefore(mobile, mobileNav.firstChild); 133 } 134 135 var menus = document.querySelectorAll('[data-language-menu], [data-language-menu-mobile]'); 136 if (!menus.length) return; 137 138 Array.prototype.forEach.call(menus, function (menu) { 139 menu.addEventListener('click', function (event) { 140 var link = event.target.closest('a'); 141 if (!link) return; 142 var href = link.getAttribute('href'); 143 Object.keys(LANGUAGE_HOMES).some(function (language) { 144 if (LANGUAGE_HOMES[language] !== href) return false; 145 rememberLanguage(language); 146 return true; 147 }); 148 }); 149 }); 150 151 document.addEventListener('click', function (event) { 152 Array.prototype.forEach.call(menus, function (menu) { 153 if (!menu.contains(event.target)) menu.removeAttribute('open'); 154 }); 155 }); 156 157 document.addEventListener('keydown', function (event) { 158 if (event.key !== 'Escape') return; 159 Array.prototype.forEach.call(menus, function (menu) { 160 if (!menu.hasAttribute('open')) return; 161 menu.removeAttribute('open'); 162 var summary = menu.querySelector('summary'); 163 if (summary) summary.focus(); 164 }); 165 }); 166 } 167 168 /* Home: four-tab view switcher -------------------------------------- */ 169 function initViewTabs() { 170 var buttons = document.querySelectorAll('[data-tab-btn]'); 171 if (!buttons.length) return; 172 var panels = document.querySelectorAll('[data-tab-panel]'); 173 174 function select(name) { 175 Array.prototype.forEach.call(buttons, function (b) { 176 var on = b.getAttribute('data-tab-btn') === name; 177 b.style.background = on ? '#eceae3' : '#f8f6f0'; 178 b.style.color = on ? '#16292c' : '#5f6a67'; 179 b.setAttribute('aria-selected', on ? 'true' : 'false'); 180 }); 181 Array.prototype.forEach.call(panels, function (p) { 182 p.hidden = p.getAttribute('data-tab-panel') !== name; 183 }); 184 } 185 186 Array.prototype.forEach.call(buttons, function (b) { 187 b.addEventListener('click', function () { select(b.getAttribute('data-tab-btn')); }); 188 }); 189 select(document.querySelector('[data-tab-default]') ? 190 document.querySelector('[data-tab-default]').getAttribute('data-tab-default') : 'comparison'); 191 } 192 193 /* Home: procedures carousel ----------------------------------------- */ 194 function initCarousel() { 195 var track = document.querySelector('[data-proc-track]'); 196 if (!track) return; 197 var slides = Array.prototype.slice.call(track.querySelectorAll('[data-proc]')); 198 if (!slides.length) return; 199 var viewport = track.parentElement; 200 var title = document.querySelector('[data-proc-title]'); 201 var note = document.querySelector('[data-proc-note]'); 202 var prev = document.querySelector('[data-proc-prev]'); 203 var next = document.querySelector('[data-proc-next]'); 204 var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); 205 var i = 0; 206 207 function position(slide) { 208 return slide.getBoundingClientRect().left - track.getBoundingClientRect().left; 209 } 210 211 function select(n) { 212 i = n; 213 if (title) title.textContent = slides[i].getAttribute('data-label'); 214 if (note) note.textContent = slides[i].getAttribute('data-note');
215 slides.forEach(function (slide, index) { 216 slide.toggleAttribute('data-selected', index === i); 217 }); 218 } 219 220 function show(n) { 221 var index = (n + slides.length) % slides.length; 222 viewport.scrollTo({ left: position(slides[index]), behavior: reducedMotion.matches ? 'instant' : 'smooth' }); 223 } 224 225 viewport.addEventListener('scroll', function () { 226 var nearest = 0; 227 slides.forEach(function (slide, index) { 228 if (Math.abs(position(slide) - viewport.scrollLeft) < 229 Math.abs(position(slides[nearest]) - viewport.scrollLeft)) nearest = index; 230 }); 231 if (nearest !== i) select(nearest); 232 }, { passive: true }); 233 234 slides.forEach(function (slide, index) { 235 var selectedOnPointerDown = false; 236 slide.addEventListener('pointerdown', function () { 237 selectedOnPointerDown = index === i; 238 }); 239 slide.addEventListener('click', function (event) { 240 if ((event.detail ? selectedOnPointerDown : index === i) || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return; 241 event.preventDefault(); 242 show(index); 243 }); 244 }); 245 if (prev) prev.addEventListener('click', function () { show(i - 1); }); 246 if (next) next.addEventListener('click', function () { show(i + 1); }); 247 viewport.addEventListener('keydown', function (event) { 248 if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; 249 event.preventDefault(); 250 show(i + (event.key === 'ArrowRight' ? 1 : -1)); 251 }); 252 new ResizeObserver(function () { 253 viewport.scrollTo({ left: position(slides[i]), behavior: 'instant' }); 254 }).observe(viewport); 255 select(0); 256 } 257 258 /* Get started: platform switcher ------------------------------------ */ 259 function initPlatformTabs() { 260 var buttons = document.querySelectorAll('[data-os-btn]'); 261 if (!buttons.length) return; 262 var panels = document.querySelectorAll('[data-os-panel]'); 263 264 var reportedPlatform = 265 (navigator.userAgentData && navigator.userAgentData.platform) || 266 navigator.platform || 267 navigator.userAgent; 268 var detectedPlatform = /mac/i.test(reportedPlatform) ? 'mac' : 'windows'; 269 270 function select(os) { 271 Array.prototype.forEach.call(buttons, function (b) { 272 var on = b.getAttribute('data-os-btn') === os; 273 b.style.borderBottomColor = on ? '#1d5960' : 'transparent'; 274 b.style.color = on ? '#16292c' : '#6b7a77'; 275 b.style.fontWeight = on ? '600' : '400'; 276 b.setAttribute('aria-selected', on ? 'true' : 'false'); 277 }); 278 Array.prototype.forEach.call(panels, function (p) { 279 p.hidden = p.getAttribute('data-os-panel') !== os; 280 }); 281 } 282 283 Array.prototype.forEach.call(buttons, function (b) { 284 b.addEventListener('click', function () { select(b.getAttribute('data-os-btn')); }); 285 }); 286 select(detectedPlatform); 287 } 288 289 /* FAQ accordion ------------------------------------------------------ */ 290 function initAccordion() { 291 var buttons = document.querySelectorAll('[data-question]'); 292 if (!buttons.length) return; 293 294 Array.prototype.forEach.call(buttons, function (b) { 295 b.addEventListener('click', function () { 296 var panel = document.getElementById(b.getAttribute('aria-controls')); 297 var open = panel.hidden; 298 Array.prototype.forEach.call(buttons, function (other) { 299 var p = document.getElementById(other.getAttribute('aria-controls')); 300 p.hidden = true; 301 other.setAttribute('aria-expanded', 'false'); 302 var sign = other.querySelector('[data-sign]'); 303 if (sign) sign.textContent = '+'; 304 }); 305 if (open) { 306 panel.hidden = false; 307 b.setAttribute('aria-expanded', 'true'); 308 var sign = b.querySelector('[data-sign]'); 309 if (sign) sign.textContent = '\u2013'; 310 } 311 }); 312 }); 313 } 314 315 /* Support form ------------------------------------------------------- */ 316 function initSupportForm() { 317 var form = document.querySelector('[data-support-form]'); 318 if (!form) return; 319 form.addEventListener('submit', function (e) { 320 e.preventDefault(); 321 var ok = form.querySelector('[data-sent]'); 322 if (ok) ok.hidden = false; 323 form.querySelector('button[type="submit"]').disabled = true; 324 });
325 } 326 327 if (!applyInitialLanguagePreference()) { 328 ready(function () { 329 initMenu(); 330 initLanguageMenus(); 331 initViewTabs(); 332 initCarousel(); 333 initPlatformTabs(); 334 initAccordion(); 335 initSupportForm(); 336 }); 337 } 338})();
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.