PageSourceSearch

https://www.plastic-surgery-simulator.com/static/v20260909-12/site.js

js plastic-surgery-simulator.com collected 2026-10-02 04:07:30 UTC 13,993 bytes, 338 lines download raw bytes

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.