PageSourceSearch

https://cloudevo.ai/scripts/scripts.js?c5caaf87bdb5a066cf6e

js cloudevo.ai collected 2026-10-05 11:55:19 UTC 26,156 bytes, 761 lines download raw bytes

1// services
2
3const services = document.querySelectorAll(".services-list li");
4
5const handleIntersection = (entries, observer) => {
6        entries.forEach(entry => {
7            if (entry.isIntersecting) {
8                entry.target.classList.add('on');
9                observer.unobserve(entry.target);
10            }
11        });
12};
13
14const observerOptions = {
15    root: null, 
16    rootMargin: '50px',
17    threshold: 0.5
18};
19
20
21const observer = new IntersectionObserver(handleIntersection, observerOptions);
22
23if(services.length > 0){
24    services.forEach(service => {
25        observer.observe(service);
26    });
27}
28
29// service
30
31const serviceBody = document.querySelectorAll(".service-description");
32
33if(serviceBody.length > 0) serviceBody.forEach(service => observer.observe(service)); 
34
35
36// moto
37
38const homeMoto = document.querySelector(".home-moto");
39
40if(homeMoto) observer.observe(homeMoto);
41
42// home contact 
43
44const homeContact = document.querySelector(".home-contact--wrapper");
45if(homeContact) observer.observe(homeContact);
46
47
48// home services
49
50const homeSerNav = document.querySelector(".home-services .services-nav");
51const homeServices = document.querySelector(".swiperHomeService");
52
53if(homeSerNav && homeServices){
54    observer.observe(homeSerNav);
55    observer.observe(homeServices);
56}
57
58// standard transition
59
60const anim = document.querySelectorAll(".anim");
61
62if(anim.length > 0){
63    anim.forEach(el => observer.observe(el));
64}
65
66// careers
67
68const careersSwiper = new Swiper('.careers-carousel.swiper', {
69    navigation: {
70        nextEl: '.swiper-button-next',
71        prevEl: '.swiper-button-prev',
72      },
73});
74
75// vacanvies
76
77const vacancies = document.querySelectorAll(".vacancy");
78
79if(vacancies.length > 0){
80    vacancies.forEach(vacancy => vacancy.addEventListener("click", function(){
81        vacancy.classList.toggle("open");
82        const vacancyDescription = vacancy.querySelector(".vacancy--description");
83        toggleHeight(vacancyDescription);
84    }));
85}
86
87function toggleHeight(item, start = 0) {
88  if (item.style.height === '0px' || item.style.height === '' || item.style.height === `${start}px` ) {
89    // Υπολόγισε το πραγματικό ύψος
90    console.log(start);
91    item.style.height = 'auto';
92    const height = item.scrollHeight + 'px';
93    item.style.height = `${start}px`; // Επαναφορά στο 0
94    
95    // Ξεκίνα το transition
96    setTimeout(() => {
97      item.style.height = height;
98    }, 10); // Μικρή καθυστέρηση για να εφαρμοστεί το transition
99  } else {
100    // Κλείσιμο του στοιχείου
101    item.style.height = item.scrollHeight + 'px';
101 // Ορίζουμε το τρέχον ύψος για να ξεκινήσει το transition
102    setTimeout(() => {
103      item.style.height = `${start}px`;
104    }, 10);
105  }
106}
107
108// home services swiper
109
110const  autoplayDuration = 5000;
111
112const servicesThumbs = new Swiper('.services-nav.swiper', {
113    spaceBetween: 10,
114    slidesPerView: "auto",
115    freeMode: true,
116    watchSlidesProgress: true,
117    breakpoints : {
118        920 : {
119            spaceBetween: 0,
120            slidesPerView: 5,    
121        },   
122    },
123});
124const servicesSwiper = new Swiper('.swiperHomeService.swiper', {
125    navigation: {
126        nextEl: '.swiper-button-next',
127        prevEl: '.swiper-button-prev',
128    },
129    thumbs: {
130        swiper: servicesThumbs
131    },
132    autoplay: {
133            delay: autoplayDuration,
134            disableOnInteraction: false,
135    },
136    on: {
137        autoplayTimeLeft(s, time, progress) {
138            const active = document.querySelector(".home-services .service-nav-item.swiper-slide-thumb-active span");
139            let move = ((autoplayDuration - time)/autoplayDuration) * -100;
140            if(move === -100) move = 0;
141            active.style.setProperty('--pos', `${move}%`);
142        },
143        init(swiper) {
144            syncServiceCanvasLink(swiper);
145        },
146        slideChange(swiper) {
147            syncServiceCanvasLink(swiper);
148        }
149    }
150});
151
152function syncServiceCanvasLink(swiper) {
153    const canvasLink = document.getElementById('serviceCanvasLink');
154    if (!canvasLink || !swiper) return;
155
156    const activeSlide = swiper.slides[swiper.activeIndex];
157    const slideLink = activeSlide && activeSlide.querySelector('.slide-link');
158    if (slideLink && slideLink.getAttribute('href')) {
159        canvasLink.setAttribute('href', slideLink.getAttribute('href'));
160    }
161}
162
163if(servicesSwiper) servicesSwiper.autoplay.stop();
164
165const autoplayObserver = new IntersectionObserver((entries) => {
166  entries.forEach(entry => {
167    if (entry.isIntersecting) {
168      // Το στοιχείο είναι ορατό - ξεκίνα το autoplay
169      servicesSwiper.autoplay.start();
170    } else {
171      // Το στοιχείο δεν είναι ορατό - σταμάτα το autoplay
172      servicesSwiper.autoplay.stop();
173    }
174  });
175});
176
177if(homeServices) autoplayObserver.observe(homeServices);
178
179
180// menu
181
182const body = document.querySelector("body");
183const panel = document.querySelector(".panel");
184const menu = document.querySelector(".menu");
185const menuBtn = document.querySelector(".menu-btn");
186
187if(menu && menuBtn){
188    menuBtn.addEventListener("click", function(){
189        body.classList.toggle("menu-on");
190        body.classList.toggle("stop-scrolling");
191        menu.classList.toggle("on");
192        panel.classList.toggle("on");
193    });
194}
195
196if(panel){
197    panel.addEventListener("click", function(){
198        body.classList.toggle("menu-on");
199        body.classList.toggle("stop-scrolling");
200        menu.classList.toggle("on");
201        panel.classList.toggle("on");
202    });    
203}
204
205// transitions
206
207// hero
208
209const hero =document.querySelector(".hero");
210
211if(hero){ 
212    observer.observe(hero);
213    setTimeout(() => hero.classList.add("clear"), 5000);
214}
215
216// setion header
217
218const headerIntersection = (entries, headerObserverOptions) => {
219        entries.forEach(entry => {
220            if (entry.isIntersecting) {
221                entry.target.classList.add('on');
222                observer.unobserve(entry.target);
223            }
224        });
225};
226
227const headerObserverOptions = {
228    root: null, 
229    rootMargin: window.innerWidth > 920 ?  '-100px' : '20px',
230    threshold: 0.5
231};
232
233const headerObserver = new IntersectionObserver(headerIntersection, headerObserverOptions);
234
235const sectionHeaders = document.querySelectorAll(".section-header");
236
237if(sectionHeaders.length > 0){
238    sectionHeaders.forEach(header => headerObserver.observe(header));
239}
240
241
242// home transitions
243
244const homeClients = document.querySelector(".home-clients-list");
245if(homeClients) headerObserver.observe(homeClients);
246
247const clientsSwiperEl = document.querySelector('.clientsSwiper');
248if (clientsSwiperEl) {
249    new Swiper('.clientsSwiper', {
250        slidesPerView: 2.5,
251        spaceBetween: 24,
252        loop: true,
253        speed: 800,
254        autoplay: {
255            delay: 2500,
256            disableOnInteraction: false,
257        },
258        breakpoints: {
259            580: {
260                slidesPerView: 3.5,
261                spaceBetween: 32,
262            },
263            950: {
264                slidesPerView: 5,
265                spaceBetween: 40,
266            },
267            1280: {
268                slidesPerView: 6,
269                spaceBetween: 50,
270            },
271        },
272    });
273}
274
275// home ai
276
277const homeAi = document.querySelector(".home-aibanner");
278if(homeAi) headerObserver.observe(homeAi);
279
280
281// service hash
282
283document.addEventListener('DOMContentLoaded', () => {
284    // Συνάρτηση για την εμφάνιση του σωστού div
285    function showSectionFromHash() {
286        // Παίρνουμε το hash από την URL (π.χ. "#area", "#map", "#info")
287        const hash = window.location.hash;
288
289        console.log(hash);
290
291        // Κρύβουμε όλα τα τμήματα περιεχομένου
292        document.querySelectorAll('.service-box').forEach(section => {
293            section.classList.remove('active');
294        });
295
296        const activeNav = document.querySelector(".service-nav-item.selected");      
297        if(activeNav) activeNav.classList.remove("selected");
298
299
300        // Αν υπάρχει hash και αντιστοιχεί σε κάποιο ID
301        if (hash) {
302            const targetId = hash.substring(1); // Αφαιρούμε το "#"
303            if(window.innerWidth > 620){
304                const targetSection = document.getElementById(targetId);
305
306                if (targetSection) {
307                    targetSection.classList.add('active'); // Εμφανίζουμε το επιλεγμένο τμήμα
308                }
309            }else{
310                console.log("target : " + targetId);
311                const slides = document.querySelectorAll(".singleServiceSwiper .swiper-slide");
312                for(let i = 0; i < slides.length; i++){
313                    console.log(slides[i].id);
314                    if(slides[i].id === targetId){
315                        initSwiper(i);
316                        break;    
317                    }
318                }
319
320            }
321            const linkText = `.service-nav-item a[href="#${targetId}"]`;
322            const selectedNav = document.querySelector(linkText);
323            if(selectedNav){
324                selectedNav.closest(".service-nav-item").classList.add("selected");
325                const title = selectedNav.querySelector("span");
326                // console.log(title.innerText);
327                const breadcrumbsTitle = document.querySelector(".breadcrumbs .active-title");
328                breadcrumbsTitle.innerText = title.innerText;
329            }
330        }
331        // Αν δεν υπάρχει hash, μπορείτε να αποφασίσετε αν θα εμφανίζεται κάτι default
332        // π.χ. να εμφανιστεί το #AREA αν δεν υπάρχει hash
333        else {
334            const defaultSection = document.getElementById('data-gathering');
335            if (defaultSection) {
336                defaultSection.classList.add('active');
337                const selectedNav = document.querySelector('.service-nav-item a[href="#data-gathering"]');
338                if(selectedNav) selectedNav.closest(".service-nav-item").classList.add("selected");
339                const breadcrumbsTitle = document.querySelector(".breadcrumbs .active-title");
340                breadcrumbsTitle.innerText = "Data Gathering & Analysis";
341            }
342        }
343    }
344
345    // Καλεί τη συνάρτηση όταν φορτώνει η σελίδα
346    showSectionFromHash();
347
348    // Καλεί τη συνάρτηση όταν αλλάζει το hash (π.χ. ο χρήστης κάνει copy-paste URL με διαφορετικό hash)
349    window.addEventListener('hashchange', showSectionFromHash);
350});
351
352
353let singleServiceSwiper; // Δηλώστε τη μεταβλητή για το Swiper global (εκτός της συνάρτησης)
354
355function initSwiper(start = 0) {
356  // Ελέγξτε αν η οθόνη είναι μικρότερη ή ίση με 620px
357  if (window.innerWidth <= 620) {
358    // Αν δεν έχει ήδη αρχικοποιηθεί το Swiper
359    if (!singleServiceSwiper) {
360      singleServiceSwiper = new Swiper('.singleServiceSwiper.swiper', {
361        // Οι ρυθμίσεις του Swiper σας
362        slidesPerView: 1,
363        spaceBetween: 16,
364        navigation: {
365          nextEl: '.swiper-button-next',
366          prevEl: '.swiper-button-prev',
367        },
368        initialSlide : start,
369        // Προσθέστε όσες ρυθμίσεις θέλετε
370      });
371      console.log('Swiper initialized.');
372    }
373  } else {
374    // Αν η οθόνη είναι μεγαλύτερη από 620px και το Swiper είÎ
374½Î±Î¹ ενεργό
375    if (singleServiceSwiper) {
376      singleServiceSwiper.destroy(true, true); // Καταστρέφει το Swiper και καθαρίζει το DOM
377      singleServiceSwiper = null; // Μηδενίζει τη μεταβλητή
378      console.log('Swiper destroyed.');
379    }
380  }
381}
382
383// 1. Καλέστε την initSwiper όταν φορτώνει η σελίδα
384// document.addEventListener('DOMContentLoaded', initSwiper);
385
386// 2. Καλέστε την initSwiper όταν αλλάζει το μέγεθος του παραθύρου
387// Χρησιμοποιήστε debouncing για καλύτερη απόδοση σε resize events
388let resizeTimeout;
389window.addEventListener('resize', () => {
390  clearTimeout(resizeTimeout);
391  resizeTimeout = setTimeout(initSwiper, 200); // Περιμένει 200ms μετά το resize για να τρέξει
392});
393
394
395// clients 
396
397const clientsWrapper = document.querySelector(".clients-list");
398const clientsList = document.querySelector(".clients-list ul");
399const clientsBtn = document.querySelector(".clients-all");
400
401if(clientsBtn){
402    clientsBtn.addEventListener("click", function(){
403        clientsWrapper.classList.toggle("open");
404        toggleHeight(clientsList, 78);    
405    });
406}
407
408// video observer
409
410const handleVideoIntersection = (entries, observer) => {
411        entries.forEach(entry => {
412            if (entry.isIntersecting) {
413                entry.target.play();
414            }else{
415                entry.target.pause();
416            }
417        });
418};
419
420const videoObserverOptions = {
421    root: null, 
422    rootMargin: '0px',
423    threshold: 0.25
424};
425
426
427const videoObserver = new IntersectionObserver(handleVideoIntersection, videoObserverOptions);
428
429const videos = document.querySelectorAll("video");
430if(videos.length > 0){
431    videos.forEach(vid => videoObserver.observe(vid));
432}
433
434// service canvas
435
436// Εδώ μπορείς να προσθέσεις τον JavaScript κώδικα για να σχεδιάσεις στο canvas
437const canvas = document.getElementById('serviceCanvas');
438
439if(canvas){
440    const ctx = canvas.getContext('2d');
441
442    // Ορίζουμε τις διαστάσεις του canvas
443    const canvasWidth = canvas.width;
444    const canvasHeight = canvas.height;
445
446    // Ορίζουμε τις παραμέτρους για τα τετράγωνα
447    const numberOfSquares = 40;
448    const minSquareSize = 5;
449    const maxSquareSize = 20;
450    const delayPerSquare = 200;
451
452    const squares = [];
453    const bigSquares = [];
454    const maxAttempts = 50;
455
456    function drawRandomSquare(color) {
457        // Ορίζουμε το χρώμα
458        ctx.fillStyle = color;
459
460        const squareSize = getRandomInt(10, 25)
461        
462        // Δημιουργούμε τυχαίες συντεταγμένες για κάθε τετράγωνο
463        const randomX = Math.random() * (canvasWidth - squareSize);
464        const randomY = Math.random() * (canvasHeight - squareSize);
465
466        // Σχεδιάζουμε το τετράγωνο
467        ctx.fillRect(randomX, randomY, squareSize, squareSize);
468    }
469
470    function getRandomInt(min, max) {
471        min = Math.ceil(min); // Στρογγυλοποιεί προς τα πάνω το ελάχιστο όριο
472        max = Math.floor(max); // Στρογγυλοποιεί προς τα κάτω το μέγιστο όριο
473        return Math.floor(Math.random() * (max - min + 1)) + min;
474    }
475
476    // Συνάρτηση ελέγχου σύγκρουσης μεταξύ δύο τετραγώνων
477    function isColliding(square1, square2) {
478        return square1.x < square2.x + square2.size &&
479                square1.x + square1.size > square2.x &&
480                square1.y < square2.y + square2.size &&
481                square1.y + square1.size > square2.y;
482    }
483
484    // Συνάρτηση για τη δημιουργία ενός τετραγώνου με έλεγχο σύγκρουσης
485    function createSquare(color, isBig = false) {
486        let square;
487        let attempts = 0;
488        let isOverlapping = true;
489
490        while (isOverlapping && attempts < maxAttempts) {
491            // Δημιουργία τυχαίων συντεταγμένων
492            // const size = Math.floor(Math.random() * (maxSquareSize - 10 + 1)) + 10;
493            const size = isBig ? getRandomInt(30, 70) : getRandomInt(minSquareSize, maxSquareSize);
494            const x = Math.random() * (canvasWidth - size);
495            const y = Math.random() * (canvasHeight - size);
496
497            square = {
498                x, y,
499                size: 0,
500                color,
501                maxSize: size,
502                speed: Math.random() * 2 + 0.1,
503                isBig : isBig
504            };
505            
506            isOverlapping = false;
507            if(!isBig){
508                for (const existingSquare of squares) {
509                    if (isColliding(square, existingSquare)) {
510                        isOverlapping = true;
511                        break;
512                    }
513                }
514                attempts++;
515            }
516        }
517
518        // Επιστρέφουμε το τετράγωνο μόνο αν βρέθηκε θέση χωρίς επικάλυψη
519        if (isOverlapping && !isBig) {
520            console.log("Δεν βρέθηκε ελεύθερη θέση για ένα τετράγωνο μετά από " + maxAttempts + " προσπάθειες.");
521            return null;
522        }
523        
524        return square;
525    }
526
527    for (let i = 0; i < numberOfSquares; i++) {
528        // const color = (i % 3 == 0) ? '#1EFF1E' : '#F8F5F0';
529        const color = '#F8F5F0';
530        const newSquare = createSquare(color);
531        if (newSquare) {
532            squares.push(newSquare);
533        }
534    }
535
536    const bigSquareInterval = 2000; // 5 δευτερόλεπτα
537    let lastBigSquareTime = Date.now();
538
539    // Μεταβλητές για τη διαχείριση της δημιουργίας νέων τετραγώνων
540    let newSquareCount = 0;
541    const newSquareInterval = 500; // Κάθε 0.5 δευτερόλεπτα προσπαθούμε να προσθέσουμε νέο τετράγωνο
542    let lastNewSquareTime = Date.now();
543
544    // Συνάρτηση animation loop
545    function animate() {
546        // Καθαρίζουμε όλο το canvas
547        ctx.clearRect(0, 0, canvasWidth, canvasHeight);
548
549        // Σχεδιάζουμε όλα τα τετράγωνα
550        squares.forEach(square => {
551            // Αν το τετράγωνο δεν έχει φτάσει το μέγιστο μέγεθος, το μεγαλώνουμε
552            
553            if (square.size < square.maxSize) {
554                square.size += square.speed;
555                if (square.size > square.maxSize) {
556                    square.size = square.maxSize; // Εξασφαλίζουμε ότι δεν θα το ξεπεράσει
557                }
558            }
559
560            // Σχεδιάζουμε το τετράγωνο με το τρέχον μέγεθός του
561            ctx.fillStyle = square.color;
562            ctx.fillRect(square.x, square.y, square.size, square.size);
563        });
564
565        // Έλεγχος για προσθήκη νέου τετραγώνου
566        const now = Date.now();
567        if (squares.length >= numberOfSquares && now - lastNewSquareTime > newSquareInterval) {
568            // Αφαιρούμε το πρώτο τετράγωνο
569            squares.shift();
570            
571            // Δημιουργούμε ένα νέο τετράγωνο
572            const color = '#F8F5F0';
573            const newSquare = createSquare(color, 0); // Το νέο τετράγωνο ξεκινά από μέγεθος 0
574            if (newSquare) {
575                squares.push(newSquare);
576            }
577            
578            newSquareCount++;
579            lastNewSquareTime = now;
580        }
581
582        bigSquares.forEach((square, index) => {
583            if (square.size < square.maxSize) {
584                square.size += square.speed;
585                if (square.size > square.maxSize) {
586                    square.size = square.maxSize;
587                }
588            }
589            ctx.strokeStyle = '#1EFF1E';
590            ctx.lineWidth = 1;
591            ctx.strokeRect(square.x, square.y, square.size, square.size);
592
593            if (index % 2 === 0) {
594                const lineLength = square.size * 2;
595                const centerX = square.x + square.size / 2;
596                const centerY = square.y + square.size / 2;
597                
598                // Πρώτη κάθετη γραμμή
599                ctx.beginPath();
600                ctx.moveTo(centerX, centerY - lineLength / 2);
601                ctx.lineTo(centerX, centerY + lineLength / 2);
602                ctx.strokeStyle = '#FF6CF5';
603                ctx.stroke();
604
605                // Δεύτερη κάθετη γραμμή
606                ctx.beginPath();
607                ctx.moveTo(centerX - lineLength / 2, centerY);
608                ctx.lineTo(centerX + lineLength / 2, centerY);
609                ctx.strokeStyle = '#FF6CF5';
610                ctx.stroke();
611            }
612        });
613        if (now - lastBigSquareTime > bigSquareInterval) {
614            if (bigSquares.length >= 5) {
615            bigSquares.shift();
616            }
617
618            const newBigSquare = createSquare('', true);
619            if (newBigSquare) {
620                bigSquares.push(newBigSquare);
621            }
622            
623            lastBigSquareTime = now;
624        }
625
626        // Καλεί ξανά τη συνάρτηση στο επόμενο διαθέσιμο frame
627        requestAnimationFrame(animate);
628    }
629
630    // Ξεκινάμε το animation
631    animate();
632}
633
634// share modal
635(function initShareModal() {
636    const openBtns = document.querySelectorAll('[data-share-open]');
637    const modal = document.querySelector('[data-share-modal]');
638    if (!openBtns.length || !modal) return;
639
640    const toast = modal.querySelector('[data-share-toast]');
641    const pageUrl = window.location.href;
642    const pageTitle = document.title;
643
644    const shareUrls = {
645        linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(pageUrl)}`,
646        facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(pageUrl)}`,
647        x: `https://twitter.com/intent/tweet?url=${encodeURIComponent(pageUrl)}&text=${encodeURIComponent(pageTitle)}`,
648    };
649
650    const openModal = () => {
651        modal.hidden = false;
652        document.body.classList.add('share-modal-open');
653    };
654
655    const closeModal = () => {
656        modal.hidden = true;
657        document.body.classList.remove('share-modal-open');
658        if (toast) toast.hidden = true;
659    };
660
661    const copyLink = async () => {
662        try {
663            await navigator.clipboard.writeText(pageUrl);
664        } catch (_) {
665            const input = document.createElement('input');
666            input.value = pageUrl;
667            document.body.appendChild(input);
668            input.select();
669            document.execCommand('copy');
670            document.body.removeChild(input);
671        }
672
673        if (toast) {
674            toast.hidden = false;
675            setTimeout(() => {
676                toast.hidden = true;
677            }, 1500);
678        }
679    };
680
681    openBtns.forEach((btn) => {
682        btn.addEventListener('click', (e) => {
683            e.preventDefault();
684            openModal();
685        });
686    });
687
688    modal.addEventListener('click', (e) => {
689        if (e.target.closest('[data-share-close]')) {
690            closeModal();
691            return;
692        }
693
694        const target = e.target.closest('[data-share-network]');
695        if (!target) return;
696        e.preventDefault();
697
698        const network = target.getAttribute('data-share-network');
699        if (network === 'copy') {
700            copyLink();
701            return;
702        }
703
704        const url = shareUrls[network];
705        if (!url) return;
706        window.open(url, '_blank', 'noopener,noreferrer,width=640,height=720');
707        closeModal();
708    });
709
710    document.addEventListener('keydown', (e) => {
711        if (e.key === 'Escape' && !modal.hidden) closeModal();
712    });
713})();
714
715// Open existing Quantcast / InMobi Choice privacy UI
716(function initQcPrivacyLink() {
717    function openPrivacyUi() {
718        try {
719            const tcf = window.__tcfapi || (window.top && window.top.__tcfapi);
720            if (typeof tcf === 'function') {
721                tcf('displayConsentUi', 2, function () {});
722                return true;
723            }
724        } catch (_) {}
725
726        try {
727            const usp = window.__uspapi || (window.top && window.top.__uspapi);
728            if (typeof usp === 'function') {
729                usp('displayUspUi');
730                return true;
731            }
732        } catch (_) {}
733
734        const qcLink =
735            document.getElementById('qc-cmp2-persistent-link') ||
736            document.querySelector('.qc-cmp2-persistent-link');
737        if (qcLink) {
738            qcLink.dispatchEvent(new MouseEvent('click', {
739                bubbles: true,
740                cancelable: true,
741                view: window,
742            }));
743            return true;
744        }
745        return false;
746    }
747
748    document.addEventListener('click', (e) => {
749        const trigger = e.target.closest('[data-qc-privacy]');
750        if (!trigger) return;
751        e.preventDefault();
752
753        if (openPrivacyUi()) return;
754
755        let tries = 0;
756        const timer = setInterval(() => {
757            tries += 1;
758            if (openPrivacyUi() || tries >= 40) clearInterval(timer);
759        }, 100);
760    });
761})();

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.