PageSourceSearch

https://investidor10.com.br/microfront-ends/app-download-bar/

js investidor10.com.br collected 2026-09-24 08:53:27 UTC 28,410 bytes, 760 lines download raw bytes

1class AppDownloadBar extends HTMLElement {
2    /**
3     * Publica cedo o estado esperado do banner na sessão para evitar conflitos de timing
4     * com outros banners do projeto (ex.: banners IRPF).
5     *
6     * @param {boolean} shouldShow
7     */
8    setSessionShouldShow(shouldShow) {
9        try {
10            sessionStorage.setItem('app-download-should-show', shouldShow ? '1' : '0');
11        } catch {
12            // storage pode estar indisponível (Safari private mode / quota etc.)
13        }
14    }
15
16    async connectedCallback() {
17        await this.ensureAlpine();
18
19        this.downloadAppUrl = 'null';
20        this.laravelUrl = 'https://investidor10.com.br';
21        this.isAndroid = 'null' === '1';
22        this.isiOS = 'null' === '1';
23        this.timeoutToShowBanner = '30000';
24        this.isMobileDevice = 'false' === 'true';
25        this.hasAppInstalled = 'false' === 'true';
26        const breakpointValue = parseInt('768');
27        this.breakpoint = isNaN(breakpointValue) ? 768 : breakpointValue;
28
29        const isMobile = window.innerWidth < this.breakpoint;
30        const isTouchDevice = window.matchMedia("(pointer: coarse)").matches;
31
32        this.shouldRenderMobile = isMobile && (isTouchDevice || this.isMobileDevice);
33
34        if (!this.shouldRenderMobile) {
35            this.setSessionShouldShow(false);
36            this.style.display = 'none';
37            return;
38        }
39
40        if (this.hasAppInstalled) {
41            this.setSessionShouldShow(false);
42            this.style.display = 'none';
43            return;
44        }
45
46        if (!this.downloadAppUrl || this.downloadAppUrl === 'null') {
47            this.setSessionShouldShow(false);
48            this.style.display = 'none';
49            return;
50        }
51
52        const closedAt = localStorage.getItem('app-download-bar-closed-at');
53        if (closedAt) {
54            const closedDate = new Date(closedAt);
55            const currentDate = new Date();
56            const limitPeriod = parseInt(localStorage.getItem('limit-period') || '3', 10);
57            const diffInDays = Math.floor((currentDate - closedDate) / (1000 * 60 * 60 * 24));
58
59            if (diffInDays < limitPeriod) {
60                this.setSessionShouldShow(false);
61                this.style.display = 'none';
62                return;
63            }
64        }
65
66        this.setSessionShouldShow(true);
67        this.render();
68
69        this.initAlpine();
70    }
71
72    async ensureAlpine() {
73        if (window.Alpine && typeof window.Alpine.initTree === 'function') {
74            return Promise.resolve();
75        }
76
77        return new Promise((resolve) => {
78            let attempts = 0;
79            const maxAttempts = 50;
80
81            const checkAlpine = () => {
82                attempts++;
83
84                if (window.Alpine && typeof window.Alpine.initTree === 'function') {
85                    resolve();
86                    return;
87                }
88
89                if (attempts >= maxAttempts) {
90                    this.loadAlpineFromCDN().then(resolve).catch(() => {
91                        console.warn('[AppDownloadBar] Alpine.js not available, component may not work correctly');
92                        resolve();
93                    });
94                    return;
95                }
96
97                setTimeout(checkAlpine, 100);
98            };
99
100            checkAlpine();
101        });
102    }
103
104    loadAlpineFromCDN() {
105        return new Promise((resolve, reject) => {
106            if (window.Alpine) {
107                resolve();
108                return;
109            }
110
111            const existingScript = document.querySelector('script[src*="alpinejs"]');
112            if (existingScript) {
113                const checkInterval = setInterval(() => {
114                    if (window.Alpine) {
115                        clearInterval(checkInterval);
116                        resolve();
117                    }
118                }, 50);
119
120                setTimeout(() => {
121                    clearInterval(checkInterval);
122                    if (!window.Alpine) {
123                        reject(new Error('Alpine.js failed to load'));
124                    } else {
125                        resolve();
126                    }
127                }, 5000);
128                return;
129            }
130
131            const script = document.createElement('script');
132            script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js';
133            script.defer = false;
134            script.onload = () => {
135                setTimeout(() => {
136                    if (window.Alpine) {
137                        resolve();
138                    } else {
139                        reject(new Error('Alpine.js failed to initialize'));
140                    }
141                }, 50);
142            };
143            script.onerror = () => {
144                reject(new Error('Failed to load Alpine.js from CDN'));
145            };
146            document.head.appendChild(script);
147        });
148    }
149
150    initAlpine() {
151        if (!window.Alpine) {
152            console.error('[AppDownloadBar] Alpine.js is not available');
153            return;
154        }
155
156        requestAnimationFrame(() => {
157            try {
158                if (window.Alpine && typeof window.Alpine.initTree === 'function') {
159                    window.Alpine.initTree(this);
160                }
161            } catch (error) {
162                console.error('[AppDownloadBar] Failed to initialize Alpine:', error);
163                setTimeout(() => {
164                    try {
165                        if (window.Alpine && typeof window.Alpine.initTree === 'function') {
166                            window.Alpine.initTree(this);
167                        }
168                    } catch (retryError) {
169                        console.error('[AppDownloadBar] Retry failed:', retryError);
170                    }
171                }, 100);
172            }
173        });
174    }
175
176    getAlpineData() {
177        const closedAt = localStorage.getItem('app-download-bar-closed-at');
178        const limitPeriod = parseInt(localStorage.getItem('limit-period') || '3', 10);
179        const currentDate = new Date();
180
181        let shouldShow = true;
182        if (closedAt) {
183            const closedDate = new Date(closedAt);
184            const diffInDays = Math.floor((currentDate - closedDate) / (1000 * 60 * 60 * 24));
185            shouldShow = diffInDays >= limitPeriod;
186        }
187
188        if (this.hasAppInstalled) {
189            shouldShow = false;
190        }
191
192        if (!this.downloadAppUrl || this.downloadAppUrl === 'null') {
193            shouldShow = false;
194        }
195
196        // Capturar o breakpoint da instância da classe
197        const breakpoint = this.breakpoint || 768;
198        const shouldRenderMobile = this.shouldRenderMobile;
199
200        return {
201            showBanner: false,
202            showModal: false,
203            shouldShow: shouldShow,
204            downloadAppUrl: this.downloadAppUrl,
205            laravelUrl: this.laravelUrl,
206            isAndroid: this.isAndroid,
207            isiOS: this.isiOS,
208            bannerVisible: false,
209            timeoutToShowBanner: this.timeoutToShowBanner,
210            isClosing: false,
211            breakpoint: breakpoint,
212            isMobile: shouldRenderMobile,
213            checkMobile() {
214                this.isMobile = shouldRenderMobile;
215            },
216            init() {
217                this.checkMobile();
218                // Listener para atualizar quando a tela mudar de tamanho
219                const handleResize = () => {
220                    this.checkMobile();
221                    // Se a tela ficar maior que o breakpoint, esconder o banner
222                    if (!this.isMobile && this.showBanner) {
223                        this.showBanner = false;
224                    }
225                };
226                
227                window.addEventListener('resize', handleResize);
228                
229                // Monitorar mudanças no showBanner para garantir que só apareça em mobile
230                this.$watch('showBanner', (value) => {
231                    if (value && !this.isMobile) {
232                        this.showBanner = false;
233                    }
234                });
235                
236                sessionStorage.setItem('app-download-should-show', this.shouldShow ? 1 : 0);
237                if (this.shouldShow && this.isMobile) {
238                    setTimeout(() => {
239                        // Verificar novamente antes de mostrar (caso a tela tenha mudado)
240                        this.checkMobile();
241                        if (this.isMobile) {
242                            this.showBanner = true;
243                            this.trackImpression();
244                        }
245                    }, this.timeoutToShowBanner);
246                }
247            },
248
249            openModal() {
250                // Prevent opening if already open or currently closing
251                if (this.showModal || this.isClosing) return;
252
253                this.isClosing = false; // Reset flag if opening
254                this.showModal = true;
255                this.trackEvent('cliques_baixar_app');
256            },
257
258            closeModal() {
259                try {
260                    // Prevent multiple calls
261                    if (!this.showModal || this.isClosing) return;
262
263                    this.isClosing = true;
264                    this.showModal = false;
265
266                    // Force hide the modal element to prevent reopening
267                    const modalElement = document.getElementById('download_app_modal_c2');
268                    if (modalElement) {
269                        modalElement.style.display = 'none';
270                        modalElement.classList.remove('show');
271                    }
272
273                    // Reset closing flag after a short delay to prevent immediate reopening
274                    setTimeout(() => {
275                        this.isClosing = false;
276                    }, 300);
277                } catch (error) {
278                    console.error('[AppDownloadBar] Error closing modal:', error);
279                    // Force hide even if there's an error
280                    this.showModal = false;
281                    this.isClosing = false;
282                    const modalElement = document.getElementById('download_app_modal_c2');
283                    if (modalElement) {
284                        modalElement.style.display = 'none';
285                        modalElement.classList.remove('show');
286                    }
287                }
288            },
289            closeBanner() {
290                try {
291                    this.showBanner = false;
292                    sessionStorage.setItem('app-download-should-show', '0');
293                    window.dispatchEvent(new CustomEvent('app-download-banner-closed'));
294                    this.fullyRemoveSelector();
295                    this.saveBannerState(7);
296                } catch (error) {
297                    console.error('[AppDownloadBar] Error closing banner:', error);
298                    this.showBanner = false;
299                    sessionStorage.setItem('app-download-should-show', '0');
300                    window.dispatchEvent(new CustomEvent('app-download-banner-closed'));
301                }
302            },
303            fullyRemoveSelector() {
304                setTimeout(() => {
305                    const tag = document.querySelector('app-download-bar')
306                    if (tag) {
307                        tag.remove();
308                    }
309                }, 1500);
310            },
311            saveBannerState(period) {
312                setTimeout(() => {
313                    this.safeSet('app-download-bar-closed-at', new Date().toISOString());
314                    this.safeSet('limit-period', period.toString());
315                }, 1500);
316            },
317            safeSet(key, value) {
318                if (typeof window === 'undefined') return;
319                if (typeof window?.localStorage === 'undefined') return;
320                if (key === 'app-download-bar-closed-at' || key === 'limit-period') {
321                    try {
322                        localStorage.setItem(key, value);
323                    } catch {
324                        // 
325                    }
326                    return;
327                }
328            },
329            trackEvent(label) {
330                try {
331                    window.dataLayer = window.dataLayer || [];
332
333                    window.dataLayer.push({
334                        event: 'Smartbanner',
335                        event_category: 'B2',
336                        event_label: label
337                    });
338
339                } catch (error) {
340                    console.error('[AppDownloadBar] Failed to track event:', error);
341                }
342            },
343
344            trackImpression() {
345                if (this.bannerVisible) return;
346                this.bannerVisible = true;
347                this.trackEvent('impressoes_baixar_app');
348            },
349
350            handleDownloadAppClick() {
351                if (this.isAndroid) {
352                    this.trackEvent('Google_Play');
353                } else if (this.isiOS) {
354                    this.trackEvent('App_Store');
355                }
356            }
357        };
358    }
359
360    render() {
361        const styles = `<style>
362            /* Hide elements with x-cloak until Alpine is ready */
363            [x-cloak] {
364                display: none !important;
365            }
366
367            #android-app-download-bar-b2 {
368                z-index: 9999;
369                position: fixed;
370                left: 0;
371                right: 0;
372                width: 100%;
373                max-width: 100vw;
374                box-sizing: border-box;
375                z-index: 99;
376                opacity: 1;
377                height: 440px;
378                bottom: 0;
379                transform: translateY(100%);
380                border-radius: 16px 16px 0 0;
381                border-right: 1px solid #C2C0BC;
382                border-bottom: 1px solid #C2C0BC;
383                border-left: 1px solid #C2C0BC;
384                background: #ffffff;
385                transition: transform 0.5s ease-out;
386                display: none;
387            }
388
389            @media (max-width: ${this.breakpoint}px) {
390                #android-app-download-bar-b2 {
391                    display: block;
392                }
393            }
394
395            #android-app-download-bar-b2.show-banner {
396                transform: translateY(0);
397                box-shadow: 0 0 80px 0 #594426;
398            }
399
400            #android-app-download-bar-b2 .container-download-app {
401                position: relative;
402                display: flex;
403                padding: 16px 12px 20px;
404                flex-direction: column;
405                align-items: center;
406                gap: 16px;
407            }
408
409            #android-app-download-bar-b2 .header-container-download-app {
410                display: flex;
411                justify-content: space-between;
412                align-items: center;
413                width: 100%;
414            }
415
416            #android-app-download-bar-b2 .header-container-download-app .logo {
417                width: 200px;
418            }
419
420            #android-app-download-bar-b2 .header-container-download-app .btn-close-container-download-app {
421                padding: 0 5px;
422                border-radius: 100%;
423                border: none;
424                background: #F3F4F7;
425                color: white;
426                cursor: pointer;
427                transition: background-color 0.2s ease;
428                width: 30px;
429                height: 30px;
430                display: flex;
431                justify-content: center;
432                align-items: center;
433            }
434
435            #android-app-download-bar-b2 .header-container-download-app .btn-close-container-download-app:hover {
436                background: #1f2329;
437            }
438
439            #android-app-download-bar-b2 .header-container-download-app .btn-close-container-download-app:focus {
440                outline: 2px solid #009974;
441                outline-offset: 2px;
442            }
443
444            #android-app-download-bar-b2 .description {
445                text-align: center;
446                font-family: Figtree, sans-serif;
447                font-size: 18px;
448                font-weight: 500;
449                line-height: 120%;
450                background: linear-gradient(91deg, #2F3137 0.94%, #54432B 136.28%);
451                background-clip: text;
452                -webkit-background-clip: text;
453                -webkit-text-fill-color: transparent;
454            }
455
456            #android-app-download-bar-b2 .content {
457                display: flex;
458                flex-direction: column;
459                justify-content: center;
460                align-items: center;
461                gap: 8px;
462                width: 100%;
463            }
464
465            #android-app-download-bar-b2 .content .reviews {
466                display: flex;
467                justify-content: center;
468                align-items: center;
469                gap: 10px;
470            }
471
472            #android-app-download-bar-b2 .content .reviews img {
473                width: 70px;
474                padding-bottom: 4px;
475            }
476
477            #android-app-download-bar-b2 .content .reviews .review {
478                color: #2F3137;
479                font-family: Figtree, sans-serif;
480                font-size: 14px;
481                font-weight: 600;
482                line-height: 20px;
483            }
484
485            #android-app-download-bar-b2 .content .downloads {
486                display: flex;
487                justify-content: center;
488                align-items: center;
489                gap: 12px;
490            }
491
492            #android-app-download-bar-b2 .content .downloads .text {
493                color: #2F3137;
494                font-family: Figtree, sans-serif;
495                font-size: 14px;
496                font-weight: 500;
497                line-height: 16px;
498            }
499
500            #android-app-download-bar-b2 .btn-download {
501                display: flex;
502                padding: 16px 24px;
503                justify-content: center;
504                align-items: center;
505                gap: 8px;
506                width: 100%;
507                border-radius: 100px;
508                border: none;
509                box-shadow: 0 2px 10px rgba(8, 33, 69, 0.25);
510                cursor: pointer;
511                font-family: Figtree, sans-serif;
512                font-weight: 700;
513                font-size: 16px;
514                line-height: 24px;
515                z-index: 1058;
516                background: #F7BA6E;
517                color: #000;
518                transition: transform 0.2s ease, box-shadow 0.2s ease;
519            }
520
521            #android-app-download-bar-b2 .btn-download:hover {
522                transform: translateY(-2px);
523                box-shadow: 0 4px 15px rgba(8, 33, 69, 0.35);
524            }
525
526            #android-app-download-bar-b2 .btn-download:active {
527                transform: translateY(0);
528            }
529
530            #android-app-download-bar-b2 .btn-download:focus {
531                outline: 2px solid #fff;
532                outline-offset: 2px;
533            }
534
535            #android-app-download-bar-b2 .img-download {
536                width: 270px;
537                height: 400px;
538                position: absolute;
539                top: 55%;
540                bottom: -150px;
541            }
542
543            /* MODAL STRUCTURE */
544            .modal-default {
545                position: fixed;
546                top: 0;
547                left: 0;
548                width: 100%;
549                height: 100%;
550                background-color: rgba(0, 0, 0, 0.66);
551                z-index: 1059;
552                display: none;
553                justify-content: center;
554                flex-direction: column;
555                align-items: center;
556            }
557
558            @media (max-width: 767px) {
559                .modal-default {
560                    padding: 25px;
561                }
562            }
563
564            #download_app_modal_c2.show {
565                display: flex !important;
566            }
567
568            #download_app_modal_c2 .modal-default-container-download-app {
569                padding: 35px 26px 10px;
570                border-radius: 5px;
571                background: linear-gradient(180deg, #fff 0%, #b4b4b4 100%);
572                width: 100%;
573                max-width: 620px;
574                position: relative;
575                box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
576                animation-delay: 0.3s;
577            }
578
579            #download_app_modal_c2 .modal-default-container-download-app .btn-close-container-download-app {
580                position: absolute;
581                right: 10px;
582                top: 3px;
583                border: none;
584                background: transparent;
585                line-height: 35px;
586                font-size: 50px;
587                font-weight: 100;
588                cursor: pointer;
589                color: #000;
590                transition: color 0.2s ease;
591            }
592
593            #download_app_modal_c2 .modal-default-container-download-app .btn-close-container-download-app:hover {
594                color: #666;
595            }
596
597            #download_app_modal_c2 .modal-default-container-download-app .btn-close-container-download-app:focus {
598                outline: 2px solid #009974;
599                outline-offset: 2px;
600            }
601
602            #download_app_modal_c2 .modal-body {
603                display: flex;
604                flex-direction: column;
605                align-items: center;
606                gap: 14px;
607            }
608
609            #download_app_modal_c2 .modal-body .logo {
610                height: 94px;
611                display: flex;
612                justify-content: center;
613            }
614
615            #download_app_modal_c2 .modal-body .logo img {
616                height: 100%;
617                width: auto;
618            }
619
620            #download_app_modal_c2 .modal-body .description {
621                font-family: "Nunito", sans-serif;
622                display: flex;
623                justify-content: center;
624                align-items: center;
625                color: #000;
626                font-size: 20px;
627                font-weight: 700;
628                gap: 4px;
629            }
630
631            #download_app_modal_c2 .modal-body .description strong {
632                font-weight: bolder !important;
633            }
634
635            #download_app_modal_c2 .modal-body .btns {
636                display: flex;
637                gap: 20px;
638            }
639
640            #download_app_modal_c2 .modal-body .btns a {
641                flex: 1;
642                transition: transform 0.2s ease;
643            }
644
645            #download_app_modal_c2 .modal-body .btns a:hover {
646                transform: scale(1.05);
647            }
648
649            #download_app_modal_c2 .modal-body .btns a:active {
650                transform: scale(0.98);
651            }
652
653            #download_app_modal_c2 .modal-body .btns img {
654                width: 100%;
655            }
656        </style>`;
657
658        this._alpineData = this.getAlpineData();
659
660        const componentId = 'app-download-bar-6ab4e5077b2de';
661        this._componentId = componentId;
662
663        window[`__alpineData_${componentId}`] = () => this.getAlpineData();
664
665        const html = `
666            <div 
667                id="android-app-download-bar-b2"
668                x-data="window['__alpineData_${componentId}']()"
669                x-init="init()"
670                x-show="showBanner && shouldShow && isMobile"
671                x-cloak
672                x-transition:enter="transition ease-out duration-500"
673                x-transition:enter-start="transform translate-y-full"
674                x-transition:enter-end="transform translate-y-0"
675                x-transition:leave="transition ease-in duration-500"
676                x-transition:leave-start="transform translate-y-0"
677                x-transition:leave-end="transform translate-y-full"
678                :class="{ 'show-banner': showBanner && shouldShow && isMobile }"
679                role="banner"
680                aria-label="Baixar aplicativo"
681            >
682                <div class="container-download-app">
683                    <div class="header-container-download-app">
684                        <div style="overflow: hidden; width: 20px;" aria-hidden="true"></div>
685                        <img 
686                            class="logo" 
687                            src="${this.laravelUrl}/images/i10-logo-2.svg" 
688                            alt="Investidor10"
689                            loading="lazy"
690                        >
691                        <button 
692                            type="button" 
693                            class="btn-close-container-download-app" 
694                            @click.stop="closeBanner()"
695                            aria-label="Fechar banner"
696                        >
697                            <svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9" fill="none">
698                                <path d="M8.86813 8.23142C8.90994 8.27322 8.9431 8.32286 8.96573 8.37748C8.98835 8.4321 9 8.49065 9 8.54977C9 8.6089 8.98835 8.66744 8.96573 8.72207C8.9431 8.77669 8.90994 8.82632 8.86813 8.86813C8.82632 8.90994 8.77669 8.9431 8.72207 8.96573C8.66744 8.98835 8.6089 9 8.54977 9C8.49065 9 8.4321 8.98835 8.37748 8.96573C8.32286 8.9431 8.27322 8.90994 8.23142 8.86813L4.5 5.13615L0.768582 8.86813C0.684149 8.95257 0.569632 9 0.450225 9C0.330818 9 0.216301 8.95257 0.131868 8.86813C0.0474343 8.7837 2.3538e-09 8.66918 0 8.54977C-2.3538e-09 8.43037 0.0474343 8.31585 0.131868 8.23142L3.86385 4.5L0.131868 0.768582C0.0474343 0.684149 -8.89652e-10 0.569632 0 0.450225C8.89652e-10 0.330818 0.0474343 0.216301 0.131868 0.131868C0.216301 0.0474343 0.330818 8.89653e-10 0.450225 0C0.569632 -8.89652e-10 0.684149 0.0474343 0.768582 0.131868L4.5 3.86385L8.23142 0.131868C8.31585 0.0474343 8.43037 -2.3538e-09 8.54977 0C8.66918 2.3538e-09 8.7837 0.0474343 8.86813 0.131868C8.95257 0.216301 9 0.330818 9 0.450225C9 0.569632 8.95257 0.684149 8.86813 0.768582L5.13615 4.5L8.86813 8.23142Z" fill="#2F3137"/>
699                            </svg>
700                        </button>
701                    </div>
702                    
703                    <span class="description">
704                        Invista com mais informação e praticidade. Tudo o que você mais gosta no site,<br/>agora no app.
705                    </span>
706
707                    <div class="content">
708                        <div class="reviews">
709                            <img 
710                                src="${this.laravelUrl}/images/reviews.svg" 
711                                alt="Avaliações"
712                                loading="lazy"
713                            >
714                            <span class="review">4.9</span>
715                        </div>
716                        <div class="downloads">
717                            <img 
718                                src="${this.laravelUrl}/assets/front/general-icons/app-store.svg" 
719                                alt="App Store"
720                                loading="lazy"
721                            >
722                            <img 
723                                src="${this.laravelUrl}/assets/front/general-icons/google-play.svg" 
724                                alt="Google Play"
725                                loading="lazy"
726                            >
727                            <span class="text">Mais de 1 milhão de downloads</span>
728                        </div>
729                    </div>
730
731                    <a
732                        href="${this.downloadAppUrl}"
733                        class="btn-download" 
734                        @click="handleDownloadAppClick()"
735                        aria-label="Baixar aplicativo grátis"
736                    >
737                        Quero baixar o app grátis
738                    </a>
739
740                    <img 
741                        class="img-download" 
742                        src="${this.laravelUrl}/images/popup-download-img-v6.svg" 
743                        alt=""
744                        loading="lazy"
745                        aria-hidden="true"
746                    >
747                </div>
748            </div>`;
749
750        this.innerHTML = styles + html;
751    }
752
753    disconnectedCallback() {
754        if (this._componentId && window[`__alpineData_${this._componentId}`]) {
755            delete window[`__alpineData_${this._componentId}`];
756        }
757    }
758}
759
760customElements.define('app-download-bar', AppDownloadBar);

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.