PageSourceSearch

https://lave.live/pricing

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