PageSourceSearch

https://lave.live/pricing

html lave.live collected 2026-10-02 01:59:39 UTC 144,255 bytes, 3,059 lines download raw bytes

1<!DOCTYPE html>
2
3<html lang="en"
4    dir="ltr">
5
6<head>
7
8    <link rel="stylesheet" data-navigate-track
9        href="https://lave.live/css/localization.css?direction=ltr&v=3">
10    <meta name="csrf-token" content="ovoz3KLq7cYiRVt0AJRBD4c02Ozq7Z9iI2ZIIRks">
11    <meta charset="utf-8">
12    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no" />
13
14
15    <title>Business Plan Pricing | Lave</title>
16<meta name="description" content="View Lave Business plan pricing for online stores, payments, custom domains, staff access, marketing tools, and business management.">
17<link rel="canonical" href="https://lave.live/pricing">
18<meta property="og:type" content="website">
19<meta property="og:locale" content="en_US">
20<meta property="og:site_name" content="Lave">
21<meta property="og:url" content="https://lave.live/pricing">
22<meta property="og:title" content="Business Plan Pricing | Lave">
23<meta property="og:description" content="See the Lave Business plan price and the tools included for selling online and managing your business.">
24<meta property="og:image" content="https://fls-a04cfec4-d445-4c41-8c2c-0bc04adc61a3.laravel.cloud/main_assets/opengraph/index.jpg">
25<meta name="twitter:card" content="summary_large_image">
26<meta name="twitter:site" content="@laveappofficial">
27<meta name="twitter:title" content="Business Plan Pricing | Lave">
28<meta name="twitter:description" content="View Lave Business plan pricing for online stores, payments, custom domains, staff access, marketing tools, and business management.">
29<meta name="twitter:image" content="https://fls-a04cfec4-d445-4c41-8c2c-0bc04adc61a3.laravel.cloud/main_assets/opengraph/index.jpg">
30<link rel="alternate" hreflang="x-default" href="https://lave.live/pricing">
31<link rel="alternate" hreflang="en" href="https://lave.live/pricing">
32<link rel="alternate" hreflang="fr" href="https://lave.live/fr/pricing">
33<link rel="alternate" hreflang="ar" href="https://lave.live/ar/pricing">
34<meta name="keywords" content="Lave pricing, Lave Business plan, sell online Africa, mobile money ecommerce, Lave Ghana pricing, payment fees Africa, Lave store cost, online store pricing Africa">
35<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"Lave","url":"https://lave.live"}</script>
vendor: 1 bytes, line 35
35
36<script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"Lave","url":"https://lave.live","logo":"https://lave.live/images/icons/icon-512x512.png"}</script>
vendor: 1 bytes, line 36
36
37<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Business Plan Pricing | Lave","description":"See the Lave Business plan price and the tools included for selling online and managing your business.","url":"https://lave.live/pricing"}</script>
37
38    <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon/favicon-32x32.png?v=1.0">
39    <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon/favicon-16x16.png?v=1.0">
40    <link rel="shortcut icon" href="/images/favicon/favicon-32x32.png?v=1.0">
41    <link rel="preconnect" href="https://im.zoho.com">
42    <link rel="preconnect" href="https://static.zohocdn.com">
43    <link rel="preconnect" href="https://desk.zoho.com">
44    <link rel="dns-prefetch" href="//im.zoho.com">
45    <link rel="dns-prefetch" href="//static.zohocdn.com">
46    <link rel="dns-prefetch" href="//desk.zoho.com">
47
48
49
50    
51    <!-- Web Application Manifest -->
52
53    <link rel="manifest" href="/manifest.json">
54
55    <!-- Chrome for Android theme color -->
56    <meta name="theme-color" content="white">
57
58    <!-- Add to homescreen for Chrome on Android -->
59    <meta name="mobile-web-app-capable" content="yes">
60    <meta name="application-name" content="Lave">
61    <link rel="icon" sizes="512x512" href="/images/icons/icon-512x512.png?v=1.0">
62
63    <!-- Add to homescreen for Safari on iOS -->
64    <meta name="apple-mobile-web-app-capable" content="yes">
65    <meta name="apple-mobile-web-app-status-bar-style" content="white">
66    <meta name="apple-mobile-web-app-title" content="Lave">
67    <link rel="apple-touch-icon" href="/images/icons/icon-512x512.png?v=1.0">
68
69    
69<script src="https://cdn.jsdelivr.net/npm/[email protected]/cdn.min.js"></script>
69
70
71    
71<script>
72        if (typeof window.iosPWASplash === 'function') {
73            window.iosPWASplash("/images/icons/icon-512x512.png?v=1.0", '#EC4899');
74        }
75    </script>
75
76
77    <!-- Tile for Win8 -->
78    <meta name="msapplication-TileColor" content="blue">
79    <meta name="msapplication-TileImage" content="/images/icons/icon-512x512.png?v=1.0">
80
81    
82
83    <link rel="stylesheet"
84        href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/uikit.min.css?v=3" />
85
86    
86<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/uikit.min.js"></script>
86
87    
87<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/uikit-icons.min.js"></script>
87
88
89    
89<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
89
90
91
92    <link rel="stylesheet"
93        href="https://lave.live/css/tailwind-full.css?v=1790843855-3">
94
95    
96    <style>
97        .uk-button:not(.uk-button-link):not(.uk-button-text):disabled,
98        .uk-button:not(.uk-button-link):not(.uk-button-text):disabled:hover {
99            background-color: #f9fafb;
100            border-color: #e5e7eb;
101            /* Ring, because a bare .uk-button has no border of its own to colour. */
102            box-shadow: 0 0 0 1px #e5e7eb;
103            color: #6b7280;
104        }
105    </style>
106
107    <!-- Date Range Picker CSS -->
108    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/daterangepicker.css?v=3">
109    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select/dist/css/tom-select.css?v=3">
110    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/driver.js/dist/driver.css?v=3">
111
112    <link
113        href="https://fonts.googleapis.com/css2?family=Acme&family=Aladin&family=Inter&family=Poppins&family=Roboto&family=Manrope&family=Space+Grotesk&family=Plus+Jakarta+Sans&family=Sora&family=Open+Sans&family=Lato&family=Ubuntu&family=Nunito&family=Work+Sans&family=Syne&family=Montserrat&display=swap&v=3"
114        rel="stylesheet">
115
116    <link rel="preconnect" href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;500;600;700&display=swap"
117        rel="stylesheet">
118
119    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css?v=3">
120
121
122    
122<script src="https://unpkg.com/[email protected]"></script>
122
123
124    
124<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/hugerte.min.js"></script>
124
125    <meta name="facebook-domain-verification" content="zylazptgkc1z0arghjheromt5oq39m" />
126    <style>
127        html {
128            overscroll-behavior: none;
129            background-color: #f9fafb;
130            scrollbar-gutter: stable;
131        }
132
133        body {
134            font-family: 'Inter', 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
135        }
136
137
138        @font-face {
139            font-family: 'Nova Mono';
140            src: url('https://lave.live/fonts/nova-mono.ttf') format('truetype');
141            font-weight: normal;
142            font-style: normal;
143        }
144
145        .font-nova-mono {
146            font-family: 'Nova Mono', monospace !important;
147        }
148
149        /* Responsive page header spacing */
150        .page-header {
151            padding-top: 2.5rem;
152            /* pt-10 for mobile */
153        }
154
155        @media (min-width: 768px) {
156            .page-header {
157                padding-top: 5rem;
158                /* pt-20 for tablets and laptops */
159            }
160        }
161
162
163        .uk-notification {
164            z-index: 100 !important;
165        }
166    </style>
167
168                    <!-- Meta Pixel Code -->
169        
169<script>
170            ! function(f, b, e, v, n, t, s) {
171                if (f.fbq) return;
172                n = f.fbq = function() {
173                    n.callMethod ?
174                        n.callMethod.apply(n, arguments) : n.queue.push(arguments)
175                };
176                if (!f._fbq) f._fbq = n;
177                n.push = n;
178                n.loaded = !0;
179                n.version = '2.0';
180                n.queue = [];
181                t = b.createElement(e);
182                t.async = !0;
183                t.src = v;
184                s = b.getElementsByTagName(e)[0];
185                s.parentNode.insertBefore(t, s)
186            }(window, document, 'script',
187                'https://connect.facebook.net/en_US/fbevents.js');
188            window.laveMetaAdvancedMatchingData = {};
189            window.laveMetaPixelId = "950672977777178";
190            window.laveMetaPixelOptions = {"testEventCode":"TEST52976"};
191            window.laveApplyMetaAdvancedMatching = function(source) {
192                if (typeof fbq === 'undefined' || !window.laveMetaPixelId) {
193                    return;
194                }
195
196                const data = {};
197
198                if (source instanceof HTMLFormElement) {
199                    const email = source.querySelector('[name="email"]')?.value?.trim().toLowerCase();
200
201                    if (email) {
202                        data.em = email;
203                    }
204                } else if (source && typeof source === 'object') {
205                    Object.assign(data, source);
206                }
207
208                if (!data.em) {
209                    return;
210                }
211
212                const signature = JSON.stringify(data);
213                if (window.laveMetaAdvancedMatchingSignature === signature) {
214                    return;
215                }
216
217                window.laveMetaAdvancedMatchingSignature = signature;
218                window.laveMetaAdvancedMatchingData = data;
219
220                if (window.laveMetaPixelOptions) {
221                    fbq('init', window.laveMetaPixelId, data, window.laveMetaPixelOptions);
222                } else {
223                    fbq('init', window.laveMetaPixelId, data);
224                }
225            };
226                            fbq('init', window.laveMetaPixelId, window.laveMetaAdvancedMatchingData, window.laveMetaPixelOptions);
227                    </script>
227
228        <!-- End Meta Pixel Code -->
229    
230            <!-- Google Tag Manager -->
231        
231<script>
vendor: 610 bytes, lines 231-245
231
232            (function(w, d, s, l, i) {
233                w[l] = w[l] || [];
234                w[l].push({
235                    'gtm.start': new Date().getTime(),
236                    event: 'gtm.js'
237                });
238                var f = d.getElementsByTagName(s)[0],
239                    j = d.createElement(s),
240                    dl = l != 'dataLayer' ? '&l=' + l : '';
241                j.async = true;
242                j.src =
243                    'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
244                f.parentNode.insertBefore(j, f);
245            })(window, document, 'script', 'dataLayer', '
245GTM-WFZBP7S3
vendor: 12 bytes, lines 245-246
245');
246        
246</script>
246
247        <!-- End Google Tag Manager -->
248        <!-- Livewire Styles --><style >[wire\:loading][wire\:loading], [wire\:loading\.delay][wire\:loading\.delay], [wire\:loading\.list-item][wire\:loading\.list-item], [wire\:loading\.inline-block][wire\:loading\.inline-block], [wire\:loading\.inline][wire\:loading\.inline], [wire\:loading\.block][wire\:loading\.block], [wire\:loading\.flex][wire\:loading\.flex], [wire\:loading\.table][wire\:loading\.table], [wire\:loading\.grid][wire\:loading\.grid], [wire\:loading\.inline-flex][wire\:loading\.inline-flex] {display: none;}[wire\:loading\.delay\.none][wire\:loading\.delay\.none], [wire\:loading\.delay\.shortest][wire\:loading\.delay\.shortest], [wire\:loading\.delay\.shorter][wire\:loading\.delay\.shorter], [wire\:loading\.delay\.short][wire\:loading\.delay\.short], [wire\:loading\.delay\.default][wire\:loading\.delay\.default], [wire\:loading\.delay\.long][wire\:loading\.delay\.long], [wire\:loading\.delay\.longer][wire\:loading\.delay\.longer], [wire\:loading\.delay\.longest][wire\:loading\.delay\.longest] {display: none;}[wire\:offline][wire\:offline] {display: none;}[wire\:dirty]:not(textarea):not(input):not(select) {display: none;}:root {--livewire-progress-bar-color: rgb(231, 20, 111);}[x-cloak="x-cloak"],[x-cloak=""] {display: none !important;}[wire\:cloak="wire:cloak"],[wire\:cloak=""] {display: none !important;}dialog#livewire-error::backdrop {background-color: rgba(0, 0, 0, .6);}</style>
249    <style>
250            .animate-flash {
251                position: relative;
252                overflow: hidden;
253                isolation: isolate;
254            }
255
256            .animate-flash::before {
257                content: '';
258                position: absolute;
259                inset: 0;
260                z-index: -1;
261                pointer-events: none;
262                background: linear-gradient(110deg, transparent 30%, currentColor 50%, transparent 70%);
263                opacity: 0.5;
264                transform: translateX(-120%);
265                animation: lave-button-shine 3s ease-in-out infinite;
266            }
267
268            @keyframes lave-button-shine {
269                0%, 40% { transform: translateX(-120%); }
270                90%, 100% { transform: translateX(120%); }
271            }
272
273            @media (prefers-reduced-motion: reduce) {
274                .animate-flash::before { animation: none; }
275            }
276        </style>
277    
277<script defer src="https://lave.live/vendor/thumbmarkjs/thumbmark.umd.js"></script>
277
278    
278<script defer src="https://lave.live/js/browser-fingerprint.js"></script>
278
279</head>
280
281
282<body>
283    
284            
284<script>
285            (() => {
286                // Generate on display, not render: prefetched/history pages need a fresh ID per visit.
287                const eventId = crypto.randomUUID();
288                if (typeof fbq === 'function') {
289                    fbq('trackSingle', "950672977777178", 'PageView', {}, { eventID: eventId });
290                }
291            })();
292        </script>
292
293    
294    <!-- Live wire progress bar -->
295    <style>
296        #nprogress .bar {
297            height: 5px;
298            background: rgb(231, 20, 111);
299        }
300    </style>
301
302            <noscript><img height="1" width="1" style="display:none"
303                src="https://www.facebook.com/tr?id=950672977777178&ev=PageView&noscript=1" /></noscript>
304    
305            <!-- Google Tag Manager (noscript) -->
vendor: 76 bytes, lines 305-306
305
306        <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
306GTM-WFZBP7S3
vendor: 107 bytes, lines 306-308
306"
307                height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
308        
308<!-- End Google Tag Manager (noscript) -->
309    
310    
310<script src="https://cdnjs.cloudflare.com/ajax/libs/pulltorefreshjs/0.1.22/index.umd.min.js"></script>
310
311
312    
313    
313<script src="https://cdn.jsdelivr.net/npm/tom-select/dist/js/tom-select.complete.min.js"></script>
313
314
315
316    
316<script src="https://cdn.jsdelivr.net/npm/[email protected]/tsparticles.confetti.bundle.min.js"></script>
316
317
318    
318<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
318
319    
319<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
319
320    
320<script src="https://cdn.jsdelivr.net/npm/driver.js/dist/driver.js.iife.js"></script>
320
321
322
323
324
325    <!-- lave brand color = #E7146F -->
326    <div class="progress" role="progressbar" aria-label="Loading" style="height: 5px; background-color: white;">
327        <div class="indeterminate" style="background-color:  rgb(20, 55, 231);"></div>
328    </div>
329
330    <style>
331        .progress {
332            position: fixed;
333            top: 0;
334            height: 15px;
335            width: 100%;
336            border-radius: 8px;
337            background-clip: padding-box;
338            overflow: hidden;
339            z-index: 100;
340        }
341
342        .progress .indeterminate:before {
343            content: "";
344            position: absolute;
345            background-color: inherit;
346            top: 0;
347            left: 0;
348            bottom: 0;
349            will-change: left, right;
350            -webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
351            animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
352            z-index: 100;
353        }
354
355        .progress .indeterminate:after {
356            content: "";
357            position: absolute;
358            background-color: inherit;
359            top: 0;
360            left: 0;
361            bottom: 0;
362            will-change: left, right;
363            -webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
364            animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
365            -webkit-animation-delay: 1.15s;
366            animation-delay: 1.15s;
367            z-index: 100;
368        }
369
370        .progress {
371            display: none;
372        }
373
374        @-webkit-keyframes indeterminate {
375            0% {
376                left: -35%;
377                right: 100%;
378            }
379
380            60% {
381                left: 100%;
382                right: -90%;
383            }
384
385            100% {
386                left: 100%;
387                right: -90%;
388            }
389        }
390
391        @keyframes indeterminate {
392            0% {
393                left: -35%;
394                right: 100%;
395            }
396
397            60% {
398                left: 100%;
399                right: -90%;
400            }
401
402            100% {
403                left: 100%;
404                right: -90%;
405            }
406        }
407
408        @-webkit-keyframes indeterminate-short {
409            0% {
410                left: -200%;
411                right: 100%;
412            }
413
414            60% {
415                left: 107%;
416                right: -8%;
417            }
418
419            100% {
420                left: 107%;
421                right: -8%;
422            }
423        }
424
425        @keyframes indeterminate-short {
426            0% {
427                left: -200%;
428                right: 100%;
429            }
430
431            60% {
432                left: 107%;
433                right: -8%;
434            }
435
436            100% {
437                left: 107%;
438                right: -8%;
439            }
440        }
441    </style>
442
443
444    
445    <div wire:snapshot="{&quot;data&quot;:{&quot;selectedCountry&quot;:&quot;Ghana&quot;,&quot;selectedCurrency&quot;:&quot;&quot;},&quot;memo&quot;:{&quot;id&quot;:&quot;3f7fR14811A2a30KTRVf&quot;,&quot;name&quot;:&quot;pages::landing.pricing&quot;,&quot;path&quot;:&quot;pricing&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;release&quot;:&quot;a-a-a&quot;,&quot;children&quot;:[],&quot;scripts&quot;:[],&quot;assets&quot;:[],&quot;errors&quot;:[],&quot;locale&quot;:&quot;en&quot;,&quot;islands&quot;:[]},&quot;checksum&quot;:&quot;399b6d8eb22337060219b8d5fdb72cf050da1ad054c60ccf94e3a4df9a994c37&quot;}" wire:effects="[]" wire:id="3f7fR14811A2a30KTRVf" wire:name="pages::landing.pricing">
446    <div
447    class="lave-progress"
448    wire:loading
449    wire:target="selectedCountry,updateVisitorCountry"
450    role="progressbar"
451    aria-label="Loading"
452>
453    <div class="lave-progress-indeterminate"></div>
454</div>
455
456<style>
457    .lave-progress {
458        position: fixed;
459        top: 0;
460        left: 0;
461        z-index: 1000;
462        display: none;
463        width: 100%;
464        height: 5px;
465        overflow: hidden;
466        background: white;
467    }
468
469    .lave-progress-indeterminate {
470        position: absolute;
471        width: 35%;
472        height: 100%;
473        background:  rgb(231, 20, 111);
474        animation: lave-progress 1.5s ease-in-out infinite;
475    }
476
477    @keyframes lave-progress {
478        0% { left: -35%; }
479        100% { left: 100%; }
480    }
481</style>
482    <div class="sticky top-0" style="z-index: 15;">
483
484    <header class="hidden bg-white shadow-lg lg:block">
485        <div class="mx-auto flex max-w-7xl items-center justify-between px-6 py-3">
486            <!-- Logo -->
487            <a href="https://lave.live" wire:navigate class="flex items-center" style="text-decoration: none;">
488                <span class="text-4xl font-semibold" style="font-family: 'Nova Mono'; color: #eb487f;">lave</span>
489            </a>
490
491
492            <!-- Nav Links -->
493            <nav class="hidden space-x-6 rtl:space-x-reverse text-sm font-medium text-gray-700 lg:flex">
494                <!-- Features Dropdown -->
495                <div x-data="{ dropdownOpen: false }" x-on:click.outside="dropdownOpen = false"
496                    x-on:keydown.escape.window="dropdownOpen = false" class="dropdown-group relative">
497                    <button type="button" x-on:click="dropdownOpen = ! dropdownOpen"
498                        x-bind:aria-expanded="dropdownOpen" aria-haspopup="true"
499                        class="dropdown-btn flex items-center hover:text-pink-600">
500                        Features
501                        <svg class="ms-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
502                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
503                        </svg>
504                    </button>
505                    <div x-show="dropdownOpen" x-cloak
506                        class="dropdown-menu absolute start-0 z-20 mt-3 min-w-[300px] rounded-lg bg-white p-6 shadow-xl transition-all duration-300">
507                        <ul class="space-y-2 text-sm text-gray-700">
508                            <li><a wire:navigate href="https://lave.live/features" class="block hover:text-pink-500">All Features</a></li>
509                            <li><a wire:navigate href="https://lave.live/record-sales" class="block hover:text-pink-500">Sales, Expenses &amp; Debts</a></li>
510                            <li><a wire:navigate href="https://lave.live/online-store" class="block hover:text-pink-500">Online Store</a></li>
511                            <li><a wire:navigate href="https://lave.live/whatsapp-orders" class="block hover:text-pink-500">WhatsApp Orders</a></li>
512                            <li><a wire:navigate href="https://lave.live/payments" class="block hover:text-pink-500">Payments</a></li>
513                            <li><a wire:navigate href="https://lave.live/inventory" class="block hover:text-pink-500">Products &amp; Inventory</a></li>
514                            <li><a wire:navigate href="https://lave.live/marketing" class="block hover:text-pink-500">Marketing Tools</a></li>
515                            <li><a wire:navigate href="https://lave.live/plugins" class="block hover:text-pink-500">Plugins &amp; Integrations</a></li>
516                            <li class="border-t border-gray-200 pt-3 text-xs font-bold uppercase tracking-wider text-gray-400">More from Lave</li>
517                            <li><a wire:navigate href="https://lave.live/advertise" class="block hover:text-pink-500">Advertise Your Brand</a></li>
518                            <li><a wire:navigate href="https://lave.live/taskers" class="block hover:text-pink-500">Become a Tasker</a></li>
519                            <li><a wire:navigate href="https://lave.live/refer-and-earn" class="block hover:text-pink-500">Refer &amp; Earn</a></li>
520                            
521                        </ul>
522                    </div>
523                </div>
524
525                <!-- Pricing -->
526                <a wire:navigate href="https://lave.live/pricing" class="hover:text-pink-600">Pricing</a>
527
528                <!-- resources Dropdown -->
529                <div x-data="{ dropdownOpen: false }" x-on:click.outside="dropdownOpen = false"
530                    x-on:keydown.escape.window="dropdownOpen = false" class="dropdown-group relative">
531                    <button type="button" x-on:click="dropdownOpen = ! dropdownOpen"
532                        x-bind:aria-expanded="dropdownOpen" aria-haspopup="true"
533                        class="dropdown-btn flex items-center hover:text-pink-600">
534                        Resources
535                        <svg class="ms-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
536                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
537                        </svg>
538                    </button>
539                    <div x-show="dropdownOpen" x-cloak
540                        class="dropdown-menu absolute start-0 z-20 mt-3 w-48 rounded-lg bg-white p-6 shadow-xl transition-all duration-300">
541                        <ul class="space-y-2 text-sm text-gray-700">
542                            <li><a wire:navigate href="https://lave.live/blog" class="block hover:text-pink-500">Blog</a>
543                            </li>
544                            <li><a wire:navigate href="https://lave.live/help" class="block hover:text-pink-500">Help</a>
545                            </li>
546                            <li><a wire:navigate href="https://lave.live/updates"
547                                    class="block hover:text-pink-500">Updates</a></li>
548                            <li><a wire:navigate href="https://lave.live/faqs" class="block hover:text-pink-500">FAQs</a>
549                            </li>
550                            <li><a href="https://www.youtube.com/@lavedotlive" target="_blank">Videos</a></li>
551                            <li><a wire:navigate href="https://lave.live/contact"
552                                    class="block hover:text-pink-500">Contact</a></li>
553                            <li><a wire:navigate href="https://lave.live/about"
554                                    class="block hover:text-pink-500">About</a></li>
555                        </ul>
556                    </div>
557                </div>
558
559
560                
561            </nav>
562
563            <!-- CTA Button -->
564            <div class="hidden items-center gap-4 lg:flex">
565                <form method="POST" action="https://lave.live/language">
566    <input type="hidden" name="_token" value="ovoz3KLq7cYiRVt0AJRBD4c02Ozq7Z9iI2ZIIRks" autocomplete="off">    <input type="hidden" name="return_to" value="/pricing">
567    <!--[if BLOCK]><![endif]-->        <label class="relative inline-flex min-h-[44px] items-center gap-2 px-3 py-2 text-sm font-bold text-gray-700 transition-colors hover:text-pink-600 focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2">
568            <span aria-hidden="true" uk-icon="icon: world; ratio: 0.8"></span>
569            <span aria-hidden="true">EN</span>
570            <span aria-hidden="true" uk-icon="icon: chevron-down; ratio: 0.8"></span>
571            <span class="sr-only">Language</span>
572            <select name="locale" class="absolute inset-0 h-full w-full cursor-pointer opacity-0"
573                onchange="this.form.requestSubmit()">
574                <!--[if BLOCK]><![endif]-->                    <option value="en" selected>English</option>
575                                    <option value="fr" >Français</option>
576                                    <option value="ar" >العربية</option>
577                <!--[if ENDBLOCK]><![endif]-->            </select>
578        </label>
579    <!--[if ENDBLOCK]><![endif]--></form>
580                <a href="https://lave.live/register" wire:navigate>
581                    <button
582                        class="rounded-lg bg-pink-500 px-5 py-2 text-sm font-bold text-white transition-colors hover:bg-pink-600">
583                        Login / Register
584                    </button>
585                </a>
586            </div>
587        </div>
588    </header>
589</div>
590
591<!-- Mobile Nav -->
592<div x-data="{ mobileMenuOpen: false }" x-effect="document.documentElement.classList.toggle('overflow-hidden', mobileMenuOpen)"
593    x-on:keydown.escape.window="mobileMenuOpen = false" class="sticky top-0 block lg:hidden" style="z-index: 15;">
594    <!-- Offcanvas Sidebar Wrapper (initially hidden) -->
595    <div x-show="mobileMenuOpen" x-cloak x-transition.opacity style="z-index: 20;" class="fixed inset-0 overscroll-contain overflow-y-auto">
596        <!-- Overlay -->
597        <div x-on:click="mobileMenuOpen = false"
598            class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 overscroll-contain overflow-y-auto"></div>
599
600        <!-- Fullscreen Sidebar -->
601
602        <div x-transition:enter="transition-transform duration-300 ease-in-out"
603            x-transition:enter-start="-translate-x-full" x-transition:enter-end="translate-x-0"
604            x-transition:leave="transition-transform duration-300 ease-in-out"
605            x-transition:leave-start="translate-x-0" x-transition:leave-end="-translate-x-full"
606            class="fixed inset-0 overflow-y-auto bg-white p-6 shadow-lg overscroll-contain" style="z-index: 20;">
607
608            <!-- Brand -->
609            <a href="https://lave.live" wire:navigate class="text-4xl font-bold"
610                style="font-family: 'Nova Mono'; color: #eb487f;">
611                lave
612            </a>
613
614            <!-- Close Button -->
615            <button type="button" x-on:click="mobileMenuOpen = false"
616                class="absolute end-4 top-4 text-2xl text-gray-600 hover:text-black">✕</button>
617
618            <!-- Divider -->
619            <div class="my-6 flex items-center px-6">
620                <div class="flex-grow border-t border-gray-300"></div>
621                <span class="mx-4 text-gray-400">⋯</span>
622                <div class="flex-grow border-t border-gray-300"></div>
623            </div>
624
625
626            <!-- Navigation -->
627            <nav class="mt-0 space-y-10">
628                <ul class="uk-nav-default space-y-2 text-lg" uk-nav>
629                    <!-- Features Dropdown -->
630                    <li class="uk-parent">
631                        <a href="#">
632                            <span class="uk-margin-xsmall-right" uk-icon="icon: settings"></span>
633                            Features
634                            <span uk-nav-parent-icon></span>
635                        </a>
636                        <ul class="uk-nav-sub space-y-2 ps-4 text-[17px] leading-[1.75rem]">
637                            <li><a wire:navigate href="https://lave.live/features">All Features</a></li>
638                            <li><a wire:navigate href="https://lave.live/record-sales">Sales, Expenses &amp; Debts</a></li>
639                            <li><a wire:navigate href="https://lave.live/online-store">Online Store</a></li>
640                            <li><a wire:navigate href="https://lave.live/whatsapp-orders">WhatsApp Orders</a></li>
641                            <li><a wire:navigate href="https://lave.live/payments">Payments</a></li>
642                            <li><a wire:navigate href="https://lave.live/inventory">Products &amp; Inventory</a>
643                            </li>
644                            <li><a wire:navigate href="https://lave.live/marketing">Marketing Tools</a></li>
645                            <li><a wire:navigate href="https://lave.live/plugins">Plugins &amp; Integrations</a></li>
646                            <li class="uk-nav-divider"></li>
647                            <li class="uk-nav-header">More from Lave</li>
648                            <li><a wire:navigate href="https://lave.live/advertise">Advertise Your Brand</a>
649                            </li>
650                            <li><a wire:navigate href="https://lave.live/taskers">Become a Tasker</a></li>
651                            <li><a wire:navigate href="https://lave.live/refer-and-earn">Refer &amp; Earn</a></li>
652                            
653                        </ul>
654                    </li>
655
656                    <!-- Pricing -->
657                    <li>
658                        <a wire:navigate href="https://lave.live/pricing">
659                            <span class="uk-margin-xsmall-right" uk-icon="icon: tag"></span>
660                            Pricing
661                        </a>
662                    </li>
663
664                    <li class="uk-parent">
665                        <a href="#">
666                            <span class="uk-margin-xsmall-right" uk-icon="icon: info"></span>
667                            Resources
668                            <span uk-nav-parent-icon></span>
669                        </a>
670                        <ul class="uk-nav-sub space-y-2 ps-4 text-[17px] leading-[1.75rem]">
671                            <li><a wire:navigate href="https://lave.live/blog">Blog</a></li>
672                            <li><a wire:navigate href="https://lave.live/help">Help</a></li>
673                            <li><a wire:navigate href="https://lave.live/updates"
674                                    class="block hover:text-pink-500">Updates</a>
675                            </li>
676                            <li><a wire:navigate href="https://lave.live/faqs">FAQs</a></li>
677                            <li><a href="https://www.youtube.com/@lavedotlive" target="_blank">Videos</a></li>
678                            <li><a wire:navigate href="https://lave.live/contact">Contact</a></li>
679                            <li><a wire:navigate href="https://lave.live/about">About</a></li>
680                        </ul>
681                    </li>
682
683                    
684
685                    <li class="uk-nav-divider"></li>
686
687                    <!-- Login/Register -->
688                    <li>
689                        <a href="https://lave.live/register" wire:navigate>
690                            <button class="uk-button uk-width-1-1 bg-pink-500 text-white">
691                                Login / Register
692                            </button>
693                        </a>
694                    </li>
695                </ul>
696            </nav>
697
698
699        </div>
700
701    </div>
702
703    <!-- Mobile Navbar -->
704
705    <nav class="flex items-center justify-between bg-white px-4 py-3 shadow-md">
706        <!-- Left: Logo -->
707        <div class="p-1 text-pink-500">
708            <a href="https://lave.live" class="text-2xl font-bold"
709                style="font-family: 'Nova Mono'; color: #eb487f;" wire:navigate>
710                lave
711            </a>
712        </div>
713
714        <div class="flex items-center gap-3">
715            <!-- Register CTA (Mobile Only) -->
716            
717
718            <form method="POST" action="https://lave.live/language">
719    <input type="hidden" name="_token" value="ovoz3KLq7cYiRVt0AJRBD4c02Ozq7Z9iI2ZIIRks" autocomplete="off">    <input type="hidden" name="return_to" value="/pricing">
720    <!--[if BLOCK]><![endif]-->        <label class="relative inline-flex min-h-[44px] items-center gap-2 px-3 py-2 text-sm font-bold text-gray-700 transition-colors hover:text-pink-600 focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2">
721            <span aria-hidden="true" uk-icon="icon: world; ratio: 0.8"></span>
722            <span aria-hidden="true">EN</span>
723            <span aria-hidden="true" uk-icon="icon: chevron-down; ratio: 0.8"></span>
724            <span class="sr-only">Language</span>
725            <select name="locale" class="absolute inset-0 h-full w-full cursor-pointer opacity-0"
726                onchange="this.form.requestSubmit()">
727                <!--[if BLOCK]><![endif]-->                    <option value="en" selected>English</option>
728                                    <option value="fr" >Français</option>
729                                    <option value="ar" >العربية</option>
730                <!--[if ENDBLOCK]><![endif]-->            </select>
731        </label>
732    <!--[if ENDBLOCK]><![endif]--></form>
733
734            <!-- Hamburger Icon -->
735            <button type="button" x-on:click="mobileMenuOpen = true" class="text-gray-700 lg:hidden">
736                <svg class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
737                    <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
738                </svg>
739            </button>
740        </div>
741    </nav>
742
743</div>
744
745
746    <div class="bg-white">
747
748        <section class="bg-pink-50 py-20">
749            <div class="uk-container mx-auto max-w-6xl px-6 text-center">
750                <p class="text-6xl font-extrabold leading-tight text-pink-600">Pricing</p>
751                <p class="mx-auto max-w-2xl text-xl text-gray-700">
752                    Simple Affordable Pricing
753                </p>
754            </div>
755        </section>
756
757
758        <section class="uk-container uk-width-1-2@l py-10">
759            <div class="text-center">
760                <p>Select country</p>
761
762                <div style="display:inline;">
763                    <div wire:ignore>
764                        <div uk-form-custom="target: > * > span:first-child" class="inline-flex">
765                            <select aria-label="Custom controls" wire:model.live="selectedCountry"
766                                wire:change="updateVisitorCountry">
767                                <!--[if BLOCK]><![endif]-->                                    <option value="Ghana" selected>
768                                        Ghana (GHS)
769                                    </option>
770                                                                    <option value="Kenya" >
771                                        Kenya (KES)
772                                    </option>
773                                                                    <option value="Nigeria" >
774                                        Nigeria (NGN)
775                                    </option>
776                                                                    <option value="Rwanda" >
777                                        Rwanda (RWF)
778                                    </option>
779                                                                    <option value="South Africa" >
780                                        South Africa (ZAR)
781                                    </option>
782                                                                    <option value="Tanzania" >
783                                        Tanzania (TZS)
784                                    </option>
785                                                                    <option value="Uganda" >
786                                        Uganda (UGX)
787                                    </option>
788                                                                    <option value="Zambia" >
789                                        Zambia (ZMW)
790                                    </option>
791                                <!--[if ENDBLOCK]><![endif]-->                            </select>
792                            <button class="uk-button uk-button-default uk-button-small" type="button" tabindex="-1">
793                                <span></span>
794                                <span uk-icon="icon: chevron-down"></span>
795                            </button>
796                        </div>
797                    </div>
798                </div>
799
800            </div>
801
802            <div> <br>
803                <p class="uk-text-small"> With Lave, pricing depends on your plan.</p>
804                <br>
805
806                <p class="uk-text-small"> <b>Collection fee</b> - This can come in the form of payment links sales or website sales. </p><br>
807                <p class="uk-text-small"> <b>Withdrawal fee</b> - Our payment partners charge you to transfer funds to your bank account / Mobile money wallet
808                </p>
809                <br>
810                
811                <p class="uk-text-small"> <b>Exchange / swap currency</b> - This is the amount charged to swap one currency to another. It is completely free</p>
812
813                <br>
814
815                <p class="uk-text-small">Cap simply means, we wont charge you more than the stated amount</p><br>
816
817
818                <P>Collection =
819                    1.95%
820                    <!--[if BLOCK]><![endif]-->                        +
821                        GHS 1
822                    <!--[if ENDBLOCK]><![endif]-->                    <!--[if BLOCK]><![endif]-->                        capped at GHS
823                        50
824                    <!--[if ENDBLOCK]><![endif]-->                </P>
825
826                <P>Withrawal =
827                    1%
828
829                    <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                    <!--[if BLOCK]><![endif]-->                        capped at GHS
830                        50
831                    <!--[if ENDBLOCK]><![endif]-->                </P>
832
833
834                <br>
835                <p>International collection fee: 
836                    5% for payments made in a currency different from the store currency, plus the fixed collection fee.</p>
837
838                <hr class="uk-divider-icon">
839
840                <p class="mt-3 text-center text-lg font-bold">
841                    Get started for free. Upgrade when you are ready to go live.
842                </p>
843
844                <div class="uk-child-width-1-1 uk-grid-small uk-grid-match uk-padding-small" uk-grid>
845                    
846                    
847
848
849
850                    <div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm sm:p-8">
851                        <h2 class="m-0 text-2xl font-bold tracking-tight text-gray-900">Business plan</h2>
852                        <p class="mt-1 text-sm text-gray-600">All your business tools. Choose how long you want to pay for.</p>
853                        <p class="mt-2 text-sm font-semibold text-gray-700">Minimum 3 months, paid upfront.</p>
854                        <div class="mt-8 grid grid-cols-1 gap-5 md:grid-cols-3" aria-label="Business billing options">
855                            <!--[if BLOCK]><![endif]-->                                <div class="relative flex flex-col rounded-xl border px-5 py-7 border-gray-200 bg-white">
856                                    <h3 class="m-0 text-lg font-semibold text-gray-800">3 months</h3>
857                                    <!--[if BLOCK]><![endif]-->                                        <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                                        <p class="mt-5 text-3xl font-bold tracking-tight text-gray-900">GHS 150</p>
858
859                                        <p class="mt-2 text-sm font-semibold text-pink-600">GHS 50/month</p>
860                                        <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                                    <!--[if ENDBLOCK]><![endif]-->                                </div>
861                                                            <div class="relative flex flex-col rounded-xl border px-5 py-7 border-gray-200 bg-white">
862                                    <h3 class="m-0 text-lg font-semibold text-gray-800">6 months</h3>
863                                    <!--[if BLOCK]><![endif]-->                                        <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->                                        <p class="mt-5 text-3xl font-bold tracking-tight text-gray-900">GHS 270</p>
864
865                                        <p class="mt-2 text-sm font-semibold text-pink-600">GHS 45/month</p>
866                                        <!--[if BLOCK]><![endif]-->                                            <div class="mt-4 flex flex-wrap items-center gap-2">
867                                                <span class="text-sm text-red-600 line-through" aria-label="Cost when paying every 3 months">GHS 300</span>
868                                                <span class="inline-flex rounded bg-green-600 px-3 py-1 text-xs font-bold text-white">Save GHS 30</span>
869                                            </div>
870                                        <!--[if ENDBLOCK]><![endif]-->                                    <!--[if ENDBLOCK]><![endif]-->                                </div>
871                                                            <div class="relative flex flex-col rounded-xl border px-5 py-7 border-pink-300 bg-pink-50">
872                                    <h3 class="m-0 text-lg font-semibold text-gray-800">1 year</h3>
873                                    <!--[if BLOCK]><![endif]-->                                        <!--[if BLOCK]><![endif]-->                                            <span class="absolute -top-3 right-4 rounded-full bg-pink-600 px-3 py-1 text-xs font-bold text-white">Best value</span>
874                                        <!--[if ENDBLOCK]><![endif]-->                                        <p class="mt-5 text-3xl font-bold tracking-tight text-gray-900">GHS 480</p>
875
876                                        <p class="mt-2 text-sm font-semibold text-pink-600">GHS 40/month</p>
877                                        <!--[if BLOCK]><![endif]-->                                            <div class="mt-4 flex flex-wrap items-center gap-2">
878                                                <span class="text-sm text-red-600 line-through" aria-label="Cost when paying every 3 months">GHS 600</span>
879                                                <span class="inline-flex rounded bg-green-600 px-3 py-1 text-xs font-bold text-white">Save GHS 120</span>
880                                            </div>
881                                        <!--[if ENDBLOCK]><![endif]-->                                    <!--[if ENDBLOCK]><![endif]-->                                </div>
882                            <!--[if ENDBLOCK]><![endif]-->                        </div>
883                        <a href="https://lave.live/dashboard/store/subscriptions" wire:navigate
884                            class="mt-6 inline-flex items-center justify-center rounded-lg bg-pink-600 px-6 py-3 font-semibold text-white no-underline hover:bg-pink-700 hover:text-white">Select plan</a>
885                        <ul class="uk-accordion-default mt-8 border-t border-gray-200 pt-6" uk-accordion="active: 0">
886                            <li>
887                                <a class="uk-accordion-title" href="#">
888                                    <span class="text-base font-semibold text-blue-600">View features</span>
889                                    <span wire:ignore uk-accordion-icon></span>
890                                </a>
891                                <div class="uk-accordion-content uk-text-small">
892     <p>✔️ Everything you need to start selling online</p>
893     <p>✔️ Add Variants</p>
894     <p>✔️ Online payments</p>
895     <p>✔️ Custom Payment (create your own payment instructions)</p>
896     <p>✔️ Cash on delivery payment method</p>
897     <p>✔️ Buy on Whatsapp payment method</p>
898     <p>✔️ free <b>mystore.oursite.store</b> subdomain</p>
899     <p>✔️ Analytics / business report</p>
900     <p>✔️ Advanced SEO (coming soon)</p>
901     <p>✔️ Facebook/instagram shop</p>
902     <p>✔️ upload unlimited products</p>
903     <p>✔️ upload Product Videos</p>
904     <p>✔️ Product forms</p>
905     <p>✔️ Product sections</p>
906     <p>✔️ Multi-currency payments (12+ currencies)</p>
907     <p>✔️ Multiple Themes</p>
908     <p>✔️ connect domain name (eg:
909         <b>www.mystore.com</b>)
910         <span class="uk-text-small">(Domain name cost not included)</span>
911     </p>
912     <p>✔️ Add store managers / staff</p>
913     <p>✔️ Bundles</p>
914     <p>✔️ Newsletter</p>
915     <p>✔️ Product reviews </p>
916     <p>✔️ Facebook Pixel </p>
917     <p>✔️ Meta Conversions API </p>
918     <p>✔️ TikTok Pixel </p>
919     <p>✔️ Custom meta tags </p>
920     <p>✔️ Product &amp; Cart countdown Timer</p>
921     <p>✔️ Abandoned cart recovery (automatically send emails to customers who added to cart but didnt complete purchase)</p>
922     <p>✔️ Sales pages (coming soon)</p>
923     <p>✔️ Store Branches / Location (coming soon)</p>
924     <p>✔️ Google Tag Manager</p>
925     
926     <p>✔️ Export orders,transactions to excel, csv etc (coming soon)</p>
927     <p>✔️ Setup your own affiliate / referral program (coming soon) </p>
928 </div>
929                            </li>
930                        </ul>
931                    </div>
932                </div>
933                <br>
934
935            </div>
936        </section>
937    </div>
938
939    <hr>
940<footer class="bg-gray-50 px-6 pb-6 pt-12 sm:px-12">
941    <!-- CTA Section -->
942
943    <div class="mx-auto max-w-7xl">
944        <div class="mb-12 rounded-xl bg-pink-700 px-6 py-12 text-center text-white">
945            <p class="mb-2 text-3xl font-bold sm:text-4xl">Run and grow your business with Lave</p>
946            <p class="mb-6 text-lg">Manage your sales, customers, payments, and online store—all from one connected app.</p>
947            <div class="flex flex-col items-center justify-center gap-4 sm:flex-row">
948                <a href="https://lave.live/register" wire:navigate
949                    class="rounded-lg bg-white px-6 py-3 font-semibold text-pink-700 transition hover:bg-gray-100">
950                    Get Started
951                </a>
952                <a wire:navigate href="https://lave.live/download"
953                    class="rounded-lg border-2 border-white px-6 py-3 font-semibold text-white transition hover:bg-white hover:text-pink-700">
954                    Download App
955                </a>
956            </div>
957        </div>
958    </div>
959
960
961    <!-- Main Footer Grid -->
962    <div class="mx-auto grid max-w-7xl grid-cols-1 gap-8 text-sm sm:grid-cols-2 md:grid-cols-4">
963        <!-- Brand + Social -->
964        <div class="pb-4">
965            <p class="mb-4 text-4xl font-semibold" style="font-family: 'Nova Mono'; color: #eb487f;">lave</p>
966
967            <p class="mb-4">The easiest way to create your online store and grow your business across Africa.</p>
968            <div class="mt-2 flex space-x-4 rtl:space-x-reverse">
969                <a href="https://instagram.com/laveappofficial" target="_blank" aria-label="Instagram"
970                    uk-icon="icon: instagram" class="text-xl text-gray-700 hover:text-purple-700"></a>
971                <a href="https://tiktok.com/@laveappofficial" target="_blank" aria-label="tiktok" uk-icon="icon: tiktok"
972                    class="text-xl text-gray-700 hover:text-purple-700"></a>
973                <a href="https://x.com/laveappofficial" target="_blank" aria-label="Twitter" uk-icon="icon: twitter"
974                    class="text-xl text-gray-700 hover:text-purple-700"></a>
975                <a href="https://facebook.com/laveappofficial" target="_blank" aria-label="Facebook"
976                    uk-icon="icon: facebook" class="text-xl text-gray-700 hover:text-purple-700"></a>
977                <a href="https://www.youtube.com/@lavedotlive" target="_blank" aria-label="YouTube"
978                    uk-icon="icon: youtube" class="text-xl text-gray-700 hover:text-purple-700"></a>
979            </div>
980        </div>
981
982        <!-- Company -->
983        <div>
984            <div class="mb-4 font-semibold text-gray-900">Company</div>
985            <ul class="space-y-2">
986                <li><a wire:navigate href="https://lave.live/about" class="hover:underline">About Us</a></li>
987                <li><a wire:navigate href="https://lave.live/pricing" class="hover:underline">Pricing</a></li>
988                <li><a wire:navigate href="https://lave.live/contact" class="hover:underline">Contact</a></li>
989            </ul>
990        </div>
991
992        <!-- Resources -->
993
994        <div>
995            <div class="mb-4 font-semibold text-gray-900">Resources</div>
996            <ul class="space-y-2">
997
998                <li><a wire:navigate href="https://lave.live/blog" class="hover:underline">Blogs</a></li>
999                <li><a wire:navigate href="https://lave.live/faqs" class="hover:underline">FAQs</a></li>
1000                <li><a href="https://www.youtube.com/@lavedotlive" target="_blank" class="hover:underline">Videos</a>
1001                </li>
1002            </ul>
1003        </div>
1004
1005        <!-- Legal -->
1006        <div>
1007            <div class="mb-4 font-semibold text-gray-900">Legal</div>
1008            <ul class="space-y-2">
1009                <li><a wire:navigate href="https://lave.live/terms" class="hover:underline">Terms &amp; Conditions</a></li>
1010                <li><a wire:navigate href="https://lave.live/privacy" class="hover:underline">Privacy Policy</a></li>
1011            </ul>
1012        </div>
1013    </div>
1014
1015    <div class="mx-auto mt-8 max-w-7xl">
1016        <hr class="m-0 border-gray-200">
1017        <p class="mb-0 pt-6 text-center text-xs text-gray-500">© 2026 Lave. All rights reserved.</p>
1018    </div>
1019</footer>
1020
1021</div>
1022
1023            <div x-persist="website-assistant">
1024            <div>
1025                <div wire:key="lw-3437951620-0-3" wire:snapshot="{&quot;data&quot;:{&quot;question&quot;:&quot;&quot;,&quot;visitorToken&quot;:&quot;8ukj9J23W9rt7e03QOSbkNdhO6tz7FUYgA8gPpD3&quot;,&quot;conversationId&quot;:null,&quot;failure&quot;:&quot;&quot;},&quot;memo&quot;:{&quot;id&quot;:&quot;kA7BvTwN3xR5W2pE5LZo&quot;,&quot;name&quot;:&quot;website-assistant&quot;,&quot;path&quot;:&quot;pricing&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;release&quot;:&quot;a-a-a&quot;,&quot;children&quot;:[],&quot;scripts&quot;:[&quot;34428343-0&quot;],&quot;assets&quot;:[],&quot;errors&quot;:[],&quot;locale&quot;:&quot;en&quot;,&quot;islands&quot;:[]},&quot;checksum&quot;:&quot;f710d2809661a19deae4e556b9624868b4043a2c1cac0c6ed4a1ed7a5188f9e5&quot;}" wire:effects="{&quot;scripts&quot;:{&quot;34428343-0&quot;:&quot;&lt;script&gt;\n    Alpine.data(&#039;websiteAssistantChat&#039;, () =&gt; ({\n        chatModalOpen: false,\n        sending: false,\n        stopped: false,\n        cancelReply: null,\n        unsubscribe: null,\n        messageObserver: null,\n        destroyed: false,\n        init() {\n            let visible = false;\n            this.messageObserver = new ResizeObserver(([entry]) =&gt; {\n                const nowVisible = entry.contentRect.height &gt; 0 &amp;&amp; entry.contentRect.width &gt; 0;\n                if (nowVisible &amp;&amp; !visible) this.scrollToLatest();\n                visible = nowVisible;\n            });\n            this.messageObserver.observe(this.$refs.messages);\n            this.unsubscribe = this.$wire.intercept(&#039;send&#039;, ({ action, onFinish }) =&gt; {\n                this.stopped = false;\n                this.sending = true;\n                this.scrollToLatest();\n                this.cancelReply = () =&gt; action.cancel();\n                onFinish(() =&gt; {\n                    this.sending = false;\n                    this.cancelReply = null;\n                });\n            });\n        },\n        scrollToQuestion(pending = false) {\n            this.$nextTick(() =&gt; {\n                requestAnimationFrame(() =&gt; {\n                    if (this.destroyed) return;\n                    const container = this.$refs.messages;\n                    const question = [...container.querySelectorAll(&#039;[data-message-role=\&quot;user\&quot;]&#039;)].at(-1);\n                    if (!question) return;\n                    const padding = parseFloat(getComputedStyle(container).paddingTop) || 0;\n                    const top = container.scrollTop + question.getBoundingClientRect().top - container.getBoundingClientRect().top - padding;\n                    container.scrollTo({ top: Math.max(0, top), behavior: &#039;instant&#039; });\n                });\n            });\n        },\n        scrollToLatest() {\n            this.$nextTick(() =&gt; {\n                requestAnimationFrame(() =&gt; {\n                    if (!this.destroyed) this.$refs.messages.scrollTo({ top: this.$refs.messages.scrollHeight, behavior: &#039;instant&#039; });\n                });\n            });\n        },\n        stopReply() {\n            if (!this.sending || !this.cancelReply) return;\n            this.stopped = true;\n            this.cancelReply();\n        },\n        destroy() {\n            this.destroyed = true;\n            this.cancelReply?.();\n            this.unsubscribe?.();\n            this.messageObserver?.disconnect();\n        },\n    }));\n&lt;\/script&gt;\n    &quot;}}" wire:id="kA7BvTwN3xR5W2pE5LZo" wire:name="website-assistant" x-data="websiteAssistantChat" x-on:assistant-support-opened="chatModalOpen = false" x-on:keydown.escape.window="chatModalOpen = false" x-on:website-assistant-answered.window="scrollToQuestion()">
1026    <button type="button" class="fixed bottom-[88px] end-4 z-10 flex h-14 w-14 cursor-pointer items-center justify-center overflow-hidden rounded-full border-0 bg-pink-500 p-0 text-white shadow-lg hover:bg-pink-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink-700" x-ref="launcher" aria-label="Need help? Open Lave AI Assistant" x-bind:aria-expanded="chatModalOpen" x-on:click="chatModalOpen = !chatModalOpen; if(chatModalOpen) $nextTick(() => $refs.question.focus())"><span uk-icon="icon: comment; ratio: 1.5" aria-hidden="true"></span></button>
1027    <section class="fixed bottom-[156px] end-4 z-40 flex h-[min(590px,calc(100dvh-165px))] min-h-[280px] w-96 max-w-[calc(100vw-24px)] flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white text-sm text-gray-700 shadow-xl max-md:inset-0 max-md:z-[1000] max-md:h-dvh max-md:min-h-0 max-md:w-full max-md:max-w-none max-md:rounded-none max-md:border-0 [&_button:disabled]:cursor-wait [&_button:disabled]:opacity-50 [&_button:focus-visible]:outline [&_button:focus-visible]:outline-2 [&_button:focus-visible]:outline-offset-2 [&_button:focus-visible]:outline-pink-700" role="dialog" aria-label="Lave AI Assistant" x-show="chatModalOpen" x-cloak>
1028        <header class="flex shrink-0 items-center justify-between gap-2 border-b border-gray-200 bg-white px-4 py-3 max-md:pt-[max(0.75rem,env(safe-area-inset-top))]">
1029            <div class="flex min-w-0 items-center gap-2"><span class="shrink-0 text-xl text-pink-700" aria-hidden="true">✦</span><div class="min-w-0"><strong class="block text-sm text-gray-800">Lave AI Assistant</strong><small class="block text-xs text-gray-500">A little help getting started</small></div></div>
1030            <button type="button" class="shrink-0 rounded px-2 text-2xl text-gray-600 hover:bg-gray-100" aria-label="Close assistant" x-on:click="chatModalOpen = false;
1030 $refs.launcher.focus()">×</button>
1031        </header>
1032        <div class="flex shrink-0 items-center justify-between gap-3 border-b border-gray-100 bg-gray-100 px-4 py-2.5">
1033            <button type="button" class="inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-gray-600 bg-gray-600 px-3 py-2 text-xs font-medium text-white transition hover:border-gray-700 hover:bg-gray-700" wire:click="startAgain" x-on:click="stopped = false" x-bind:disabled="sending" wire:loading.attr="disabled" wire:target="send,startAgain"><span aria-hidden="true" class="text-base leading-none">+</span> New chat</button>
1034            <button type="button"
1035    class="inline-flex shrink-0 items-center gap-1 rounded text-xs font-bold !no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 px-1 py-2 !text-gray-800 hover:text-gray-950 focus-visible:outline-gray-600"
1036    x-on:click="if (typeof window.openZohoChat === 'function') { window.openZohoChat($event); $dispatch('assistant-support-opened'); }"
1037>
1038    <span>Talk to a human</span>
1039</button>
1040        </div>
1041        <div class="min-h-0 min-w-0 flex-1 overflow-auto overscroll-contain px-5 py-4" x-ref="messages" wire:navigate:scroll aria-live="polite" role="log">
1042            <!--[if BLOCK]><![endif]-->                <div class="flex flex-col gap-2.5">
1043                    <h2 class="my-3 text-xl text-gray-700">What do you need help with?</h2>
1044                    <p class="leading-relaxed text-gray-500">Type your question below.</p>
1045                </div>
1046            <!--[if ENDBLOCK]><![endif]-->            <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->            <p x-show="stopped" x-cloak class="text-xs text-gray-500" role="status">Reply stopped.</p>
1047            <p x-show="sending" x-cloak class="inline-flex items-center gap-2 text-xs text-pink-700" role="status"><span class="shrink-0" uk-spinner="ratio: 0.45" aria-hidden="true"></span><span>Checking that for you…</span></p>
1048        </div>
1049        <form wire:submit="send" class="shrink-0 border-t border-gray-100 bg-white p-3 max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))]">
1050            <label class="sr-only" for="website-ai-question">Your question</label>
1051            <div class="flex items-end gap-[10px] border border-[#e5dbe9] bg-gray-50 p-[10px] focus-within:border-[#cc79a7]"><textarea class="min-h-0 min-w-0 w-full resize-none border-0 bg-transparent p-[3px] text-sm leading-[1.6] text-[#423448] outline-none focus:ring-0 max-md:text-base" id="website-ai-question" x-ref="question" wire:model="question" x-bind:readonly="sending" rows="2" maxlength="1000" placeholder="Ask about Lave…" required></textarea><button x-show="!sending" type="submit" class="min-h-[40px] shrink-0 rounded-[10px] border-0 bg-[#bd3c80] px-[15px] py-[10px] text-xs text-white hover:bg-pink-700" wire:loading.attr="disabled" wire:target="send,startAgain"><span wire:loading wire:target="send" uk-spinner="ratio: 0.4"></span>Send</button><button type="button" x-show="sending" x-cloak x-on:click="stopReply" class="min-h-[40px] shrink-0 rounded-[10px] border-0 bg-[#bd3c80] px-[15px] py-[10px] text-xs text-white hover:bg-pink-700" aria-label="Stop generating reply">Stop</button></div>
1052            <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->            <!--[if BLOCK]><![endif]--><!--[if ENDBLOCK]><![endif]-->            <small class="mt-2 block text-[10px] leading-normal text-gray-500">AI answers can be mistaken. Chats are saved for continuity.</small>
1053        </form>
1054    </section>
1055</div>
1056
1057
1058
1059                </div>
1060        </div>
1061        
1062
1063    
1063<script>
1064        (() => {
1065            if (window.laveCanPullToRefresh) return;
1066
1067            let gestureBlocked = false;
1068
1069            function isVisible(element) {
1070                if (element.closest('[hidden], [inert], [aria-hidden="true"], [x-cloak]')) return false;
1071
1072                const style = getComputedStyle(element);
1073                const rect = element.getBoundingClientRect();
1074
1075                return style.display !== 'none' && style.visibility !== 'hidden'
1076                    && rect.width > 0 && rect.height > 0
1077                    && rect.bottom > 0 && rect.right > 0
1078                    && rect.top < innerHeight && rect.left < innerWidth;
1079            }
1080
1081            function hasOpenOverlay() {
1082                if (typeof window.isPageScrollLocked === 'function' && window.isPageScrollLocked()) return true;
1083
1084                return Array.from(document.querySelectorAll(
1085                    '[data-pull-refresh-overlay], [role="dialog"], [aria-modal="true"], dialog[open], '
1086                    + '.uk-modal.uk-open, .uk-offcanvas.uk-open, .fixed.inset-0'
1087                )).some(isVisible);
1088            }
1089
1090            function startsInScrollArea(target) {
1091                for (let element = target; element instanceof Element && element !== document.body
1092                    && element !== document.documentElement; element = element.parentElement) {
1093                    const style = getComputedStyle(element);
1094
1095                    // Contained areas keep their gestures even at the top or with short content.
1096                    if (['contain', 'none'].includes(style.overscrollBehaviorY)
1097                        || (/auto|scroll/.test(style.overflowY) && element.scrollHeight > element.clientHeight)) {
1098                        return true;
1099                    }
1100                }
1101
1102                return false;
1103            }
1104
1105            document.addEventListener('touchstart', event => {
1106                gestureBlocked = event.touches.length !== 1 || hasOpenOverlay()
1107                    || startsInScrollArea(event.target);
1108            }, { capture: true, passive: true });
1109
1110            // Keep the decision through touchend and any delayed refresh callback.
1111            document.addEventListener('touchcancel', () => { gestureBlocked = true; }, { passive: true });
1112            document.addEventListener('livewire:navigating', () => { gestureBlocked = true; });
1113
1114            window.laveCanPullToRefresh = () => !gestureBlocked && !window.scrollY && !hasOpenOverlay();
1115        })();
1116
1117        function initPullToRefresh() {
1118            if (!window.PullToRefresh || typeof window.PullToRefresh.init !== 'function') {
1119                return;
1120            }
1121
1122            if (window.__lavePullToRefresh) {
1123                window.__lavePullToRefresh.destroy();
1124                window.__lavePullToRefresh = null;
1125            }
1126
1127            const pullToRefreshDisabledPaths = [
1128                '/dashboard/store/products/bulk-upload',
1129                '/dashboard/store/products/bulk-upload/csv',
1130                '/dashboard/store/products/bulk-edit',
1131            ];
1132            const currentPath = window.location.pathname.replace(/\/$/, '');
1133
1134            if (currentPath.includes('/sort') || pullToRefreshDisabledPaths.includes(currentPath)) {
1135                return;
1136            }
1137
1138            window.__lavePullToRefresh = window.PullToRefresh.init({
1139                mainElement: 'body',
1140                shouldPullToRefresh: function() {
1141
1142                    return window.laveCanPullToRefresh();
1143                },
1144                onRefresh: function() {
1145                    if (window.laveCanPullToRefresh()) window.location.reload(true);
1146                }
1147            });
1148        }
1149
1150        if (document.readyState === 'loading') {
1151            document.addEventListener('DOMContentLoaded', initPullToRefresh, {
1152                once: true
1153            });
1154        } else {
1155            initPullToRefresh();
1156        }
1157
1158        document.addEventListener('livewire:navigated', initPullToRefresh);
1159    </script>
1159
1160
1161
1162
1163    
1163<script>
1164        function goback() {
1165            if (window.history.length > 1) {
1166                window.history.back();
1167            } else {
1168                window.location.href = "/home/admin";
1169            }
1170        }
1171    </script>
1171
1172
1173
1174
1175    <!-- ALL Faq script -->
1176    
1176<script>
1177        function bindFaqToggleEvents() {
1178            const toggles = document.querySelectorAll('.faq-toggle');
1179
1180            toggles.forEach(toggle => {
1181                if (!toggle.dataset.bound) {
1182                    toggle.addEventListener('click', () => {
1183                        const item = toggle.closest('.faq-item');
1184                        const content = item.querySelector('.faq-content');
1185                        const icon = toggle.querySelector('.plus');
1186
1187                        // Collapse other items
1188                        document.querySelectorAll('.faq-content').forEach(c => {
1189                            if (c !== content) {
1190                                c.style.maxHeight = null;
1191                                c.parentElement.classList.remove('active');
1192                                c.previousElementSibling.querySelector('.plus').textContent = '+';
1193                            }
1194                        });
1195
1196                        // Toggle current
1197                        if (content.style.maxHeight) {
1198                            content.style.maxHeight = null;
1199                            icon.textContent = '+';
1200                        } else {
1201                            content.style.maxHeight = content.scrollHeight + "px";
1202                            icon.textContent = '−';
1203                        }
1204                    });
1205
1206                    toggle.dataset.bound = "true"; // Prevent duplicate bindings
1207                }
1208            });
1209        }
1210
1211        // Initial bind
1212        document.addEventListener('DOMContentLoaded', bindFaqToggleEvents);
1213        document.addEventListener('livewire:navigated', bindFaqToggleEvents);
1214    </script>
1214
1215
1216
1217
1218    
1218<script>
1219        if ('serviceWorker' in navigator) {
1220            let refreshing = false;
1221            let lastUpdateCheckAt = 0;
1222            const minUpdateIntervalMs = 30 * 60 * 1000; // 30 minutes
1223
1224            const maybeUpdate = (reg, reason) => {
1225                const now = Date.now();
1226
1227                if (document.hidden) {
1228                    return;
1229                }
1230
1231                if (now - lastUpdateCheckAt < minUpdateIntervalMs) {
1232                    return;
1233                }
1234
1235                lastUpdateCheckAt = now;
1236
1237                if (reason) {
1238                    console.log(`[SW] ${reason} - checking for updates...`);
1239                }
1240
1241                reg.update();
1242            };
1243
1244            // Register service worker
1245            navigator.serviceWorker.register('/serviceworker.js', {
1246                    updateViaCache: 'none' // Always check for updates, never use cached service worker
1247                })
1248                .then(reg => {
1249                    console.log('[SW] Service worker registered:', reg.scope);
1250
1251                    // IMMEDIATE: Check for updates on every page load
1252                    maybeUpdate(reg, 'App load');
1253
1254                    // Check for updates when page becomes visible (app opened)
1255                    document.addEventListener('visibilitychange', () => {
1256                        if (!document.hidden) {
1257                            if (Date.now() - lastUpdateCheckAt < minUpdateIntervalMs) {
1258                                return;
1259                            }
1260
1261                            lastUpdateCheckAt = Date.now();
1262                            console.log('[SW] App visible - checking for updates...');
1263                            reg.update();
1264                        }
1265                    });
1266
1267                    // Check for updates when app comes to foreground (PWA)
1268                    window.addEventListener('focus', () => {
1269                        if (Date.now() - lastUpdateCheckAt < minUpdateIntervalMs) {
1270                            return;
1271                        }
1272
1273                        lastUpdateCheckAt = Date.now();
1274                        console.log('[SW] App focused - checking for updates...');
1275                        reg.update();
1276                    });
1277
1278                    // Check for updates every 24 hours (background safety check)
1279                    // Most updates are caught by: app load, visibility change, and focus events above
1280                    setInterval(() => {
1281                        maybeUpdate(reg, 'Background check');
1282                    }, 24 * 60 * 60 * 1000); // 24 hours
1283
1284                    // Listen for new service worker found
1285                    reg.addEventListener('updatefound', () => {
1286                        const newWorker = reg.installing;
1287                        if (newWorker) {
1288                            console.log('[SW] New service worker version detected!');
1289                            newWorker.addEventListener('statechange', () => {
1290                                if (newWorker.state === 'installed') {
1291                                    if (navigator.serviceWorker.controller) {
1292                                        // New service worker is ready
1293                                        console.log('[SW] New version installed, activating...');
1294                                        // Service worker will auto-activate due to skipWaiting()
1295                                    } else {
1296                                        // First time installation
1297                                        console.log('[SW] Service worker installed for first time');
1298                                    }
1299                                }
1300                            });
1301                        }
1302                    });
1303                })
1304                .catch(err => console.error('[SW] Registration failed:', err));
1305
1306            // Also check for updates if service worker is already registered (for returning users)
1307            window.addEventListener('load', () => {
1308                if (Date.now() - lastUpdateCheckAt < minUpdateIntervalMs) {
1309                    return;
1310                }
1311
1312                lastUpdateCheckAt = Date.now();
1313                navigator.serviceWorker.getRegistration().then(reg => {
1314                    if (reg) {
1315                        console.log('[SW] Checking for updates on app load...');
1316                        reg.update();
1317                    }
1318                });
1319            });
1320
1321            // Handle controller change (new service worker activated)
1322            navigator.serviceWorker.addEventListener('controllerchange', () => {
1323                if (!refreshing) {
1324                    refreshing = true;
1325                    console.log('[SW] New version activated - reloading page...');
1326                    window.location.reload();
1327                }
1328            });
1329        }
1330    </script>
1330
1331
1332
1333
1334    <style>
1335    body {
1336        /* Keep horizontal safe-area support without creating a visible top gap */
1337        padding-top: 0;
1338        padding-bottom: 0;
1339        padding-left: env(safe-area-inset-left);
1340        padding-right: env(safe-area-inset-right);
1341    }
1342
1343
1344    .safe-area {
1345        padding-bottom: max(15px, env(safe-area-inset-bottom));
1346        z-index: 10;
1347    }
1348</style>
1349
1350
1351    <div>
1352
1353    
1354    <div id="contactPickerModal" class="fixed inset-0 hidden items-center justify-center bg-gray-900 bg-opacity-50 px-4"
1355        style="z-index: 60;">
1356        <div class="relative w-full max-w-sm overflow-hidden rounded-2xl bg-white shadow-2xl">
1357            <!-- Header with X button -->
1358            <div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
1359                <div id="contactNameHeader" class="truncate text-lg font-semibold text-gray-900">
1360                    Select Phone Number
1361                </div>
1362                <button id="closeModalPhone" type="button"
1363                    class="flex h-8 w-8 items-center justify-center rounded-full text-gray-400 transition hover:bg-gray-100 hover:text-gray-600">
1364                    <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1365                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
1366                            d="M6 18L18 6M6 6l12 12" />
1367                    </svg>
1368                </button>
1369            </div>
1370
1371            <!-- Contact list with fixed max height -->
1372            <div id="contactList" class="max-h-96 divide-y divide-gray-100 overflow-y-auto"></div>
1373        </div>
1374    </div>
1375
1376    
1377    
1377<script data-navigate-once>
1378        /**
1379         * Initialize custom headers for all HTTP requests
1380         * This allows your Laravel server to detect if the request is from the Capacitor app
1381         */
1382        async function initAppHeaders() {
1383            if (!window.Capacitor) {
1384                console.log('? Capacitor not detected - running in web browser');
1385                return;
1386            }
1387
1388            console.log('🔵 Capacitor detected! Initializing app headers...');
1389
1390            try {
1391                const {
1392                    Device,
1393                    App
1394                } = window.Capacitor.Plugins;
1395
1396                // Get app and device info
1397                const appInfo = await App.getInfo();
1398                const deviceInfo = await Device.getInfo();
1399
1400                // Store app info globally
1401                window._appHeaders = {
1402                    'X-Capacitor-App': 'true',
1403                    'X-App-Version': appInfo.version,
1404                    'X-App-Build': appInfo.build,
1405                    'X-App-Platform': deviceInfo.platform, // 'ios' or 'android'
1406                    'X-Device-Model': deviceInfo.model,
1407                };
1408
1409                console.log('? App headers initialized:', window._appHeaders);
1410
1411                // Intercept fetch requests to add headers
1412                const originalFetch = window.fetch;
1413                window.fetch = function(...args) {
1414                    const [url, config = {}] = args;
1415
1416                    // Only add headers to requests to your domain
1417                    if (typeof url === 'string' && (url.includes('lave.live') || url.startsWith('/'))) {
1418                        config.headers = {
1419                            ...config.headers,
1420                            ...window._appHeaders
1421                        };
1422                    }
1423
1424                    return originalFetch(url, config);
1425                };
1426
1427                // Intercept XMLHttpRequest (for HTMX and other libraries)
1428                const originalOpen = XMLHttpRequest.prototype.open;
1429                const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
1430
1431                XMLHttpRequest.prototype.open = function(...args) {
1432                    this._url = args[1];
1433                    return originalOpen.apply(this, args);
1434                };
1435
1436                XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
1437                    // Store original headers
1438                    if (!this._customHeaders) {
1439                        this._customHeaders = {};
1440                    }
1441                    this._customHeaders[header] = value;
1442                    return originalSetRequestHeader.apply(this, arguments);
1443                };
1444
1445                // Add app headers before send
1446                const originalSend = XMLHttpRequest.prototype.send;
1447                XMLHttpRequest.prototype.send = function(...args) {
1448                    // Only add headers to requests to your domain
1449                    if (this._url && (typeof this._url === 'string' && (this._url.includes('lave.live') || this._url
1450                            .startsWith('/')))) {
1451                        for (const [header, value] of Object.entries(window._appHeaders)) {
1452                            originalSetRequestHeader.call(this, header, value);
1453                        }
1454                    }
1455                    return originalSend.apply(this, args);
1456                };
1457
1458                console.log('? Request interceptors initialized - All requests will include app headers');
1459
1460            } catch (error) {
1461                console.error('? Failed to initialize app headers:', error);
1462                alert('? Error initializing app headers: ' + error.message);
1463            }
1464        }
1465
1466        // Initialize as early as possible
1467        if (document.readyState === 'loading') {
1468            document.addEventListener('DOMContentLoaded', initAppHeaders);
1469        } else {
1470            initAppHeaders();
1471        }
1472    </script>
1472
1473
1474
1475
1476
1477    
1478    
1478<script data-navigate-once>
1479        /**
1480         * Lock text zoom to 1.0 (100%) to prevent system font size from affecting the app
1481         * This ensures your app always uses your default font size
1482         */
1483        async function lockTextZoom() {
1484            // Wait for Capacitor to be available
1485            if (!window.Capacitor) {
1486                setTimeout(lockTextZoom, 100);
1487                return;
1488            }
1489
1490            // Wait for TextZoom plugin to be available
1491            if (!window.Capacitor.Plugins || !window.Capacitor.Plugins.TextZoom) {
1492                setTimeout(lockTextZoom, 100);
1493                return;
1494            }
1495
1496            try {
1497                // Set text zoom to 1.0 (100% - normal size)
1498                await window.Capacitor.Plugins.TextZoom.set({
1499                    value: 1.0
1500                });
1501                console.log('? Text zoom locked to 1.0 (100%)');
1502            } catch (error) {
1503                console.log('Could not set text zoom:', error);
1504            }
1505        }
1506
1507        // Lock text zoom when DOM is ready
1508        if (document.readyState === 'loading') {
1509            document.addEventListener('DOMContentLoaded', lockTextZoom);
1510        } else {
1511            // DOM is already ready
1512            lockTextZoom();
1513        }
1514
1515        // Also lock after window loads (as a fallback)
1516        window.addEventListener('load', lockTextZoom);
1517
1518        // Re-lock text zoom after HTMX content swaps
1519        document.body.addEventListener("htmx:afterSwap", lockTextZoom);
1520    </script>
1520
1521
1522    
1522<script data-navigate-once>
1523        async function getAppInfo() {
1524            if (!window.Capacitor || !window.Capacitor.Plugins?.App) {
1525                //  alert("Capacitor App plugin not available.");
1526                return;
1527            }
1528
1529            const App = window.Capacitor.Plugins.App;
1530
1531            try {
1532                const info = await App.getInfo();
1533                const state = await App.getState();
1534
1535                // alert(
1536                //     "App Info:\n" +
1537                //     "Name: " + info.name + "\n" +
1538                //     "ID: " + info.id + "\n" +
1539                //     "Version: " + info.version + "\n" +
1540                //     "Build: " + info.build + "\n" +
1541                //     "State: " + state.isActive
1542                // );
1543
1544                console.log("Full App Info:", info);
1545                console.log("App State:", state);
1546
1547            } catch (err) {
1548                console.error("Error getting app info:", err);
1549            }
1550        }
1551
1552        document.addEventListener("deviceready", getAppInfo);
1553        document.addEventListener("DOMContentLoaded", getAppInfo);
1554        document.addEventListener('livewire:navigated', getAppInfo);
1555    </script>
1555
1556
1557    
1557<script data-navigate-once>
1558        // Capture app launch time for grace windows
1559        const appLaunchTime = Date.now();
1560
1561        function initCapacitorCore() {
1562            if (!window.Capacitor) return;
1563
1564            // Back Button Handler - Simple version using Capacitor's built-in canGoBack
1565            if (!window._backHandlerSet) {
1566                window._backHandlerSet = true;
1567                const {
1568                    App
1569                } = window.Capacitor.Plugins;
1570
1571                // Handle back button
1572                App.addListener('backButton', async (event) => {
1573                    console.log('📱 Back button pressed. canGoBack:', event.canGoBack);
1574
1575                    if (event.canGoBack) {
1576                        // Navigate back in web history
1577                        window.history.back();
1578                        console.log('⬅️ Navigating back in history');
1579                    } else {
1580                        // No history, minimize the app (move to background)
1581                        console.log('📱 No more history, minimizing app...');
1582                        try {
1583                            await App.minimizeApp();
1584                            console.log('? App minimized');
1585                        } catch (error) {
1586                            console.error('? Failed to minimize app:', error);
1587                        }
1588                    }
1589                });
1590
1591                console.log('? Back button handler initialized');
1592            }
1593
1594            // Share Override - intercepts navigator.share() calls to use Capacitor native share
1595            if (!navigator._shareOverrideSet) {
1596                navigator._shareOverrideSet = true;
1597                navigator.share = async (data) => {
1598                    return await window.Capacitor.Plugins.Share.share(data);
1599                };
1600            }
1601
1602            // Network Monitoring - Simple and reliable
1603            if (!window._networkMonitorSet) {
1604                window._networkMonitorSet = true;
1605
1606                async function saveUrlAndGoOffline() {
1607                    // Grace period after launch to avoid false offline on cold start
1608                    const sinceLaunch = Date.now() - appLaunchTime;
1609                    if (sinceLaunch < 4000) {
1610                        console.warn('Offline redirect suppressed (grace window)', sinceLaunch);
1611                        return;
1612                    }
1613
1614                    // If browser reports online, skip redirect
1615                    if (navigator.onLine) {
1616                        console.warn('Offline redirect skipped because navigator.onLine');
1617                        return;
1618                    }
1619
1620                    const currentUrl = window.location.href;
1621
1622                    // Save URL using Capacitor Preferences
1623                    try {
1624                        if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Preferences) {
1625                            await window.Capacitor.Plugins.Preferences.set({
1626                                key: 'returnUrl',
1627                                value: currentUrl
1628                            });
1629                        } else {
1630                            localStorage.setItem('capacitor_returnUrl', currentUrl);
1631                        }
1632                    } catch (e) {
1633                        try {
1634                            localStorage.setItem('capacitor_returnUrl', currentUrl);
1635                        } catch (e2) {}
1636                    }
1637
1638                    // Navigate to offline page
1639                    window.location.href = 'offline.html';
1640                }
1641
1642                // Livewire requests use fetch instead of HTMX's request events.
1643                let livewireNetworkMonitorSet = false;
1644                function monitorLivewireRequests() {
1645                    if (livewireNetworkMonitorSet || !window.Livewire?.interceptRequest) return;
1646                    livewireNetworkMonitorSet = true;
1647                    window.Livewire.interceptRequest(({ onFailure }) => {
1648                        onFailure(() => saveUrlAndGoOffline());
1649                    });
1650                }
1651                document.addEventListener('livewire:initialized', monitorLivewireRequests);
1652                document.addEventListener('livewire:navigated', monitorLivewireRequests);
1653                monitorLivewireRequests();
1654
1655                // HTMX: Request failed to send (offline)
1656                document.body.addEventListener('htmx:sendError', function() {
1657                    console.log('? HTMX sendError - going offline');
1658                    saveUrlAndGoOffline();
1659                });
1660
1661                // HTMX: Request sent but got network error (status 0)
1662                document.body.addEventListener('htmx:responseError', function(event) {
1663                    if (event.detail.xhr.status === 0) {
1664                        console.log('? HTMX responseError status 0 - going offline');
1665                        saveUrlAndGoOffline();
1666                    }
1667                });
1668
1669                console.log('? Network monitoring ready');
1670            }
1671        }
1672        document.addEventListener("DOMContentLoaded", initCapacitorCore);
1673        document.addEventListener('livewire:navigated', initCapacitorCore);
1674        document.body.addEventListener("htmx:afterSwap", initCapacitorCore);
1675    </script>
1675
1676
1677    
1678    <!-- Add this to your initOneSignal() function -->
1679
1680    
1680<script data-navigate-once>
1681        // Avoid double init on htmx reloads
1682        let oneSignalInitStarted = false;
1683        let oneSignalReady = false;
1684
1685        // Store pending deep links to apply after app is ready
1686        const DEEPLINK_KEY = 'pending_deeplink';
1687
1688        function handleOneSignalOpen(notification) {
1689            if (!notification) {
1690                console.log("OneSignal: click with no payload");
1691                return;
1692            }
1693
1694            const additionalData = notification.additionalData || {};
1695            const target =
1696                additionalData.targetUrl ||
1697                additionalData.url ||
1698                notification.launchURL;
1699
1700            console.log("OneSignal payload", {
1701                additionalData,
1702                launchURL: notification.launchURL,
1703                resolvedTarget: target
1704            });
1705
1706            if (!target) {
1707                console.log("OneSignal: no target URL found");
1708                return;
1709            }
1710
1711            // Stash target to apply after app is fully ready
1712            try {
1713                localStorage.setItem(DEEPLINK_KEY, target);
1714                console.log("Stored pending deep link", target);
1715            } catch (e) {
1716                console.warn("Could not store deep link", e);
1717            }
1718
1719            // Try to hide splash immediately to avoid freeze
1720            if (window.Capacitor?.Plugins?.SplashScreen) {
1721                window.Capacitor.Plugins.SplashScreen.hide().catch(() => {});
1722            }
1723        }
1724
1725        async function initOneSignal() {
1726            if (!window.plugins?.OneSignal) return;
1727            if (oneSignalInitStarted) {
1728                // Navigation can change the signed-in user or follow a failed save.
1729                if (oneSignalReady) await syncOneSignalSubscription();
1730                return;
1731            }
1732            oneSignalInitStarted = true;
1733
1734            try {
1735                // Enable debug logs (optional for dev, turn down in production)
1736                window.plugins.OneSignal.Debug.setLogLevel(6);
1737
1738                // Initialize with your OneSignal App ID
1739                window.plugins.OneSignal.initialize("38da2164-7e3c-4830-a4bc-8a975f1cee53");
1740
1741                // Request notification permission if needed
1742                const hasPermission = await window.plugins.OneSignal.Notifications.hasPermission();
1743                if (!hasPermission) {
1744                    const accepted = await window.plugins.OneSignal.Notifications.requestPermission(true);
1745                    if (!accepted) {
1746                        console.log('🔔 Notification permission denied');
1747                        alert(
1748                            '🔔 Notifications disabled\n\nTo receive updates and messages, enable notifications:\n\nSettings > Apps > Lave > Notifications'
1749                        );
1750                        oneSignalInitStarted = false;
1751                        return;
1752                    }
1753                }
1754
1755                // Ensure subscription is opted in
1756                await window.plugins.OneSignal.User.pushSubscription.optIn();
1757
1758                // 🔥 Handle notification opened events
1759                window.plugins.OneSignal.Notifications.addEventListener("click", (event) => {
1760                    console.log("Notification clicked:", event);
1761                    handleOneSignalOpen(event.notification);
1762                });
1763
1764                // Handle cold-start opens (notification tapped before JS attached)
1765                try {
1766                    const lastOpen = await window.plugins.OneSignal.Notifications.getLastNotification();
1767                    if (lastOpen?.notification) {
1768                        console.log("Handling last opened notification on init");
1769                        handleOneSignalOpen(lastOpen.notification);
1770                    }
1771                } catch (e) {
1772                    console.warn("Unable to read last opened notification", e);
1773                }
1774
1775                // Listen for subscription changes
1776                window.plugins.OneSignal.User.pushSubscription.addEventListener("change", async (event) => {
1777                    if (event.current?.id) {
1778                        console.log("Subscription ID changed:", event.current.id);
1779                        await saveSubscriptionId(event.current.id);
1780                    }
1781                });
1782
1783                oneSignalReady = true;
1784                await syncOneSignalSubscription();
1785
1786            } catch (err) {
1787                console.error("OneSignal Error:", err);
1788                oneSignalInitStarted = false; // allow retry
1789            }
1790        }
1791
1792        async function syncOneSignalSubscription() {
1793            try {
1794                const subscriptionId = await window.plugins.OneSignal.User.pushSubscription.getIdAsync();
1795                if (subscriptionId) await saveSubscriptionId(subscriptionId);
1796            } catch (error) {
1797                console.error('Could not read OneSignal subscription:', error);
1798            }
1799        }
1800
1801        /**
1802         * Save subscription ID - Simple & Effective!
1803         */
1804        async function saveSubscriptionId(subscriptionId) {
1805            // Let the server check the current session; data-navigate-once scripts
1806            // must not freeze the authentication state of the initial page.
1807        try {
1808            const response = await fetch("/save/onesignal", {
1809                method: "POST",
1810                headers: {
1811                    "Content-Type": "application/json",
1812                    "Accept": "application/json"
1813                },
1814                body: JSON.stringify({
1815                    subscription_id: subscriptionId
1816                })
1817            });
1818
1819            if (response.status === 401) return;
1820
1821            if (!response.ok) {
1822                const text = await response.text();
1823                console.error("? Server error:", response.status, text.substring(0, 200));
1824                return;
1825            }
1826
1827            const result = await response.json();
1828            if (result.status === 'success') {
1829                console.log("? Subscription saved");
1830            } else {
1831                console.error("? Failed:", result.message);
1832            }
1833        } catch (error) {
1834            console.error("? Error:", error);
1835        }
1836        }
1837
1838        // Initialize OneSignal on page load
1839        document.addEventListener("DOMContentLoaded", initOneSignal);
1840        document.addEventListener('deviceready', initOneSignal);
1841        document.addEventListener('livewire:navigated', initOneSignal);
1842        document.body.addEventListener("htmx:load", initOneSignal);
1843    </script>
1843
1844
1845
1846
1847
1848    
1849    
1849<script data-navigate-once>
1850        let specialSchemeClickHandler = null;
1851
1852        function initSpecialSchemeHandlers() {
1853            if (!window.Capacitor) return;
1854
1855            // Remove existing handler first to prevent duplicates
1856            if (specialSchemeClickHandler) {
1857                document.removeEventListener("click", specialSchemeClickHandler);
1858            }
1859
1860            // Handle tel:, mailto:, sms: links
1861            specialSchemeClickHandler = async (e) => {
1862                const link = e.target.closest('a[href^="tel:"], a[href^="mailto:"], a[href^="sms:"]');
1863                if (!link) return;
1864
1865                e.preventDefault();
1866                const url = link.href;
1867
1868                try {
1869                    // Open in external system app (phone dialer, email client, sms app)
1870                    await Capacitor.Plugins.InAppBrowser.openInExternalBrowser({
1871                        url
1872                    });
1873                    console.log('? Opened special scheme URL:', url);
1874                } catch (err) {
1875                    console.error('? Error opening special scheme URL:', err);
1876                    // Fallback: try window.open
1877                    window.open(url, '_system');
1878                }
1879            };
1880            document.addEventListener("click", specialSchemeClickHandler);
1881            console.log('? Special scheme handlers initialized (tel:, mailto:, sms:)');
1882        }
1883
1884        document.addEventListener("DOMContentLoaded", initSpecialSchemeHandlers);
1885        document.addEventListener('livewire:navigated', initSpecialSchemeHandlers);
1886        document.body.addEventListener("htmx:afterSwap", initSpecialSchemeHandlers);
1887    </script>
1887
1888
1889    
1890    
1890<script data-navigate-once>
1891        (function initDeepLinks() {
1892            if (!window.Capacitor?.Plugins?.App) return;
1893
1894            const {
1895                App
1896            } = window.Capacitor.Plugins;
1897
1898            function isAllowedHost(hostname) {
1899                return hostname === 'lave.live' || hostname.endsWith('.lave.live');
1900            }
1901
1902            function toWebUrl(incomingUrl) {
1903                if (!incomingUrl) return null;
1904
1905                // Handle lave://https://lave.live/... format (full URL embedded after scheme)
1906                if (incomingUrl.startsWith('lave://https://') || incomingUrl.startsWith('lave://http://')) {
1907                    const realUrl = incomingUrl.replace(/^lave:\/\//, '');
1908                    try {
1909                        const parsed = new URL(realUrl);
1910                        if (isAllowedHost(parsed.hostname)) return parsed.toString();
1911                    } catch (_) {
1912                        // fall through
1913                    }
1914                }
1915
1916                let u;
1917                try {
1918                    u = new URL(incomingUrl);
1919                } catch (_) {
1920                    return null;
1921                }
1922
1923                // Direct https links (if someone passes full URL)
1924                if ((u.protocol === 'https:' || u.protocol === 'http:') && isAllowedHost(u.hostname)) {
1925                    return u.toString();
1926                }
1927
1928                // Custom scheme: lave://home/app OR lave://open?url=https%3A%2F%2Flave.live%2F...
1929                if (u.protocol === 'lave:') {
1930                    // Check for embedded URL param first
1931                    const embedded = u.searchParams.get('url');
1932                    if (embedded) {
1933                        try {
1934                            const embeddedUrl = new URL(embedded);
1935                            if (isAllowedHost(embeddedUrl.hostname)) return embeddedUrl.toString();
1936                        } catch (_) {
1937                            // ignore invalid embedded URL
1938                        }
1939                    }
1940
1941                    // Convert lave://some/path?x=1#hash => https://lave.live/some/path?x=1#hash
1942                    // u.host contains the first path segment (e.g. "home" from lave://home/app)
1943                    const path = `/${u.host}${u.pathname}`.replace(/\/{2,}/g, '/');
1944                    return `https://lave.live${path}${u.search}${u.hash}`;
1945                }
1946
1947                return null;
1948            }
1949
1950            function navigateFromDeepLink(url) {
1951                const target = toWebUrl(url);
1952                if (!target) return;
1953                console.log('🔗 Deep link received, navigating to:', target);
1954                window.location.href = target;
1955            }
1956
1957            // Cold start - app was closed, opened via deep link
1958            App.getLaunchUrl()
1959                .then(result => {
1960                    if (result?.url) navigateFromDeepLink(result.url);
1961                })
1962                .catch(() => {});
1963
1964            // Warm start - app was in background, opened via deep link
1965            App.addListener('appUrlOpen', (event) => {
1966                if (event?.url) navigateFromDeepLink(event.url);
1967            });
1968
1969            console.log('? Deep link handler initialized');
1970        })();
1971    </script>
1971
1972
1973    
1974    
1974<script data-navigate-once>
1975        let targetBlankClickHandler = null;
1976        let targetBlankSubmitHandler = null;
1977
1978        function initTargetBlankHandlers() {
1979            if (!window.Capacitor) return;
1980
1981            // Remove existing handlers first to prevent duplicates
1982            if (targetBlankClickHandler) {
1983                document.removeEventListener("click", targetBlankClickHandler, true);
1984            }
1985            if (targetBlankSubmitHandler) {
1986                document.removeEventListener("submit", targetBlankSubmitHandler, true);
1987            }
1988
1989            // Handle _blank target for <a> elements
1990            // Use capture phase to catch events early and prevent default browser behavior
1991            targetBlankClickHandler = async (e) => {
1992                const link = e.target.closest('a[target="_blank"]');
1993                if (!link) return;
1994
1995                // Skip if it's a special scheme (handled by specialSchemeClickHandler)
1996                if (link.href && (link.href.startsWith('tel:') || link.href.startsWith('mailto:') || link.href
1997                        .startsWith('sms:'))) {
1998                    return;
1999                }
2000
2001                // Prevent default immediately to stop browser from opening
2002                e.preventDefault();
2003                e.stopPropagation();
2004
2005                const url = link.href || link.getAttribute('href');
2006                if (!url) return;
2007
2008                try {
2009                    // Check if native="external_browser" attribute is set to use external browser
2010                    if (link.getAttribute("native") === "external_browser") {
2011                        await Capacitor.Plugins.InAppBrowser.openInExternalBrowser({
2012                            url: url
2013                        });
2014                        console.log('✅ Opened in external browser:', url);
2015                    } else {
2016                        // Default: Use InAppBrowser System Browser (Custom Tabs for Android, SFSafariViewController for iOS)
2017                        // This opens in-app but uses system browser components, not external browser
2018                        await Capacitor.Plugins.Browser.open({
2019                            url,
2020                            options: {
2021                                showToolbar: true,
2022                                toolbarPosition: "TOP"
2023                            }
2024                        });
2025                    }
2026                } catch (err) {
2027                    alert("Browser error:", err);
2028                }
2029            };
2030            // Use capture phase (true) to catch events before they bubble
2031            document.addEventListener("click", targetBlankClickHandler, true);
2032
2033        }
2034
2035        document.addEventListener("DOMContentLoaded", initTargetBlankHandlers);
2036        document.addEventListener('livewire:navigated', initTargetBlankHandlers);
2037        document.body.addEventListener("htmx:afterSwap", initTargetBlankHandlers);
2038    </script>
2038
2039
2040    
2041    
2041<script data-navigate-once>
2042        let capacitorFileClickHandler = null;
2043
2044        function initFileInput() {
2045            if (!window.Capacitor) return;
2046
2047            const fileModal = document.getElementById("fileModal");
2048            let currentInput = null;
2049            let tempInputs = []; // Keep references to prevent garbage collection
2050
2051            // Create modal if it doesn't exist
2052            if (!fileModal) {
2053                const modal = document.createElement("div");
2054                modal.id = "fileModal";
2055                modal.className = "fixed inset-0 hidden items-center justify-center bg-black bg-opacity-60 px-4";
2056                modal.style.zIndex = "2147483647";
2057                modal.innerHTML = `
2058                    <div class="relative top-40 mx-auto w-full max-w-md overflow-hidden rounded-2xl bg-white shadow-xl">
2059                        <div class="flex items-center justify-between border-b border-gray-200 px-5 py-3">
2060                            <div class="text-base font-semibold text-gray-800">Select file</div>
2061                            <button onclick="closeModal('fileModal')" class="text-2xl font-light text-gray-400 hover:text-gray-600" type="button">×</button>
2062                        </div>
2063                        <div class="divide-y divide-gray-100">
2064                            <button onclick="choosePhoto()" class="flex w-full items-center gap-3 px-5 py-3 text-left text-gray-700 hover:bg-gray-50 active:bg-gray-100">
2065                                <span class="text-xl">📸</span>
2066                                <span class="text-sm">Take photo with camera</span>
2067                            </button>
2068                            <button onclick="chooseFromLibrary()" class="flex w-full items-center gap-3 px-5 py-3 text-left text-gray-700 hover:bg-gray-50 active:bg-gray-100">
2069                                <span class="text-xl">🖼️</span>
2070                                <span class="text-sm">Select photo from gallery</span>
2071                            </button>
2072                            <button onclick="recordVideo()" class="flex w-full items-center gap-3 px-5 py-3 text-left text-gray-700 hover:bg-gray-50 active:bg-gray-100">
2073                                <span class="text-xl">📹</span>
2074                                <span class="text-sm">Record video</span>
2075                            </button>
2076                            <button onclick="chooseFile()" class="flex w-full items-center gap-3 px-5 py-3 text-left text-gray-700 hover:bg-gray-50 active:bg-gray-100">
2077                                <span class="text-xl">📁</span>
2078                                <span class="text-sm">Choose file</span>
2079                            </button>
2080                        </div>
2081                    </div>
2082                `;
2083                document.body.appendChild(modal);
2084            }
2085
2086            const modal = document.getElementById("fileModal");
2087
2088            // Delegation also covers inputs inserted by Livewire modal renders.
2089            if (capacitorFileClickHandler) {
2090                document.removeEventListener('click', capacitorFileClickHandler);
2091            }
2092            capacitorFileClickHandler = (e) => {
2093                const inp = e.target.closest('input[type="file"]');
2094                if (!inp || inp._capacitorNativePicker) return;
2095                e.preventDefault();
2096                currentInput = inp;
2097                if (modal) modal.classList.remove('hidden');
2098            };
2099            document.addEventListener('click', capacitorFileClickHandler);
2100
2101            // Click outside modal to close
2102            if (modal) {
2103                if (modal._modalClickHandler) {
2104                    modal.removeEventListener("click", modal._modalClickHandler);
2105                }
2106
2107                modal._modalClickHandler = (e) => {
2108                    if (e.target === modal) modal.classList.add("hidden");
2109                };
2110                modal.addEventListener("click", modal._modalClickHandler);
2111            }
2112
2113            // Helper function to inject file into input
2114            function injectFile(file, targetInput) {
2115                if (!targetInput?.isConnected) {
2116                    console.error('No file input available to inject file');
2117                    return;
2118                }
2119                try {
2120                    const dt = new DataTransfer();
2121                    dt.items.add(file);
2122                    targetInput.files = dt.files;
2123                    targetInput.dispatchEvent(new Event("change", {
2124                        bubbles: true
2125                    }));
2126                    console.log(`Selected: ${file.name}`);
2127                } catch (error) {
2128                    console.error('Error injecting file:', error);
2129                }
2130            }
2131
2132            // Helper to create temp input and inject file
2133            function createAndInjectFile(inputEl) {
2134                const targetInput = currentInput;
2135                inputEl._capacitorNativePicker = true;
2136                // Add input to DOM (hidden) to prevent garbage collection
2137                inputEl.style.position = 'absolute';
2138                inputEl.style.left = '-9999px';
2139                inputEl.style.opacity = '0';
2140                inputEl.style.pointerEvents = 'none';
2141                document.body.appendChild(inputEl);
2142
2143                // Keep reference to prevent garbage collection
2144                tempInputs.push(inputEl);
2145
2146                // Use addEventListener for better reliability
2147                const changeHandler = (e) => {
2148                    if (inputEl.files && inputEl.files.length > 0) {
2149                        injectFile(inputEl.files[0], targetInput);
2150                    }
2151                    // Clean up after use
2152                    inputEl.removeEventListener('change', changeHandler);
2153                    setTimeout(() => {
2154                        if (inputEl.parentNode) {
2155                            inputEl.parentNode.removeChild(inputEl);
2156                        }
2157                        const index = tempInputs.indexOf(inputEl);
2158                        if (index > -1) {
2159                            tempInputs.splice(index, 1);
2160                        }
2161                    }, 100);
2162                };
2163
2164                inputEl.addEventListener('change', changeHandler);
2165
2166                // Small delay to ensure modal is closed and file picker can open
2167                setTimeout(() => {
2168                    inputEl.click();
2169                }, 100);
2170            }
2171
2172            // Global functions for modal buttons
2173            window.choosePhoto = function() {
2174                if (!currentInput) {
2175                    console.error('No file input selected');
2176                    return;
2177                }
2178                if (modal) modal.classList.add("hidden");
2179                // Use native file input - doesn't interfere with status bar
2180                const input = document.createElement("input");
2181                input.type = "file";
2182                input.accept = "image/*";
2183                input.capture = "environment"; // Opens camera directly
2184                createAndInjectFile(input);
2185            };
2186
2187            window.chooseFromLibrary = function() {
2188                if (!currentInput) {
2189                    console.error('No file input selected');
2190                    return;
2191                }
2192                if (modal) modal.classList.add("hidden");
2193                // Use native file input - doesn't interfere with status bar
2194                const input = document.createElement("input");
2195                input.type = "file";
2196                input.accept = "image/*";
2197                createAndInjectFile(input);
2198            };
2199
2200            window.recordVideo = function() {
2201                if (!currentInput) {
2202                    console.error('No file input selected');
2203                    return;
2204                }
2205                if (modal) modal.classList.add("hidden");
2206                // Use native file input - doesn't interfere with status bar
2207                const input = document.createElement("input");
2208                input.type = "file";
2209                input.accept = "video/*";
2210                input.capture = "environment"; // Opens camera for video
2211                createAndInjectFile(input);
2212            };
2213
2214            window.chooseFile = function() {
2215                if (!currentInput) {
2216                    console.error('No file input selected');
2217                    return;
2218                }
2219                if (modal) modal.classList.add("hidden");
2220                // Use native file input - simple and reliable
2221                const input = document.createElement("input");
2222                input.type = "file";
2223                createAndInjectFile(input);
2224            };
2225
2226            window.closeModal = function(modalId) {
2227                const m = document.getElementById(modalId);
2228                if (m) m.classList.add("hidden");
2229            };
2230        }
2231
2232        document.addEventListener("DOMContentLoaded", initFileInput);
2233        document.addEventListener('livewire:navigated', initFileInput);
2234        document.body.addEventListener("htmx:afterSwap", initFileInput);
2235    </script>
2235
2236
2237    
2238    
2238<script data-navigate-once>
2239        function updateCapacitorCountryCode(select) {
2240            const form = select.closest('form') || select.parentElement;
2241            const hiddenField = form?.querySelector('input[name="tel_country_code"]');
2242            const selectedOption = select.options[select.selectedIndex];
2243            if (!hiddenField || !selectedOption) return;
2244
2245            hiddenField.value = selectedOption.getAttribute('data-phonecode') || '';
2246            hiddenField.dispatchEvent(new Event('input', { bubbles: true }));
2247        }
2248
2249        // The select may be rendered after page initialization by Livewire.
2250        document.addEventListener('change', (event) => {
2251            const select = event.target.closest('select[name="country"]');
2252            if (select) updateCapacitorCountryCode(select);
2253        });
2254
2255        function initCountrySelect() {
2256            document.querySelectorAll('select[name="country"]').forEach(updateCapacitorCountryCode);
2257        }
2258
2259        // Initialize on page load and after HTMX swaps
2260        document.addEventListener('DOMContentLoaded', initCountrySelect);
2261        document.addEventListener('livewire:navigated', initCountrySelect);
2262        document.body.addEventListener('htmx:afterSwap', initCountrySelect);
2263    </script>
2263
2264
2265    
2266    
2266<script data-navigate-once>
2267        let contactPickerClickHandler = null;
2268
2269        function initContactPicker() {
2270            if (!window.Capacitor) return;
2271
2272            const contactPickerModal = document.getElementById('contactPickerModal');
2273            const contactList = document.getElementById('contactList');
2274            const contactNameHeader = document.getElementById('contactNameHeader');
2275            const closeModalPhoneBtn = document.getElementById('closeModalPhone');
2276
2277            function opencontactPickerModal(contactName, phones, onSelect) {
2278                if (!contactList) return;
2279                contactList.innerHTML = '';
2280                if (contactNameHeader) {
2281                    contactNameHeader.textContent = contactName || 'Select Phone Number';
2282                    contactNameHeader.title = contactName;
2283                }
2284
2285                phones.forEach((number, index) => {
2286                    const btn = document.createElement('button');
2287                    btn.className =
2288                        "w-full text-left px-5 py-4 text-base text-gray-800 hover:bg-blue-50 active:bg-blue-100 transition-colors flex items-center gap-3";
2289                    btn.type = "button";
2290
2291                    // Add phone icon
2292                    const icon = document.createElement('span');
2293                    icon.innerHTML = '📱';
2294                    icon.className = "text-xl";
2295
2296                    const text = document.createElement('span');
2297                    text.textContent = number;
2298                    text.className = "flex-1";
2299
2300                    btn.appendChild(icon);
2301                    btn.appendChild(text);
2302
2303                    btn.addEventListener('click', () => {
2304                        closecontactPickerModal();
2305                        onSelect(number);
2306                    });
2307                    contactList.appendChild(btn);
2308                });
2309
2310                if (contactPickerModal) {
2311                    contactPickerModal.classList.remove('hidden');
2312                    contactPickerModal.classList.add('flex');
2313                }
2314            }
2315
2316            function closecontactPickerModal() {
2317                if (contactPickerModal) {
2318                    contactPickerModal.classList.add('hidden');
2319                    contactPickerModal.classList.remove('flex');
2320                }
2321            }
2322
2323            // Close button handler
2324            if (closeModalPhoneBtn) {
2325                // Remove existing listener first
2326                if (closeModalPhoneBtn._clickHandler) {
2327                    closeModalPhoneBtn.removeEventListener('click', closeModalPhoneBtn._clickHandler);
2328                }
2329                closeModalPhoneBtn._clickHandler = closecontactPickerModal;
2330                closeModalPhoneBtn.addEventListener('click', closeModalPhoneBtn._clickHandler);
2331            }
2332
2333            // Click outside to close
2334            if (contactPickerModal) {
2335                if (contactPickerModal._outsideClickHandler) {
2336                    contactPickerModal.removeEventListener('click', contactPickerModal._outsideClickHandler);
2337                }
2338                contactPickerModal._outsideClickHandler = (e) => {
2339                    if (e.target === contactPickerModal) {
2340                        closecontactPickerModal();
2341                    }
2342                };
2343                contactPickerModal.addEventListener('click', contactPickerModal._outsideClickHandler);
2344            }
2345
2346            // Remove existing contact picker handler
2347            if (contactPickerClickHandler) {
2348                document.removeEventListener('click', contactPickerClickHandler);
2349            }
2350
2351            contactPickerClickHandler = async (e) => {
2352                const el = e.target.closest('[native="contact"]');
2353                if (!el) return;
2354
2355                e.preventDefault();
2356
2357                const contactForm = el.closest('form');
2358                if (!contactForm) return;
2359
2360                const {
2361                    Contacts
2362                } = window.Capacitor.Plugins || {};
2363                if (!Contacts) {
2364                    console.log('Contacts plugin not available');
2365                    return;
2366                }
2367
2368                // Function to pick contact
2369                async function tryPickContact() {
2370                    return await Contacts.pickContact({
2371                        projection: {
2372                            name: true,
2373                            phones: true,
2374                            emails: true
2375                        }
2376                    });
2377                }
2378
2379                try {
2380                    // ✅ Try pickContact() first - it should handle permissions automatically
2381                    const result = await tryPickContact();
2382                    await processContactResult(result);
2383                } catch (err) {
2384                    console.error("Contact picker error:", err);
2385
2386                    // 🔄 Fallback: If permission error, request permission and retry
2387                    if (err.message && err.message.toLowerCase().includes('permission')) {
2388                        console.log('Permission error detected, requesting permissions...');
2389
2390                        try {
2391                            const permission = await Contacts.checkPermissions();
2392                            console.log('Current permission:', permission);
2393
2394                            if (permission.contacts !== 'granted') {
2395                                const requested = await Contacts.requestPermissions();
2396                                console.log('Permission requested:', requested);
2397
2398                                if (requested.contacts !== 'granted') {
2399                                    alert(
2400                                        '📱 To select contacts, please allow access in your device settings.\n\nSettings > Apps > Lave > Permissions > Contacts > Full Access'
2401                                    );
2402                                    return;
2403                                }
2404                            }
2405
2406                            // Retry with permission granted
2407                            const result = await tryPickContact();
2408                            await processContactResult(result);
2409
2410                        } catch (permErr) {
2411                            console.error("Permission request failed:", permErr);
2412                            alert(
2413                                '📱 To select contacts, please allow access in your device settings.\n\nSettings > Apps > Lave > Permissions > Contacts > Full Access'
2414                            );
2415                        }
2416                    } else {
2417                        // Different error, show it
2418                        alert('❌ ERROR picking contact:\n\n' + err.message);
2419                    }
2420                }
2421
2422                // Process contact result
2423                async function processContactResult(result) {
2424
2425                    if (result?.contact) {
2426
2427                        const rawName = result.contact.name?.display || 'No name';
2428                        const phones = result.contact.phones?.map(p => p.number) || [];
2429                        const emails = result.contact.emails?.map(e => e.address) || [];
2430
2431
2432                        // Function to keep only alphanumeric characters and spaces (removes emojis & special chars)
2433                        function stripEmojis(text) {
2434                            return text
2435                                .replace(/[^a-zA-Z0-9\s.,'-]/g,
2436                                    '') // Keep only letters, numbers, space
2436s, and basic punctuation
2437                                .trim()
2438                                .replace(/\s+/g, ' '); // Clean up extra spaces
2439                        }
2440
2441                        const name = stripEmojis(rawName);
2442
2443                        // Function to extract country code by comparing with select options
2444                        function extractCountryCodeFromSelect(phoneNumber, countrySelect) {
2445                            // Remove all spaces, dashes, parentheses, and + sign
2446                            let cleaned = phoneNumber.replace(/[\s\-\(\)\+]/g, '');
2447
2448                            // If no digits, return empty
2449                            if (!cleaned || !/^\d+$/.test(cleaned)) {
2450                                return {
2451                                    countryCode: null,
2452                                    phone: cleaned,
2453                                    matchFound: false
2454                                };
2455                            }
2456
2457                            // Get all country codes from the select options
2458                            const options = countrySelect ? countrySelect.options : [];
2459                            const countryCodes = [];
2460
2461                            // Preferred countries for shared codes (main countries over territories)
2462                            const preferredCountries = [
2463                                'United Kingdom', 'United States', 'Canada', 'Australia',
2464                                'France', 'Denmark', 'Netherlands', 'New Zealand'
2465                            ];
2466
2467                            for (let i = 0; i < options.length; i++) {
2468                                const phonecode = options[i].getAttribute('data-phonecode');
2469                                const countryName = options[i].value || options[i].textContent.trim();
2470                                if (phonecode) {
2471                                    const isPreferred = preferredCountries.some(pref =>
2472                                        countryName.toLowerCase().includes(pref.toLowerCase())
2473                                    );
2474                                    countryCodes.push({
2475                                        code: phonecode,
2476                                        index: i,
2477                                        name: countryName,
2478                                        isPreferred: isPreferred
2479                                    });
2480                                }
2481                            }
2482
2483                            // Sort by:
2484                            // 1. Code length (longest first) - e.g., 1684 before 1
2485                            // 2. Preferred countries first (for same length codes)
2486                            // 3. Index (original order)
2487                            countryCodes.sort((a, b) => {
2488                                if (b.code.length !== a.code.length) {
2489                                    return b.code.length - a.code.length;
2490                                }
2491                                if (a.isPreferred !== b.isPreferred) {
2492                                    return b.isPreferred ? 1 : -1;
2493                                }
2494                                return a.index - b.index;
2495                            });
2496
2497                            // Try to find a matching country code
2498                            for (const country of countryCodes) {
2499                                if (cleaned.startsWith(country.code)) {
2500                                    // Found a match!
2501                                    const phone = cleaned.substring(country.code.length);
2502                                    return {
2503                                        countryCode: country.code,
2504                                        phone: phone,
2505                                        matchFound: true,
2506                                        countryIndex: country.index,
2507                                        countryName: country.name
2508                                    };
2509                                }
2510                            }
2511
2512                            // No match found, return the whole number as-is
2513                            return {
2514                                countryCode: null,
2515                                phone: cleaned,
2516                                matchFound: false
2517                            };
2518                        }
2519
2520                        // Function to populate form fields
2521                        function populateContactForm(selectedPhone) {
2522                            // The picker may return after navigation or after its form has closed.
2523                            if (!contactForm.isConnected) return;
2524
2525                            const nameInput = contactForm.querySelector('input[name="name"]');
2526                            const emailInput = contactForm.querySelector('input[name="email"]');
2527                            const telInput = contactForm.querySelector('input[name="tel"]');
2528                            const countrySelect = contactForm.querySelector('select[name="country"]');
2529                            const countryCodeInput = contactForm.querySelector('input[name="tel_country_code"]');
2530
2531                            function updateInput(input, value) {
2532                                if (!input) return;
2533                                input.value = value;
2534                                input.dispatchEvent(new Event('input', { bubbles: true }));
2535                            }
2536
2537                            // Populate name
2538                            updateInput(nameInput, name);
2539
2540                            // Populate email
2541                            if (emails.length > 0) updateInput(emailInput, emails[0]);
2542
2543                            // Process phone number and country code
2544                            if (selectedPhone) {
2545                                const result = extractCountryCodeFromSelect(selectedPhone, countrySelect);
2546
2547                                // Populate phone number (without country code)
2548                                updateInput(telInput, result.phone);
2549
2550                                // Set country based on matched country code
2551                                if (result.matchFound && countrySelect) {
2552                                    // Set the dropdown
2553                                    countrySelect.selectedIndex = result.countryIndex;
2554
2555                                    // Directly update the hidden field with the country code
2556                                    if (countryCodeInput && result.countryCode) {
2557                                        updateInput(countryCodeInput, result.countryCode);
2558                                    }
2559
2560                                    // Also trigger the change event as backup
2561                                    countrySelect.dispatchEvent(new Event('change', { bubbles: true }));
2562
2563                                    console.log('✅ Country set to:', result.countryName, '(+' + result
2564                                        .countryCode + ')');
2565                                }
2566                            }
2567                        }
2568
2569                        if (phones.length === 0) {
2570                            // No phone number, just populate name and email
2571                            populateContactForm('');
2572                        } else if (phones.length === 1) {
2573                            // Single phone number, populate immediately
2574                            populateContactForm(phones[0]);
2575                        } else {
2576                            // Multiple phone numbers, show modal
2577                            opencontactPickerModal(name, phones, selected => {
2578                                populateContactForm(selected);
2579                            });
2580                        }
2581                    } else {
2582                        // No contact in result
2583                        alert('⚠️ No contact found');
2584                    }
2585                }
2586            };
2587            document.addEventListener('click', contactPickerClickHandler);
2588        }
2589
2590        document.addEventListener("DOMContentLoaded", initContactPicker);
2591        document.addEventListener('livewire:navigated', initContactPicker);
2592        document.body.addEventListener("htmx:afterSwap", initContactPicker);
2593    </script>
2593
2594
2595
2596    
2597    
2597<script data-navigate-once>
2598        function initCapacitorButtons() {
2599            if (!window.Capacitor) return;
2600
2601            const btnDevice = document.getElementById('btnDevice');
2602            if (btnDevice && !btnDevice._capacitorHandlerAttached) {
2603                btnDevice._capacitorHandlerAttached = true;
2604                btnDevice.addEventListener('click', async () => {
2605                    try {
2606                        if (window.Capacitor.Plugins.Device) {
2607                            const info = await window.Capacitor.Plugins.Device.getInfo();
2608                            alert(JSON.stringify(info, null, 2));
2609                        } else alert(JSON.stringify({
2610                            platform: 'web',
2611                            userAgent: navigator.userAgent
2612                        }, null, 2));
2613                    } catch (err) {
2614                        alert("Error: " + err);
2615                    }
2616                });
2617            }
2618
2619        }
2620
2621        document.addEventListener("DOMContentLoaded", initCapacitorButtons);
2622        document.addEventListener('livewire:navigated', initCapacitorButtons);
2623        document.body.addEventListener("htmx:afterSwap", initCapacitorButtons);
2624    </script>
2624
2625
2626    
2627    
2627<script data-navigate-once>
2628        /**
2629         * Reveal the parsed page after a paint opportunity. Images and other
2630         * non-critical resources can finish loading with the page visible.
2631         */
2632        let splashScreenHidden = false;
2633        let splashScreenHiding = false;
2634
2635        async function hideSplashScreen() {
2636            // Prevent multiple calls
2637            if (splashScreenHidden || splashScreenHiding) return;
2638
2639            // Wait for Capacitor to be available
2640            if (!window.Capacitor) {
2641                setTimeout(hideSplashScreen, 100);
2642                return;
2643            }
2644
2645            // Wait for SplashScreen plugin to be available
2646            if (!window.Capacitor.Plugins || !window.Capacitor.Plugins.SplashScreen) {
2647                setTimeout(hideSplashScreen, 100);
2648                return;
2649            }
2650
2651            splashScreenHiding = true;
2652            try {
2653                // Wait for DOM to be ready
2654                if (document.readyState === 'loading') {
2655                    await new Promise(resolve => {
2656                        if (document.readyState === 'loading') {
2657                            document.addEventListener('DOMContentLoaded', resolve);
2658                        } else {
2659                            resolve();
2660                        }
2661                    });
2662                }
2663
2664                // Give the browser a chance to paint before revealing the page.
2665                await new Promise(resolve => requestAnimationFrame(resolve));
2666                await new Promise(resolve => requestAnimationFrame(resolve));
2667
2668                // Hide the splash screen
2669                await window.Capacitor.Plugins.SplashScreen.hide();
2670                splashScreenHidden = true;
2671                console.log('✅ Splash screen hidden - initial page ready');
2672            } catch (error) {
2673                console.warn('Could not hide splash screen:', error);
2674                // Try one more time after a delay
2675                setTimeout(async () => {
2676                    try {
2677                        if (!splashScreenHidden && window.Capacitor?.Plugins?.SplashScreen) {
2678                            await window.Capacitor.Plugins.SplashScreen.hide();
2679                            splashScreenHidden = true;
2680                            console.log('✅ Splash screen hidden (retry)');
2681                        }
2682                    } catch (retryError) {
2683                        console.error('Failed to hide splash screen after retry:', retryError);
2684                    }
2685                }, 1000);
2686            } finally {
2687                splashScreenHiding = false;
2688            }
2689        }
2690
2691        // Start checking when script loads
2692        if (document.readyState === 'loading') {
2693            document.addEventListener('DOMContentLoaded', hideSplashScreen, { once: true });
2694        } else {
2695            // DOM is already ready, start the hide process
2696            hideSplashScreen();
2697        }
2698
2699        // Fallback: ensure splash screen is hidden after window fully loads
2700        window.addEventListener('load', hideSplashScreen, { once: true });
2701
2702        // Retry initialization if an earlier readiness hook was missed.
2703        setTimeout(() => {
2704            if (splashScreenHidden) return;
2705            console.warn('Fallback splash hide after timeout');
2706            hideSplashScreen();
2707        }, 4000);
2708    </script>
2708
2709
2710    
2711    
2711<script data-navigate-once>
2712        // Apply any pending deep link without waiting for HTMX
2713        (function applyPendingDeepLink() {
2714            const DEEPLINK_KEY = 'pending_deeplink';
2715            let deepLinkApplied = false;
2716
2717            async function apply(delayMs = 0) {
2718                if (deepLinkApplied) return;
2719
2720                // Small optional delay to let bootstrapping settle
2721                if (delayMs > 0) {
2722                    await new Promise(resolve => setTimeout(resolve, delayMs));
2723                }
2724
2725                const target = localStorage.getItem(DEEPLINK_KEY);
2726                if (!target) return;
2727
2728                try {
2729                    localStorage.removeItem(DEEPLINK_KEY);
2730                } catch (_) {}
2731
2732                deepLinkApplied = true;
2733                console.log('Applying pending deep link', target);
2734                window.location.href = target;
2735            }
2736
2737            const applySoon = () => apply(50);
2738            const applyNow = () => apply(0);
2739
2740            if (document.readyState === 'loading') {
2741                document.addEventListener('DOMContentLoaded', applySoon);
2742            } else {
2743                applySoon();
2744            }
2745
2746            window.addEventListener('load', applyNow);
2747            window.addEventListener('focus', applyNow);
2748            document.addEventListener('visibilitychange', applyNow);
2749
2750            // When app returns to foreground (Capacitor)
2751            if (window.Capacitor?.Plugins?.App) {
2752                try {
2753                    window.Capacitor.Plugins.App.addListener('appStateChange', ({
2754                        isActive
2755                    }) => {
2756                        if (isActive) applyNow();
2757                    });
2758                } catch (e) {
2759                    console.warn('App state listener failed', e);
2760                }
2761            }
2762
2763            // Periodic safety check in case other hooks miss
2764            setTimeout(applyNow, 500);
2765            setTimeout(applyNow, 1500);
2766        })();
2767    </script>
2767
2768
2769</div>
2770
2771    
2771<script>
2772        (function() {
2773            const isPWA =
2774                window.matchMedia('(display-mode: standalone)').matches ||
2775                window.navigator.standalone === true;
2776
2777            if (isPWA) {
2778                document.cookie = "app_mode=pwa; path=/; max-age=31536000";
2779            }
2780        })();
2781    </script>
2781
2782
2783    
2784
2785
2786
2787    <!-- Search settings modal container -->
2788    
2788<script>
2789        /**
2790         * Global Shadow DOM Content Renderer
2791         * Processes elements with .shadow-content and .raw-content
2792         */
2793        function renderShadowContent(container = document) {
2794            // Ensure we have a valid element to search in
2795            const searchRoot = (container && container.querySelectorAll) ? container : document;
2796
2797            const targets = (searchRoot.classList && searchRoot.classList.contains('shadow-content')) ? [searchRoot] :
2798                searchRoot.querySelectorAll('.shadow-content');
2799
2800            // If we're processing a specific container and find nothing, fallback once to the whole document.
2801            if (targets.length === 0 && searchRoot !== document) {
2802                renderShadowContent(document);
2803                return;
2804            }
2805
2806            targets.forEach(host => {
2807                if (host.shadowRoot) return; // Skip if already processed
2808
2809                const dataElement = host.parentElement.querySelector('.raw-content');
2810                if (!dataElement) return;
2811
2812                try {
2813                    const shadow = host.attachShadow({
2814                        mode: 'open'
2815                    });
2816                    const content = JSON.parse(dataElement.textContent);
2817
2818                    const style = document.createElement('style');
2819                    style.textContent = `
2820
2821                     img {
2822                                    max-width: 100% !important;
2823                                    height: auto !important;
2824                                    display: block !important;
2825                                    object-fit: contain !important;
2826                                    margin: auto !important;
2827                                }
2828                                iframe, video {
2829                                    display: table !important;
2830                                    margin-left: auto !important;
2831                                    margin-right: auto !important;
2832                                    max-width: 100% !important;
2833                                    object-fit: contain !important;
2834                                }
2835                   `;
2836
2837                    const wrapper = document.createElement('div');
2838                    wrapper.innerHTML = content;
2839
2840                    shadow.appendChild(style);
2841                    shadow.appendChild(wrapper);
2842                } catch (e) {
2843                    console.error('Shadow DOM Error:', e);
2844                }
2845            });
2846        }
2847
2848        document.addEventListener('livewire:init', () => {
2849            renderShadowContent();
2850            Livewire.hook('morphed', ({
2851                el
2852            }) => renderShadowContent(el));
2853        });
2854
2855        document.addEventListener('livewire:navigated', () => renderShadowContent());
2856    </script>
2856
2857
2858
2859    
2859<script>
2860        document.addEventListener('livewire:init', () => {
2861            const refreshUIkit = (root) => {
2862                window.requestAnimationFrame(() => {
2863                    if (!window.UIkit || !root?.isConnected) {
2864                        return;
2865                    }
2866
2867                    const targets = [];
2868
2869                    if (isUIkitComponent(root)) {
2870                        targets.push(root);
2871                    }
2872
2873                    root.querySelectorAll('*').forEach((element) => {
2874                        if (isUIkitComponent(element)) {
2875                            targets.push(element);
2876                        }
2877                    });
2878
2879                    targets.forEach((element) => window.UIkit.update(element));
2880                });
2881            };
2882
2883            const isUIkitManagedGraphic = (element) => {
2884                if (element.matches(
2885                        '[uk-img], [data-uk-img], [uk-svg], [data-uk-svg], [uk-spinner], [data-uk-spinner]'
2886                    )) {
2887                    return true;
2888                }
2889
2890                return Array.from(element.attributes).some(({
2891                    name
2892                }) => {
2893                    return /^(data-)?uk-(.+-)?icon$/.test(name);
2894                });
2895            };
2896
2897            const isUIkitComponent = (element) => {
2898                return Array.from(element.attributes).some(({
2899                    name
2900                }) => {
2901                    return /^(data-)?uk-/.test(name);
2902                });
2903            };
2904
2905            Livewire.hook('morph.updating', ({
2906                el,
2907                skip,
2908                childrenOnly
2909            }) => {
2910                if (isUIkitManagedGraphic(el)) {
2911                    skip();
2912
2913                    return;
2914                }
2915
2916                if (isUIkitComponent(el)) {
2917                    childrenOnly();
2918                }
2919            });
2920
2921            Livewire.hook('morphed', ({
2922                el
2923            }) => {
2924                refreshUIkit(el);
2925            });
2926
2927            Livewire.hook('partial.morphed', ({
2928                startNode
2929            }) => {
2930                refreshUIkit(startNode.parentElement);
2931            });
2932
2933            Livewire.hook('morph.added', ({
2934                el
2935            }) => {
2936                refreshUIkit(el);
2937            });
2938        });
2939    </script>
2939
2940
2941    
2941<script>
2942        document.addEventListener('livewire:init', () => {
2943            const pendingSubmitComponents = new Set();
2944
2945            document.addEventListener('submit', (event) => {
2946                const form = event.target;
2947
2948                if (!(form instanceof HTMLFormElement)) {
2949                    return;
2950                }
2951
2952                const isLivewireForm = Array.from(form.attributes)
2953                    .some((attribute) => attribute.name === 'wire:submit' || attribute.name.startsWith(
2954                        'wire:submit.'));
2955
2956                if (!isLivewireForm) {
2957                    return;
2958                }
2959
2960                const component = form.closest('[wire\\:id]');
2961
2962                if (component) {
2963                    pendingSubmitComponents.add(component.getAttribute('wire:id'));
2964                }
2965            }, true);
2966
2967            Livewire.interceptMessage(({
2968                message,
2969                onSuccess
2970            }) => {
2971                const component = message.component;
2972
2973                if (!pendingSubmitComponents.delete(component.id)) {
2974                    return;
2975                }
2976
2977                onSuccess(({
2978                    onRender
2979                }) => {
2980                    onRender(() => {
2981                        const messages = component.$wire.$errors.all();
2982                        const firstError = Array.from(component.el.querySelectorAll(
2983                                'p, span, small, div, strong'))
2984                            .find((element) => messages.includes(element.textContent
2985                                .trim()));
2986
2987                        firstError?.scrollIntoView({
2988                            behavior: 'smooth',
2989                            block: 'center',
2990                        });
2991                    });
2992                });
2993            });
2994        });
2995    </script>
2995
2996
2997    
2997<script src="https://lave.live/vendor/livewire/livewire.min.js?id=0ae8dee3"   data-csrf="ovoz3KLq7cYiRVt0AJRBD4c02Ozq7Z9iI2ZIIRks" data-module-url="https://lave.live/livewire-af6929b1" data-update-uri="https://lave.live/livewire-af6929b1/update" data-navigate-once="true"></script>
2997
2998
2999    
2999<script>
3000    window.$zoho = window.$zoho || {};
3001    window.__zohoChatPending = false;
3002    window.__zohoChatSyncTimer = null;
3003
3004    window.__showZohoChat = function() {
3005        if (window.$zoho?.salesiq?.floatwindow?.visible) {
3006            window.$zoho.salesiq.floatwindow.visible('show');
3007            return true;
3008        }
3009
3010        return false;
3011    };
3012
3013    window.openZohoChat = function(event) {
3014        if (event) {
3015            event.preventDefault();
3016        }
3017
3018        if (window.__showZohoChat()) {
3019            return false;
3020        }
3021
3022        window.__zohoChatPending = true;
3023        return false;
3024    };
3025
3026    $zoho.salesiq = $zoho.salesiq || {};
3027    $zoho.salesiq.ready = function() {
3028        if (window.$zoho?.salesiq?.floatbutton?.visible) {
3029            window.$zoho.salesiq.floatbutton.visible('hide');
3030        }
3031
3032        if (window.__zohoChatPending) {
3033            window.__zohoChatPending = false;
3034            window.__showZohoChat();
3035        }
3036    };
3037</script>
vendor: 1 bytes, line 3037
3037
3038<script id="zsiqscript" src="https://salesiq.zohopublic.com/widget?wc=siq1cec881323d29b6db65f0661720c7d07" defer>
3039</script>
3039
3040
3041    
3041<script>
3042if (!window.laveUploadSizeGuardInstalled) {
3043    window.laveUploadSizeGuardInstalled = true;
3044    document.addEventListener('change', function (event) {
3045        const input = event.target;
3046        if (!(input instanceof HTMLInputElement) || input.type !== 'file' || !input.dataset.uploadMaxKb) return;
3047        const limit = Number(input.dataset.uploadMaxKb);
3048        if (Array.from(input.files || []).some(file => file.size > limit * 1024)) {
3049            event.preventDefault();
3050            event.stopImmediatePropagation();
3051            input.value = '';
3052            alert('This file is too large. Maximum file size is :size MB.'.replace(':size', String(Math.round(limit / 1024 * 100) / 100)));
3053        }
3054    }, true);
3055}
3056</script>
vendor: 1 bytes, line 3056
3056
3057<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a4401bdea940f32d',t:'MTc5MDkwNjM3OA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
3057</body>
3058
3059</html>

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.