PageSourceSearch

https://app.nomados.ai/chat/

html nomados.ai collected 2026-10-05 12:19:29 UTC 368,429 bytes, 9,033 lines download raw bytes

1
2<!DOCTYPE html>
3<html lang="en">
4  <head>
5    <meta charset="UTF-8" />
6    <meta name="viewport" content="width=device-width, initial-scale=1" />
7    <link rel="icon" href="/static/favicon.ico" />
8    
8<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
8
9    
9<script
10      src="https://unpkg.com/[email protected]"
11      integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq"
12      crossorigin="anonymous"
13    ></script>
13
14    <!-- Stripe.js -->
15    
15<script src="https://js.stripe.com/v3/"></script>
15
16    <meta property="og:type" content="website" />
17    <meta property="og:image" content="/static/og-image.png" />
18    <meta name="twitter:card" content="summary_large_image" />
19    <meta name="twitter:image" content="/static/og-image.png" />
20
21<script>
vendor: 327 bytes, lines 21-25
21(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
22new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
23j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
24'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
25})(window,document,'script','dataLayer','
25GTM-KTPMHMT9
vendor: 3 bytes, line 25
25');
25</script>
25
26
27    
28<title>Chat - Nomados</title>
29<meta name="description" content="Chat interface">
30<style>
31  .chat-tooltip {
32    position: relative;
33  }
34  
35  .chat-tooltip::after {
36    content: attr(data-tooltip);
37    position: absolute;
38    bottom: -130%;
39    left: 0%;
40    transform: translateX(-20%);
41    background-color: #333;
42    color: white;
43    padding: 6px 10px;
44    border-radius: 4px;
45    font-size: 12px;
46    white-space: nowrap;
47    opacity: 0;
48    visibility: hidden;
49    transition: opacity 0.3s, visibility 0.3s;
50    z-index: 1000;
51    margin-bottom: 6px;
52    pointer-events: none;
53  }
54  
55  .chat-tooltip::before {
56    content: '';
57    position: absolute;
58    bottom: -130%;
59    left: 50%;
60    transform: translateX(-50%);
61    border: 4px solid transparent;
62    border-top-color: #333;
63    opacity: 0;
64    visibility: hidden;
65    transition: opacity 0.3s, visibility 0.3s;
66    z-index: 1000;
67    margin-bottom: 2px;
68    pointer-events: none;
69  }
70
71  @media (max-width: 768px) {
72    .chat-tooltip::after {
73      bottom: 100%;
74    }
75    .chat-tooltip::before {
76      bottom: 100%;
77    }
78  }
79  
80  .chat-tooltip:hover::after,
81  .chat-tooltip:hover::before {
82    opacity: 1;
83    visibility: visible;
84  }
85
86  /* Mobile destination widget selection (check icon + dark border) */
87  .destination-widget-compact .compact-icon-hover { display: none; }
88  .destination-widget-compact:hover .compact-icon-static { display: none; }
89  .destination-widget-compact:hover .compact-icon-hover { display: block; }
90  .destination-widget-compact.mobile-selected {
91    border-width: 2px !important;
92    border-color: #3B3B3C !important;
93  }
94  .destination-widget-compact .check-overlay {
95    display: none;
96  }
97  .destination-widget-compact.mobile-selected .check-overlay {
98    display: flex;
99  }
100
101  /* Mobile services tracker - active/done states */
102  .tracker-icon-wrap.tracker-active {
103    background: #5DD2C5 !important;
104  }
105  .tracker-icon-wrap.tracker-done {
106    background: #FFAA9C !important;
107  }
108
109  /* Go Pro view open/close animation (slide from right on desktop, bottom-to-top on mobile) */
110  #go-pro-view {
111    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
112  }
113  #go-pro-view.go-pro-view--closed {
114    transform: translateX(100%);
115  }
116  @media (max-width: 767px) {
117    #go-pro-view.go-pro-view--closed {
118      transform: translateY(100%);
119    }
120  }
121
122  .go-pro-close {
123    z-index: 2002;
124    background: #ffffff;
125    border: 1px solid #e5e7eb;
126    border-radius: 50%;
127    width: 40px;
128    height: 40px;
129    display: flex;
130    align-items: center;
131    justify-content: center;
132    cursor: pointer;
133    transition: all 0.2s ease;
134    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
135  }
136
137  .go-pro-close:hover {
138    background: #f3f4f6;
139    transform: scale(1.1);
140  }
141  
142  .go-pro-close img {
143    width: 20px;
144    height: 20px;
145    object-fit: contain;
146  }
147  
148  /* On mobile, keep close button at default position */
149  @media (max-width: 1080px) {
150    .go-pro-close {
151      right: 16px;
152    }
153  }
154</style>
155
156    
157    <!-- Google Maps API - Load globally for panel views -->
158    
159    
159<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyA6Tf1MrQwlFTTe1nTFFJAuhqGNQ05CzZk&libraries=places"></script>
159
160    
161
162    <!-- Global CSS -->
163    <link rel="stylesheet" href="/static/css/global.css" />
164
165    <style>
166      html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
167      #page, .page-container { width: 100%; height: 100%; max-width: none; }
168      
169      /* Sidebar styles are now handled by Tailwind CSS classes */
170      /* Active button state - managed by JavaScript */
171      .sidebar-btn-active {
172        font-weight: 600;
173      }
174      
175      /* Nomados 2.0 Right Panel Styles - No Modal, Direct Panel */
176      /* Match the right container width exactly: md:w-5/10 xl:w-3/5 */
177      .nomados-panel {
178        position: fixed;
179        top: 0;
180        /* Position to match the right container exactly */
181        /* Chat container: md:w-5/10 (50%) or xl:w-2/5 (40%) */
182        /* Panel starts after chat, same width as right container: md:w-5/10 (50%) or xl:w-3/5 (60%) */
183        /* On mobile: no sidebar, so left: 0 */
184        left: 0;
185        right: 0;
186        width: 100%;
187        height: 100%;
188        z-index: 2000;
189        pointer-events: none;
190      }
191      
192      /* Desktop: account for sidebar */
193      @media (min-width: 768px) {
194        .nomados-panel {
195          left: 80px;
196          width: calc(100% - 80px);
197        }
198      }
199      
200      .nomados-panel.open {
201        pointer-events: all;
202      }
203      
204      .nomados-panel-content {
205        position: absolute;
206        top: 0;
207        right: 0;
208        width: 100%;
209        height: 100%;
210        background: #ffffff;
211        transform: translateX(100%);
212        transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
213        display: flex;
214        flex-direction: column;
215        pointer-events: all;
216        overflow: hidden;
217      }
218      
219      /* Mobile: bottom to top animation */
220      @media (max-width: 767px) {
221        .nomados-panel-content {
222          transform: translateY(100%);
223        }
224      }
225      
226      /* Panel Header with Back Button */
227      .nomados-panel-header {
228        display: none;
229        align-items: center;
230        gap: 12px;
231        padding: 16px 24px;
232        
233        background: #ffffff;
234        flex-shrink: 0;
235        z-index: 10;
236        max-width: 1080px;
237        margin-left: auto;
238        margin-right: auto;
239        width: 100%;
240        @media (max-width: 768px) {
241          padding: 16px;
242        }
243      }
244      
245      .nomados-panel-header.visible {
246        display: flex;
247      }
248      
249      .nomados-panel-back-btn {
250        display: flex;
251        align-items: center;
252        justify-content: center;
253        width: 40px;
254        height: 40px;
255        border-radius: 50%;
256        border: 1px solid #e5e7eb;
257        background: #ffffff;
258        cursor: pointer;
259        transition: all 0.2s ease;
260        flex-shrink: 0;
261      }
262      
263      .nomados-panel-back-btn:hover {
264        background: #f3f4f6;
265        transform: translateX(-2px);
266      }
267      
268      .nomados-panel-back-btn img {
269        width: 20px;
270        height: 20px;
271        object-fit: contain;
272      }
273      
274      .nomados-panel-header-title {
275        font-size: 18px;
276        font-weight: 600;
277        color: #1f2937;
278        flex: 1;
279      }
280      
281      /* Mobile-specific panel header classes */
282      @media (max-width: 767px) {
283        .nomados-panel-header.mobile-journey-detail {
284          position: fixed;
285          background: transparent;
286          margin-top: 4px;
287        }
288        
289        #nomados-panel-destination-detail > :first-child > div {
290          position: fixed;
291          background: transparent;
292          margin-top: 4px;
293        }
294      }
295      
296      .nomados-panel.open .nomados-panel-content {
297        transform: translateX(0);
298      }
299      
300      /* Mobile: slide up from bottom when open */
301      @media (max-width: 767px) {
302        .nomados-panel.open .nomados-panel-content {
303          transform: translateY(0);
304        }
305      }
306      
307      /* Match responsive widths exactly like the right container: md:w-5/10 xl:w-3/5 */
308      /* The right container in chat.html is: md:w-5/10 xl:w-3/5 */
309      /* Main container starts at 80px (sidebar), chat takes md:50% xl:40%, panel takes md:50% xl:60% */
310      @media (min-width: 768px) {
311        .nomados-panel {
312          left: 50vw;
313          width: 50vw;
314        }
315      }
316      
317      @media (min-width: 1280px) {
318        .nomados-panel {
319          left: 40vw;
320          width: 60vw;
321        }
322      }
323      
324      @media (max-width: 767px) {
325        .nomados-panel {
326          left: 0; /* No sidebar on mobile */
327          width: 100%;
328        }
329      }
330      
331      /* Loader Styles - overlay so it's visible on top of panel content */
332      .nomados-panel-body {
333        position: relative;
334      }
335      .nomados-panel-loader {
336        position: absolute;
337        top: 0;
338        left: 0;
339        right: 0;
340        bottom: 0;
341        z-index: 100;
342        display: flex;
343        flex-direction: column;
344        align-items: center;
345        justify-content: center;
346        min-height: 200px;
347        gap: 16px;
348        background: rgba(255, 255, 255, 0.96);
349      }
350      
351      .nomados-panel-loader-spinner {
352        width: 48px;
353        height: 48px;
354        border: 4px solid #f3f4f6;
355        border-top-color: #3b82f6;
356        border-radius: 50%;
357        animation: nomados-spin 1s linear infinite;
358      }
359      
360      .nomados-panel-loader-text {
361        color: #6b7280;
362        font-size: 14px;
363        font-weight: 500;
364      }
365      
366      @keyframes nomados-spin {
367        to {
368          transform: rotate(360deg);
369        }
370      }
371      
372      .nomados-panel-close {
373        position: absolute;
374        top: 16px;
375        right: max(16px, calc((100% - 1080px) / 2 + 16px));
376        z-index: 2002;
377        background: #ffffff;
378        border: 1px solid #e5e7eb;
379        border-radius: 50%;
380        width: 40px;
381        height: 40px;
382        display: flex;
383        align-items: center;
384        justify-content: center;
385        cursor: pointer;
386        transition: all 0.2s ease;
387        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
388      }
389      
390      .nomados-panel-close:hover {
391        background: #f3f4f6;
392        transform: scale(1.1);
393      }
394      
395      .nomados-panel-close img {
396        width: 20px;
397        height: 20px;
398        object-fit: contain;
399      }
400      
401      /* On mobile, keep close button at default position */
402      @media (max-width: 1080px) {
403        .nomados-panel-close {
404          right: 16px;
405        }
406      }
407      
408      .nomados-panel-body {
409        flex: 1;
410        overflow-y: auto;
411        overflow-x: hidden;
412        padding: 0;
413        -webkit-overflow-scrolling: touch;
414        position: relative;
415      }
416      
417      /* Mobile Responsive */
418      @media (max-width: 768px) {
419        .nomados-sidebar {
420          width: 70px;
421        }
422        
423        .nomados-panel-content {
424          max-width: 100%;
425        }
426        
427        .nomados-panel-body {
428          padding: 16px;
429        }
430        
431        /* Mobile-specific panel body classes for journey-detail and destination-detail */
432        .nomados-panel-body.mobile-journey-detail-body {
433          padding: 0;
434        }
435        
436        .nomados-panel-body.mobile-destination-detail-body {
437          padding: 0;
438        }
439      }
440      
441      /* Smooth transitions for panel content */
442      .nomados-panel-view {
443        animation: panelContentFadeIn 0.3s ease;
444      }
445      
446      @keyframes panelContentFadeIn {
447        from {
448          opacity: 0;
449          transform: translateY(10px);
450        }
451        to {
452          opacity: 1;
453          transform: translateY(0);
454        }
455      }
456      
457      /* Ensure panel views take full height and are properly isolated */
458      .nomados-panel-view {
459        min-height: 100%;
460        width: 100%;
461        max-width: 1080px;
462        margin-left: auto;
463        margin-right: auto;
464      }
465      
466      /* Ensure only one view is visible at a time */
467      .nomados-panel-view:not([style*="display: block"]) {
468        display: none !important;
469      }
470      
471      /* Isolate view content */
472      .nomados-panel-view > * {
473        width: 100%;
474      }
475    </style>
476
477    
478     
479  </head>
480
481  <body id="page" class="h-screen overflow-hidden">
482    <!-- Toast notification container -->
483    <div id="toast-container" class="fixed top-4 right-4 z-[99999] flex flex-col gap-2 pointer-events-none max-w-sm w-full"></div>
484
485    <!-- Cancel subscription confirmation modal -->
486    <div
487      id="cancelSubModal"
488      class="fixed inset-0 z-[99997] hidden items-center justify-center bg-black/50 opacity-0 transition-opacity duration-200 ease-out"
489      style="backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);"
490      aria-modal="true" role="dialog" aria-labelledby="cancelSubModalTitle"
491    >
492      <div class="relative bg-white rounded-2xl shadow-xl border border-gray-100 w-full max-w-sm mx-4 p-6 md:p-8 transform transition-all duration-200" onclick="event.stopPropagation()">
493        <!-- Icon -->
494        <div class="flex items-center justify-center w-12 h-12 rounded-full bg-red-50 mx-auto mb-4">
495          <svg class="w-6 h-6 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
496            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
497          </svg>
498        </div>
499        <!-- Title -->
500        <h3 id="cancelSubModalTitle" class="text-lg font-bold text-[#3B3B3C] text-center mb-2">Cancel your Pro subscription?</h3>
501        <!-- Body -->
502        <p class="text-sm text-gray-500 text-center mb-6">
503          Your Pro access will continue until the end of your current billing period. You won't be charged again after that.
504        </p>
505        <!-- Actions -->
506        <div class="flex flex-col gap-3">
507          <button
508            type="button"
509            onclick="closeCancelSubModal()"
510            class="w-full py-3 rounded-xl bg-[#3B3B3C] text-white font-semibold text-sm hover:bg-[#2d2d2e] transition-colors"
511          >
512            Keep my Pro
513          </button>
514          <button
515            type="button"
516            id="cancelSubConfirmBtn"
517            onclick="confirmCancelSubscription()"
518            class="w-full py-3 rounded-xl border border-red-300 text-red-600 font-semibold text-sm hover:bg-red-50 transition-colors"
519          >
520            Yes, cancel subscription
521          </button>
522        </div>
523      </div>
524    </div>
525    
525<script>
526      window.showNotification = function(message, type) {
527        type = type || 'error';
528        var container = document.getElementById('toast-container');
529        if (!container) return;
530        var colorMap = { error: 'bg-red-600', success: 'bg-[#14B8A6]', info: 'bg-[#3B3B3C]' };
531        var bg = colorMap[type] || colorMap.info;
532        var toast = document.createElement('div');
533        toast.className = 'pointer-events-auto flex items-start gap-3 px-4 py-3 rounded-xl shadow-lg text-white text-sm opacity-0 translate-y-1 transition-all duration-200 ' + bg;
534        var safe = (message || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');
535        toast.innerHTML = '<span class="flex-1">' + safe + '</span><button onclick="this.parentElement.remove()" class="shrink-0 text-white/70 hover:text-white text-lg leading-none font-bold">&times;</button>';
536        container.appendChild(toast);
537        requestAnimationFrame(function() { toast.classList.remove('opacity-0','translate-y-1'); });
538        setTimeout(function() {
539          toast.classList.add('opacity-0','translate-y-1');
540          setTimeout(function() { toast.remove(); }, 200);
541        }, 5000);
542      };
543    </script>
543
544    <div class="page-container text-gray-800 h-full">
545      <!-- Main Content Container -->
546      <div class="w-full h-full flex relative">
547        <!-- Nomados 2.0 Sidebar - Desktop Only -->
548        
549           
550
551
552<style>
553  .dropdown-menu {
554    border: 1px solid #BBB;
555    background: rgba(255, 255, 255, 0.95);
556    box-shadow: 0 6px 9px 2px rgba(0, 0, 0, 0.09);
557  }
558
559  .dropdown-button {
560    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.09) inset;
561  }
562  
563  .dropdown-item {
564    padding: 8px 16px;
565    cursor: pointer;
566    transition: background-color 0.2s;
567    font-size: 14px;
568  }
569  
570  .dropdown-item:hover {
571    background-color: #f5f5f5;
572  }
573  
574  .dropdown-item:first-child {
575    border-radius: 16px 16px 0 0;
576  }
577  
578  .dropdown-item:last-child {
579    border-radius: 0 0 16px 16px;
580  }
581</style>
582
583<!-- Nomados 2.0 Mobile Header -->
584<header class="fixed z-[2500] md:hidden bg-transparent top-0 left-0 w-full">
585  <div class="w-full px-2 py-1 mobile-header-container bg-white border-0">
586    <div class="flex items-center gap-5">
587      <button type="button" class="mobile-menu-button flex items-center" onclick="openMobileSidebar()">
588        <img src="/static/icons/menu.svg" alt="Menu" draggable="false" />
589      </button>
590
591      <!-- Logo - shown on chat page and journey list page -->
592      
593      <button id="mobile-header-logo" type="button" class="flex items-center" onclick="handleLogoClick()">
594        <img src="/static/icons/nomados_logo.svg" alt="Nomados" draggable="false" class="h-4" />
595      </button>
596
597      <!-- Dynamic view title - shown when a panel view is open (Market, Profile, Memberships) -->
598      <div id="mobile-header-view-title" class="text-lg font-semibold text-[#3b3b3c] hidden"></div>
599
600      <!-- Launch view tab dropdown - shown next to logo when panel is on journey view -->
601      <div id="mobile-header-journey-dropdown" class="relative hidden" style="min-width: 140px;">
602        <button
603          type="button"
604          class="dropdown-button flex items-center justify-between w-full px-4 py-1.5 bg-white rounded-full border border-[#D9D9D9] text-sm text-dark"
605          onclick="togglePanelJourneyDropdown()"
606        >
607          <span id="panel-selected-tab">Journeys</span>
608          <img id="panel-dropdown-arrow" src="/static/icons/arrow_drop_down.svg" alt="" draggable="false" class="w-[10px] h-[5px] ml-2 transition-transform duration-200" />
609        </button>
610        <div id="panel-journey-dropdown" class="dropdown-menu absolute w-full translate-y-2 left-0 rounded-2xl hidden z-10">
611          <div class="dropdown-item" onclick="selectPanelJourneyTab('journeys')">Journeys</div>
612          <div class="dropdown-item" onclick="selectPanelJourneyTab('favorites')">Favorites</div>
613          <div class="dropdown-item" onclick="selectPanelJourneyTab('addons')">Featured Add-Ons</div>
614        </div>
615      </div>
616      
617
618      <!-- Destination List Page - Results Count and Table View -->
619      
620      <!-- Market Page - Heading Only -->
621      
622    </div>
623  </div>
624</header>
625
626
627
628          
629
630
631<style>
632  /* Prevent sidebar flash during page transitions */
633  #mobileSidebar {
634    visibility: hidden !important;
635  }
636  
637  /* Only show sidebar when explicitly opened or after proper initialization */
638  #mobileSidebar.sidebar-ready {
639    visibility: visible !important;
640  }
641</style>
642
643
644
645<!-- Nomados 2.0 Sidebar - Hidden on mobile, visible on desktop -->
646<div id="nomados-sidebar" class="hidden md:flex h-screen w-16 z-[1000] flex-col py-6 bg-[#F7F7F7]">
647  <nav class="flex flex-col gap-0 flex-1 w-full h-full justify-between items-center">
648    <div class="w-full">
649      <!-- Active Miles Button (Top) -->
650      <button
651        id="sidebar-logo-btn"
652        class="w-full flex flex-col items-center justify-center gap-2 cursor-pointer mb-12 hover:scale-110 transition-transform duration-200 disabled:cursor-not-allowed disabled:hover:scale-100"
653        data-panel-view="logo"
654        aria-label="Logo"
655        onclick="handleLogoClick()"
656      >
657        <img
658          src="/static/icons/logo.svg"
659          alt="Logo"
660          draggable="false"
661          class=""
662        />
663      </button>
664
665       <div class="flex flex-col gap-4 w-full">
666         <!-- Miles Button -->
667         <button
668           id="sidebar-miles-btn"
669           class="sidebar-btn flex flex-col items-center justify-center gap-2 cursor-pointer transition-all duration-200 w-full min-h-[80px] hover:bg-black/5 active"
670           data-panel-view="miles"
671           onclick="closeNomadosPanel()"
672           aria-label="Miles"
673         >
674           <div class="flex items-center justify-center">
675             <img
676               src="/static/icons/miles_filled.svg"
677               alt="Miles"
678               draggable="false"
679               class="w-full h-full object-contain miles-icon-filled"
680             />
681             <img
682               src="/static/icons/miles_blank.svg"
683               alt="Miles"
684               draggable="false"
685               class="w-full h-full object-contain miles-icon-blank hidden"
686             />
687           </div>
688           <span class="sidebar-label text-xs font-bold text-center leading-tight text-[#3B3B3C] miles-text">Miles</span>
689         </button>
690
691         <!-- Launch Button -->
692         <button
693           id="sidebar-launch-btn"
694           class="sidebar-btn flex flex-col items-center justify-center gap-2 cursor-pointer transition-all duration-200 w-full min-h-[80px] hover:bg-black/5"
695           data-panel-view="journey"
696           onclick="openNomadosPanel('journey')"
697           aria-label="Launch - Journey List"
698         >
699           <div class="flex items-center justify-center">
700             <img
701               src="/static/icons/launch_filled.svg"
702               alt="Launch"
703               draggable="false"
704               class="w-full h-full object-contain launch-icon-filled hidden"
705             />
706             <img
707               src="/static/icons/launch_blank.svg"
708               alt="Launch"
709               draggable="false"
710               class="w-full h-full object-contain launch-icon-blank"
711             />
712           </div>
713           <span class="sidebar-label text-xs font-normal text-center leading-tight text-[#3B3B3C] launch-text">Launch</span>
714         </button>
715
716         <!-- Market Button -->
717         <button
718           id="sidebar-market-btn"
719           class="sidebar-btn flex flex-col items-center justify-center gap-2 cursor-pointer transition-all duration-200 w-full min-h-[80px] hover:bg-black/5"
720           data-panel-view="market"
721           onclick="openNomadosPanel('market')"
722           aria-label="Market"
723         >
724           <div class="flex items-center justify-center">
725             <img
726               src="/static/icons/market_filled.svg"
727               alt="Market"
728               draggable="false"
729               class="w-full h-full object-contain market-icon-filled hidden"
730             />
731             <img
732               src="/static/icons/market_blank.svg"
733               alt="Market"
734               draggable="false"
735               class="w-full h-full object-contain market-icon-blank"
736             />
737           </div>
738           <span class="sidebar-label text-xs font-normal text-center leading-tight text-[#3B3B3C] market-text">Market</span>
739         </button>
740       </div>
741    </div>
742
743     <!-- Profile Button + Popup (desktop only when authenticated) -->
744     <div class="relative w-full flex flex-col items-center">
745       <button
746         id="sidebar-profile-btn"
747         class="sidebar-btn flex flex-col items-center justify-center gap-2 cursor-pointer transition-all duration-200 w-full min-h-[80px] hover:bg-black/5"
748         data-panel-view="profile"
749         onclick="window.location.href = '/chat/login/';"
750         aria-label="Profile"
751         aria-expanded="false"
752         aria-haspopup="true"
753       >
754         <div class="flex items-center justify-center">
755          <img
756            src="/static/icons/profile_filled.svg"
757            alt="Profile"
758            draggable="false"
759            class="w-full h-full object-contain profile-icon-filled hidden"
760          />
761          <img
762            src="/static/icons/login_dark.svg"
763            alt="Profile"
764            draggable="false"
765            class="w-full h-full object-contain profile-icon-blank"
766          />
767         </div>
768         <span class="sidebar-label text-xs font-normal text-center leading-tight text-[#3B3B3C] profile-text !font-bold">Log In</span>
769       </button>
770
771       
772     </div>
773  </nav>
774</div>
775
776<!-- Mobile Sidebar Overlay -->
777<div id="sidebarOverlay" class="fixed inset-0 z-[2999] bg-black/30 hidden lg:hidden transition-opacity duration-500"></div>
778
779<!-- Mobile Sidebar -->
780<div id="mobileSidebar" class="fixed top-0 left-0 h-full w-80 bg-[#3b3b3c] text-white z-[3000] transform -translate-x-[calc(100%+30px)] transition-transform duration-500 ease-in-out lg:hidden flex flex-col">
781  
782  <!-- Header with Logo and Close Button -->
783  <div class="flex items-center justify-center p-4 relative mt-8">
784    <button id="mobile-sidebar-logo-btn" class="cursor-pointer hover:scale-110 transition-transform duration-200 disabled:cursor-not-allowed disabled:hover:scale-100" onclick="handleLogoClick()">
785      <img
786        src="/static/logo-white.svg"
787        alt="Nomados"
788        draggable="false"
789        class="sidebar-logo"
790      />
791    </button>
792    <button id="closeSidebar" class="absolute right-4 top-1/2 -translate-y-1/2">
793      <img
794        src="/static/icons/close-white.svg"
795        alt="Close"
796        draggable="false"
797      />
798    </button>
799  </div>
800
801  <!-- Scrollable Content Area -->
802  <div class="flex flex-col justify-between w-full h-full overflow-y-auto scrollbar-hide">
803    <!-- Navigation Menu -->
804    <nav class="py-4">
805      <button
806        class="w-full flex items-center gap-3 px-6 py-3 hover:bg-[#5d5d5d] transition-colors"
807        onclick="handleMilesClick()"
808      >
809        <div class="w-6">
810          <img
811            src="/static/icons/miles_light.svg"
812            alt="Miles"
813            draggable="false"
814          />
815        </div>
816        <span class="text-base font-medium">Miles</span>
817      </button>
818
819      <buttton
820        class="w-full flex items-center gap-3 px-6 py-3 hover:bg-[#5d5d5d] transition-colors"
821        data-panel-view="journey"
822        onclick="handleLaunchClick()"
823        aria-label="Launch - Journey List"
824      >
825        <div class="w-6">
826          <img
827          src="/static/icons/launch.svg"
828          alt="Launch"
829          draggable="false"
830          />
831        </div>
832        <span class="text-base font-medium">Launch</span>
833      </buttton>
834
835      
836
837      <button
838        class="w-full flex items-center gap-3 px-6 py-3 hover:bg-[#5d5d5d] transition-colors"
839        data-panel-view="profile"
840        onclick="window.location.href = '/chat/login/';"
841        aria-label="Profile"
842      >
843        <div class="w-6">
844          <img
845          src="/static/icons/profile_empty.svg"
846          alt="My Profile"
847          draggable="false"
848          />
849        </div>
850        <span class="text-base font-medium">Profile</span>
851      </button>
852
853      
854      <a
855        href="/chat/login/"
856        class="w-full flex items-center gap-3 px-6 py-3 hover:bg-[#5d5d5d] transition-colors"
857      >
858        <div class="w-6">
859          <img
860          src="/static/icons/login_white.svg"
861          alt="Log In"
862          draggable="false"
863          />
864        </div>
865        <span class="text-base font-medium">Log In</span>
866      </a>
867      
868    </nav>
869
870
871
872    <!-- Footer Links Section -->
873    <div class="px-6 pb-6">
874      <!-- Divider -->
875      <div class="border-t border-[#979797] my-4"></div>
876      <div class="flex w-full gap-6">
877        <!-- About Column -->
878        <div class="w-1/2 flex flex-col gap-6">
879          <div>
880            <h3 class="text-sm font-bold mb-3">About</h3>
881            <div class="flex flex-col gap-2">
882              <a href="/#" class="text-sm text-white hover:text-gray-300">
883                Company
884              </a>
885              <a href="/#" class="text-sm text-white hover:text-gray-300">
886                Community
887              </a>
888            </div>
889          </div>
890
891          <!-- Resources Column -->
892          <div class="w-1/2">
893            <h3 class="text-sm font-bold mb-3">Resources</h3>
894            <div class="flex flex-col gap-2">
895              <a href="/#" class="text-sm text-white hover:text-gray-300">
896                Articles
897              </a>
898              <a href="/feedback/" class="text-sm text-white hover:text-gray-300">
899                Support
900              </a>
901            </div>
902          </div>
903        </div>
904
905        <!-- FAQs Column -->
906        <div>
907          <h3 class="text-sm font-bold mb-3">FAQs</h3>
908          <div class="flex flex-col gap-2">
909            <a href="/#" class="text-sm text-white hover:text-gray-300">
910              Booking
911            </a>
912            <a href="/#" class="text-sm text-white hover:text-gray-300">
913              HelloLounge
914            </a>
915            <a href="/#" class="text-sm text-white hover:text-gray-300">
916              Resort Residences
917            </a>
918            <a href="/#" class="text-sm text-white hover:text-gray-300">
919              Membership
920            </a>
921          </div>
922        </div>
923      </div>
924
925      <!-- Terms and Privacy -->
926      <div class="mt-6 flex items-center justify-center gap-3 text-xs text-white font-light">
927        <a href="https://nomados.ai/terms-and-conditions/" target="_blank" class="hover:text-gray-300 transition-colors cursor-pointer">
928          Terms and Conditions
929        </a>
930        <span>|</span>
931        <a href="https://nomados.ai/privacy-policy/" target="_blank" class="hover:text-gray-300 transition-colors cursor-pointer">
932          Privacy Policy
933        </a>
934      </div>
935
936      <!-- Social Media Icons -->
937      <div class="mt-4 flex items-center justify-center gap-4">
938        <a href="https://facebook.com/nomadosai" target="_blank" rel="noopener noreferrer" class="w-10 h-10 bg-white rounded-lg flex items-center justify-center hover:bg-gray-200 transition-colors">
939          <img
940            src="/static/icons/facebook.svg"
941            alt="Facebook"
942            draggable="false"
943            class="w-5 h-5"
944          />
945        </a>
946        <a href="https://twitter.com/nomadosai" target="_blank" rel="noopener noreferrer" class="w-10 h-10 bg-white rounded-lg flex items-center justify-center hover:bg-gray-200 transition-colors">
947          <svg class="w-4 h-4 text-black" viewBox="0 0 24 24" fill="currentColor">
948            <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
949          </svg>
950        </a>
951        <a href="https://instagram.com/nomadosai" target="_blank" rel="noopener noreferrer" class="w-10 h-10 bg-white rounded-lg flex items-center justify-center hover:bg-gray-200 transition-colors">
952          <svg class="w-5 h-5 text-black" viewBox="0 0 24 24" fill="currentColor">
953            <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
954          </svg>
955        </a>
956      </div>
957
958      <!-- Copyright -->
959      <div class="mt-4 text-center text-xs text-gray-300 font-light">
960        © 2026 NomadosAI. All Rights Reserved
961      </div>
962    </div>
963  </div>
964
965  <!-- User Profile Section at Bottom -->
966  <div class="px-6 py-4">
967    <!-- Divider -->
968    <div class="border-t border-[#979797] my-4"></div>
969    <div class="flex items-center gap-3">
970      <div class="w-12 h-12 rounded-full bg-[#bbb] flex items-center justify-center overflow-hidden">
971        
972        <div class="rounded-full avatar p-0.5 hover:-translate-y-0.5 ease-in-out duration-200 transition-all">
973          <img
974            src="/static/icons/profile-default.svg"
975            alt="Wanderer"
976            draggable="false"
977            class="w-10 h-10 rounded-full"
978          />
979        </div>
980        
981      </div>
982      <div class="flex-1">
983        
984        <p class="text-sm font-semibold">Wanderer</p>
985        <p id="mobile-sidebar-membership-text" class="text-xs text-[#bbb]">No membership data</p>
986        
987      </div>
988    </div>
989  </div>
990</div>
991
992
993<!-- Sidebar JavaScript -->
994<script>
995  function initSidebar() {
996    const sidebar = document.getElementById('mobileSidebar');
997    const overlay = document.getElementById('sidebarOverlay');
998    const menuButton = document.querySelector('.mobile-menu-button');
999    const closeButton = document.getElementById('closeSidebar');
1000
1001    // Ensure sidebar is closed on page load/initialization
1002    function ensureSidebarClosed() {
1003      if (sidebar) {
1004        sidebar.classList.add('-translate-x-[calc(100%+30px)]');
1005        // Make visible after ensuring transform is applied (prevents flash)
1006        // Use double requestAnimationFrame to ensure CSS is fully applied
1007        requestAnimationFrame(() => {
1008          requestAnimationFrame(() => {
1009            sidebar.classList.add('sidebar-ready');
1010          });
1011        });
1012      }
1013      if (overlay) {
1014        overlay.classList.add('hidden');
1015      }
1016      document.body.style.overflow = '';
1017    }
1018
1019    window.openMobileSidebar = function() {
1020      if (sidebar) {
1021        sidebar.classList.add('sidebar-ready');
1022        sidebar.classList.remove('-translate-x-[calc(100%+30px)]');
1023      }
1024      if (overlay) {
1025        overlay.classList.remove('hidden');
1026      }
1027      document.body.style.overflow = 'hidden';
1028    }
1029
1030    window.closeSidebar = function() {
1031      if (sidebar) {
1032        sidebar.classList.add('-translate-x-[calc(100%+30px)]');
1033      }
1034      if (overlay) {
1035        overlay.classList.add('hidden');
1036      }
1037      document.body.style.overflow = '';
1038    }
1039
1040    // Ensure sidebar is closed on initialization
1041    ensureSidebarClosed();
1042
1043    // Open sidebar when menu button is clicked
1044    if (menuButton) {
1045      menuButton.addEventListener('click', function(e) {
1046        e.preventDefault();
1047        e.stopPropagation();
1048        openMobileSidebar();
1049      });
1050    } else {
1051      console.warn('Menu button not found!');
1052    }
1053
1054    // Close sidebar when close button is clicked
1055    if (closeButton) {
1056      closeButton.addEventListener('click', closeSidebar);
1057    }
1058
1059    // Close sidebar when overlay is clicked
1060    if (overlay) {
1061      overlay.addEventListener('click', closeSidebar);
1062    }
1063
1064    // Close sidebar when clicking on navigation links
1065    if (sidebar) {
1066      const navLinks = sidebar.querySelectorAll('nav a');
1067      navLinks.forEach(link => {
1068        link.addEventListener('click', closeSidebar);
1069      });
1070    }
1071  }
1072
1073  function handleMilesClick() {
1074    // Close Go Pro view if open
1075    if (typeof closeGoProView === 'function') {
1076      var goProEl = document.getElementById('go-pro-view');
1077      if (goProEl && !goProEl.classList.contains('hidden')) closeGoProView();
1078    }
1079    closeNomadosPanel();
1080    closeSidebar();
1081  }
1082
1083  function handleLaunchClick() {
1084    openNomadosPanel('journey');
1085    closeSidebar();
1086  }
1087
1088  function handleMarketClick() {
1089    openNomadosPanel('market');
1090    closeSidebar();
1091  }
1092
1093  function handleProfileClick() {
1094    openNomadosPanel('profile');
1095    closeSidebar();
1096  }
1097
1098  function handleUpgradeClick() {
1099    closeSidebar();
1100    if (typeof openGoProView === 'function') openGoProView();
1101  }
1102
1103  // Desktop profile popup: toggle and close on outside click
1104  window.toggleProfilePopup = function(event) {
1105    if (event) event.stopPropagation();
1106    const popup = document.getElementById('profile-popup');
1107    const btn = document.getElementById('sidebar-profile-btn');
1108    if (!popup || !btn) return;
1109    const isHidden = popup.classList.contains('hidden');
1110    popup.classList.toggle('hidden', !isHidden);
1111    popup.classList.toggle('flex', isHidden);
1112    btn.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
1113    if (isHidden) {
1114      document.addEventListener('click', closeProfilePopupOnClickOutside);
1115    } else {
1116      document.removeEventListener('click', closeProfilePopupOnClickOutside);
1117    }
1118  };
1119
1120  function closeProfilePopupOnClickOutside(e) {
1121    const popup = document.getElementById('profile-popup');
1122    const btn = document.getElementById('sidebar-profile-btn');
1123    if (!popup || !btn) return;
1124    if (popup.contains(e.target) || btn.contains(e.target)) return;
1125    popup.classList.add('hidden');
1126    popup.classList.remove('flex');
1127    if (btn) btn.setAttribute('aria-expanded', 'false');
1128    document.removeEventListener('click', closeProfilePopupOnClickOutside);
1129  }
1130
1131  window.closeProfilePopupAndOpenPanel = function(view) {
1132    const popup = document.getElementById('profile-popup');
1133    const btn = document.getElementById('sidebar-profile-btn');
1134    if (popup) {
1135      popup.classList.add('hidden');
1136      popup.classList.remove('flex');
1137    }
1138    if (btn) btn.setAttribute('aria-expanded', 'false');
1139    document.removeEventListener('click', closeProfilePopupOnClickOutside);
1140    if (typeof openNomadosPanel === 'function') openNomadosPanel(view);
1141  };
1142
1143  window.closeProfilePopupAndOpenGoProView = function() {
1144    var popup = document.getElementById('profile-popup');
1145    var btn = document.getElementById('sidebar-profile-btn');
1146    if (popup) {
1147      popup.classList.add('hidden');
1148      popup.classList.remove('flex');
1149    }
1150    if (btn) btn.setAttribute('aria-expanded', 'false');
1151    document.removeEventListener('click', closeProfilePopupOnClickOutside);
1152    if (typeof openGoProView === 'function') openGoProView();
1153  };
1154
1155
1156  function handleLogoClick() {
1157    // Refresh chat - works for both desktop and mobile
1158    if (typeof window.refreshChat === 'function') {
1159      window.refreshChat();
1160    } else if (typeof refreshChat === 'function') {
1161      refreshChat();
1162    }
1163    
1164    // Close any open panels
1165    if (typeof closeNomadosPanel === 'function') {
1166      closeNomadosPanel();
1167    }
1168    
1169    // Close mobile sidebar if open
1170    if (typeof closeSidebar === 'function') {
1171      closeSidebar();
1172    }
1173  }
1174
1175  // Initialize when DOM is ready
1176  if (document.readyState === 'loading') {
1177    document.addEventListener('DOMContentLoaded', initSidebar);
1178  } else {
1179    initSidebar();
1180  }
1181
1182
1183</script>
1183
1184        
1185        
1186<div class="relative w-full h-full flex flex-1 min-w-0" id="chat-main-area-wrapper">
1187<div class="w-full h-full flex main-container">
1188  
1189  <!-- Chat Container - 2/5 width (desktop) / Fixed overlay (mobile) -->
1190  <div id="chat-container" class="md:w-[calc(50%-30px)] xl:w-[calc(40%-30px)] flex-shrink-0 bg-white mobile-chat-overlay expanded">
1191    <div class="h-full max-w-[960px] mx-auto">
1192      <!-- HTMX Chat Widget -->
1193      <div id="chat-widget" class="chat-widget p-4 pt-11 md:pt-4 relative flex flex-col h-full">
1194          <!-- Mobile Services Tracker Bar (shown during essentials wizard) -->
1195          <div id="mobile-services-tracker" class="md:hidden flex-shrink-0" style="display: none;">
1196            <div class="flex items-center justify-around gap-6 py-2 pb-3 border-b border-[#EEE]">
1197              <div class="flex flex-col items-center gap-1" data-tracker-step="visa">
1198                <div class="w-9 h-9 rounded-full bg-[#BBBBBB] flex items-center justify-center overflow-hidden tracker-icon-wrap">
1199                  <img id="tracker-icon-visa" src="/static/icons/visa.svg" alt="Visa" class="w-7 h-7 object-contain" draggable="false" />
1200                </div>
1201                <span class="text-[10px] font-medium text-[#3B3B3C]">Visa</span>
1202              </div>
1203              <div class="flex flex-col items-center gap-1" data-tracker-step="health-insurance">
1204                <div class="w-9 h-9 rounded-full bg-[#BBBBBB] flex items-center justify-center overflow-hidden tracker-icon-wrap">
1205                  <img id="tracker-icon-health" src="/static/icons/healthcare.svg" alt="Insurance" class="w-7 h-7 object-contain" draggable="false" />
1206                </div>
1207                <span class="text-[10px] font-medium text-[#3B3B3C]">Insurance</span>
1208              </div>
1209              <div class="flex flex-col items-center gap-1" data-tracker-step="internet">
1210                <div class="w-9 h-9 rounded-full bg-[#BBBBBB] flex items-center justify-center overflow-hidden tracker-icon-wrap">
1211                  <img id="tracker-icon-internet" src="/static/icons/internet_speed.svg" alt="eSIM" class="w-7 h-7 object-contain" draggable="false" />
1212                </div>
1213                <span class="text-[10px] font-medium text-[#3B3B3C]">eSIM</span>
1214              </div>
1215              <div class="flex flex-col items-center gap-1" data-tracker-step="airport-lounge">
1216                <div class="w-9 h-9 rounded-full bg-[#BBBBBB] flex items-center justify-center overflow-hidden tracker-icon-wrap">
1217                  <img id="tracker-icon-lounge" src="/static/icons/lounge.svg" alt="Lounge" class="w-7 h-7 object-contain" draggable="false" />
1218                </div>
1219                <span class="text-[10px] font-medium text-[#3B3B3C]">Lounge</span>
1220              </div>
1221            </div>
1222          </div>
1223          <!-- Sticky GIF at top -->
1224          <div class="hidden md:flex flex-shrink-0 mb-0 lg:mb-2">
1225            <img
1226              src="/static/chat/idle.gif"
1227              alt="Anthony"
1228              class="w-[60px] h-[90px] lg:w-[80px] lg:h-[120px]"
1229              draggable="false"
1230              id="chatGif"
1231            />
1232          </div>
1233
1234          <!-- Messages Container -->
1235          <div class="chat-messages flex-1 px-1 pb-6 md:pb-14 mb-6" id="chatMessages">
1236            <div class="message bot">
1237              <div class="message-content">
1238                Hey!
1239                👋 <b>Miles </b>here. 4 quick questions - I'll supply your next journey and the vibes.
1240              </div>
1241
1242              <button 
1243                type="button"
1244                id="startFilterWorkflow"
1245                class="w-full flex justify-between items-center mt-4 text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200"
1246                onclick="startDestinationFilter()"
1247              >
1248                <span>Guided Journey</span>
1249                <img src="/static/icons/chevron_right.gif" alt="Arrow Right" class="w-5 h-5" />
1250              </button>
1251              <!-- Sample Questions -->
1252              <div class="sample-questions" id="sampleQuestions" style="display: none;">
1253                <div class="sample-questions-title flex items-center gap-1">
1254                  <img src="/static/icons/psychology.svg" alt="Question Mark" class="" />
1255                  <span class="leading-none">Related questions:</span>
1256                </div>
1257                <div class="sample-questions-list" id="sampleQuestionsList">
1258                  
1259                  <div class="sample-question" onclick="submitQuestion('Which places have a thriving art and music scene for creatives?')">
1260                    Which places have a thriving art and music scene for creatives?
1261                  </div>
1262                  
1263                  <div class="sample-question" onclick="submitQuestion('Can you recommend cities that offer both modern amenities and historic charm?')">
1264                    Can you recommend cities that offer both modern amenities and historic charm?
1265                  </div>
1266                  
1267                  <div class="sample-question" onclick="submitQuestion('Are there any cities with relaxed visa policies for digital nomads?')">
1268                    Are there any cities with relaxed visa policies for digital nomads?
1269                  </div>
1270                  
1271                </div>
1272              </div>
1273            </div>
1274          </div>
1275
1276          <!-- Typing Indicator — absolute so it doesn't steal flex space from chatMessages -->
1277          <div class="typing-indicator" id="typingIndicator">
1278            <div class="flex items-center gap-2">
1279              <div class="typing-dots">
1280                <div class="typing-dot"></div>
1281                <div class="typing-dot"></div>
1282                <div class="typing-dot"></div>
1283              </div>
1284            </div>
1285          </div>
1286
1287          <div class="w-full flex flex-col-reverse md:flex-col">
1288            <!-- Chat Input Form with HTMX -->
1289            <div class="chat-input mb-14 md:mb-0 bg-white p-2 md:p-3 2xl:p-5 mt-2 md:mt-auto transition-all duration-400 lg:duration-0 ease-in-out lg:opacity-100 bottom-0" id="chat-input">
1290              <form 
1291                hx-post="/chat/chat/api/"
1292                hx-target="#chatMessages"
1293                hx-swap="beforeend"
1294                hx-indicator="#typingIndicator"
1295                hx-trigger="submit"
1296                class="chat-input-form" 
1297                id="chatForm"
1298              >
1299                <input type="hidden" name="csrfmiddlewaretoken" value="wW5XRNroFwTKrAvYlp7o0ZIlcpfIHaiR8jbqrSOG95se38pTkp2zbUYkj50vDt3a">
1300                <button
1301                  type="button"
1302                  id="filtersButton"
1303                  class="p-1.5 w-9 h-9 border border-[#3B3B3C] rounded-full transition-all duration-200 hover:-translate-y-0.5"
1304                  onclick="openFiltersModal()"
1305                  aria-label="Open filters"
1306                >
1307                  <img src="/static/icons/filter_dark.svg" alt="Filters" class="" />
1308                </button>
1309                <input
1310                  type="text"
1311                  name="message"
1312                  class="chat-input-field p-2 md:p-3 2xl:p-5 pe-0 2xl:pe-5"
1313                  id="messageInput"
1314                  placeholder="What is on your mind?..."
1315                  autocomplete="off"
1316                  required
1317                />
1318                <button
1319                  type="submit"
1320                  class="send-button w-9 h-9 p-1.5 bg-[#3B3B3C] rounded-full transition-all duration-200 hover:-translate-y-0.5"
1321                  id="sendButton"
1322                >
1323                  <img
1324                    src="/static/icons/add_journey.svg"
1325                    alt="Add journey"
1326                    class=""
1327                  />
1328                </button>
1329              </form>
1330            </div>
1331
1332            <!-- Add-on Buttons -->
1333            <div class="w-full mt-1.5 md:mt-6 pb-0 md:pb-6 flex gap-3 justify-center items-center relative">
1334              <button class="bg-gray-300 text-gray-500 cursor-not-allowed chat-tooltip relative text-xs md:text-sm px-3 py-1.5 rounded-full hover:-translate-y-0.5 transition-all duration-200" onclick=""
1335              data-tooltip="Only available to registered users."
1336              >
1337                eVisa
1338              </button>
1339              <button class="bg-gray-300 text-gray-500 cursor-not-allowed chat-tooltip text-xs md:text-sm px-3 py-1.5 rounded-full hover:-translate-y-0.5 transition-all duration-200" onclick=""
1340              data-tooltip="Only available to registered users."
1341              >
1342                Airport Lounge
1343              </button>
1344              <button class="bg-gray-300 text-gray-500 cursor-not-allowed chat-tooltip text-xs md:text-sm px-3 py-1.5 rounded-full hover:-translate-y-0.5 transition-all duration-200" onclick=""
1345              data-tooltip="Only available to registered users."
1346              >
1347                Insurance
1348              </button>
1349              <button class="bg-gray-300 text-gray-500 cursor-not-allowed chat-tooltip text-xs md:text-sm px-3 py-1.5 rounded-full hover:-translate-y-0.5 transition-all duration-200" onclick=""
1350              data-tooltip="Only available to registered users."
1351              >
1352                eSIM
1353              </button>
1354
1355              <div class="flex md:hidden flex-shrink-0 mb-0 lg:mb-2">
1356                <img
1357                  src="/static/chat/mobile_idle.gif"
1358                  alt="Anthony"
1359                  class="w-[54px] h-[54px] border border-[#D9D9D9] rounded-full"
1360                  draggable="false"
1361                  id="mobileChatGif"
1362                />
1363              </div>
1364            </div>
1365          </div>
1366    </div>
1367  </div>
1368</div>
1369  <!-- View Toggle Buttons -->
1370  <div class="w-fit absolute hidden md:flex top-4 right-4 z-10 gap-2 toggle-buttons rounded-full bg-[#f7f7f7] p-1.5" id="viewButtonsContainer">
1371    <button
1372      id="chatViewButton"
1373      class="view-button hidden"
1374      data-view="chat"
1375      aria-label="Chat view"
1376    >
1377      Chat
1378    </button>
1379    <button
1380      id="mapViewButton"
1381      class="view-button active"
1382      data-view="map"
1383      aria-label="Map view"
1384    >
1385      Map
1386    </button>
1387    <button
1388      id="listViewButton"
1389      class="view-button"
1390      data-view="list"
1391      aria-label="List view"
1392    >
1393      List
1394    </button>
1395    <button
1396      id="tableViewButton"
1397      class="view-button"
1398      data-view="table"
1399      aria-label="Table view"
1400    >
1401      Table
1402    </button>
1403  </div>
1404
1405  <!-- Mobile View Toggle Buttons (shown in chat view when cities exist) -->
1406  <div class="w-fit absolute md:hidden bottom-[205px] md:bottom-auto top-auto md:top-4 right-4 z-10 gap-2 toggle-buttons rounded-full bg-white hidden" id="mobileViewButtonsContainer" style="display: none;">
1407    <button
1408      id="mobileMapViewButton"
1409      class="view-button flex items-center gap-1 !w-16 !bg-[#3B3B3C] !text-white"
1410      data-view="map"
1411      aria-label="Map view"
1412      onclick="switchView('map')"
1413    >
1414      <img
1415        src="/static/icons/map.svg"
1416        alt="Map"
1417        class=""
1418      />
1419      Map
1420    </button>
1421  </div>
1422
1423  <!-- Mobile Back to Chat Button (shown only in map view) -->
1424  <button
1425    id="mobileBackToChatButton"
1426    class="md:hidden fixed bottom-10 left-1/2 -translate-x-1/2 z-[3000] bg-white border border-[#3B3B3C] rounded-full items-center justify-center shadow-lg hover:-translate-y-0.5 transition-all duration-200 hidden ps-1.5 pe-3 py-1.5 gap-2"
1427    style="display: none;"
1428    onclick="goBackToChatFromMap()"
1429    aria-label="Back to chat"
1430  >
1431    <img
1432      src="/static/icons/arrow_left.svg"
1433      alt="Back to chat"
1434      class=""
1435    />
1436    <span class="text-sm">Back to chat</span>
1437  </button>
1438
1439
1440<!-- Right Side Container - 3/5 width (desktop) / Full screen (mobile) -->
1441<div class="md:w-[calc(50%+34px)] xl:w-[calc(60%+34px)] flex-shrink-0 bg-white relative h-full mobile-content-layer">
1442  <!-- Chat Toggle Button (shown when chat is collapsed on mobile) -->
1443  <button id="content-chat-toggle-btn" class="mobile-chat-toggle" onclick="toggleMobileChat()" aria-label="Open chat" style="display: none;">
1444    <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1445      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
1446    </svg>
1447  </button>
1448  <!-- Map Container -->
1449  <div id="map-container" class="h-screen md:h-full map-container">
1450    
1451    <div id="map"></div>
1452    
1453  </div>
1454
1455  <!-- List Container -->
1456  <div id="list-container" class="h-full overflow-y-auto pb-24 mt-16 bg-white max-w-[1080px] mx-auto" style="display: none;">
1457    <div class="p-6" id="citiesListContainer">
1458      <div class="text-center text-gray-500 py-12">
1459        <p class="text-lg font-semibold mb-2">No destinations yet</p>
1460        <p class="text-sm">Start chatting to see recommended destinations</p>
1461      </div>
1462    </div>
1463  </div>
1464
1465  <!-- Table Container -->
1466  <div id="table-container" class="h-full overflow-y-auto pb-24 mt-16 bg-white max-w-[1080px] mx-auto" style="display: none;">
1467    <div class="p-6" id="destinationsTableContainer">
1468      <div class="text-center text-gray-500 py-12">
1469        <p class="text-lg font-semibold mb-2">No destinations yet</p>
1470        <p class="text-sm">Start chatting to see recommended destinations</p>
1471      </div>
1472    </div>
1473  </div>
1474</div>
1475
1476  <!-- Go Pro full-screen view (same layout as panel but fills main area) -->
1477  <div id="go-pro-view" class="absolute inset-0 bg-white z-[100] hidden flex-col overflow-y-auto go-pro-view--closed" aria-hidden="true">
1478    <div class="hidden md:flex justify-end items-center gap-3 px-4 md:px-6 py-4">
1479      <button type="button" onclick="closeGoProView()" class="go-pro-close" aria-label="Back to chat">
1480        <img
1481          src="/static/icons/close-dark.svg"
1482          alt="Close"
1483          draggable="false"
1484        />
1485      </button>
1486    </div>
1487    <div class="flex-1 flex flex-col items-center pt-4 md:py-12 px-4">
1488      
1489<h1 class="text-2xl md:text-4xl font-bold text-[#3B3B3C] text-center mb-2 mt-12 md:mt-0">Go Pro</h1>
1490<p class="text-sm md:text-base text-gray-500 text-center mb-8 md:mb-10">Everything you need to live and work anywhere, seamlessly.</p>
1491
1492<div class="relative border border-[#3B3B3C] rounded-[36px] w-96 p-4 mb-16 md:mb-0">
1493
1494  <!-- Processing loader overlay -->
1495  <div id="go-pro-processing-loader" class="hidden absolute inset-0 z-10 bg-white/80 rounded-[36px] flex flex-col items-center justify-center">
1496    <svg class="animate-spin h-6 w-6 text-[#3B3B3C] mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
1497    <span id="go-pro-loader-text" class="text-sm text-gray-600">Processing...</span>
1498  </div>
1499
1500  <!-- ═══ Pro Status Header (shown when user is Pro) ═══ -->
1501  <div id="go-pro-already-pro" class="w-full max-w-md rounded-full border py-3 px-5 mb-5 text-center
1502  hidden
1503  border-[#3B3B3C]">
1504
1505    <p id="go-pro-already-pro-title" class="text-lg font-bold leading-none pb-1 text-[#3B3B3C]">
1506      You're a <span class="text-[#FF735D]">Pro</span> member
1507    </p>
1508    <p id="go-pro-already-pro-renewal" class="text-xs text-[#777777] leading-none">
1509      Active subscription
1510    </p>
1511  </div>
1512
1513  <!-- ═══ Price Display (shown for all states) ═══ -->
1514
1515  <!-- Price for Pro users (always $9/month, no toggle) -->
1516  <div id="go-pro-price-pro" class="text-center mb-5 hidden">
1517    <span class="text-5xl font-bold text-[#3B3B3C]">$9</span>
1518    <span class="text-gray-500 text-base ml-1">/month</span>
1519  </div>
1520
1521  <!-- CTA block: shown when not Pro (hidden in real time when status_update says pro) -->
1522  <div id="go-pro-cta-block" class="w-full max-w-md ">
1523    <div class="flex rounded-full p-1.5 mb-5 bg-[#F3F3F3]">
1524      <button
1525        type="button"
1526        id="goProToggleMonthly"
1527        onclick="setGoProBilling('monthly')"
1528        class="flex-1 py-3 px-3 text-base font-medium rounded-full transition-colors bg-white text-[#3B3B3C]"
1529      >
1530        Monthly
1531      </button>
1532      <button
1533        type="button"
1534        id="goProToggleAnnual"
1535        onclick="setGoProBilling('annual')"
1536        class="flex-1 py-3 px-3 text-base font-medium rounded-full transition-colors text-[#777777] flex items-center justify-center gap-2"
1537      >
1538        Annual
1539        <span class="bg-[#14B8A6] text-white text-xs font-semibold px-2 py-0.5 rounded-full">Save 45%</span>
1540      </button>
1541    </div>
1542
1543    <div id="goProPriceMonthly" class="go-pro-price-block text-center mb-7">
1544      <span class="text-5xl font-bold text-[#3B3B3C]">$9</span>
1545      <span class="text-gray-500 text-base ml-1">/month</span>
1546    </div>
1547    <div id="goProPriceAnnual" class="go-pro-price-block hidden text-center mb-1">
1548      <span class="text-5xl font-bold text-[#3B3B3C]">$59</span>
1549      <span class="text-gray-500 text-base ml-1">/year</span>
1550      <p class="text-sm text-gray-500 mt-1">Just $4.92/month, billed annually</p>
1551    </div>
1552
1553    <button
1554      type="button"
1555      id="goProCtaBtn"
1556      onclick="submitGoProCheckout()"
1557      class="w-full py-3.5 rounded-full bg-[#3B3B3C] text-white font-semibold text-base hover:bg-[#2d2d2e] transition-colors"
1558    >
1559      Get Nomados Pro
1560    </button>
1561    <p class="text-xs text-black text-center mt-3">Continue to our secure portal</p>
1562  </div>
1563
1564  <!-- ═══ Manage Membership Button (shown for Pro users) ═══ -->
1565  <div id="go-pro-manage-block" class="w-full max-w-md hidden">
1566    
1567    <a id="go-pro-manage-btn" href="https://nomados.samcart.com/customer_hub/login" target="_blank" rel="noopener noreferrer"
1568      class="block w-full py-3.5 rounded-full bg-[#3B3B3C] text-white font-semibold text-base text-center hover:bg-[#2d2d2e] transition-colors">
1569      Manage Membership
1570    </a>
1571    
1572    <p id="go-pro-manage-subtext" class="text-xs text-black text-center mt-3">
1573      Continue to our secure portal
1574    </p>
1575  </div>
1576
1577  <!-- ═══ Features List ═══ -->
1578  <div id="go-pro-features-wrap" class="w-full max-w-md border-t border-gray-200 p-4 pt-6 mt-6">
1579    <h2 class="text-xs font-semibold text-[#777777] uppercase tracking-wider mb-4">Everything included</h2>
1580    <ul class="space-y-3">
1581      <li class="flex items-center gap-3">
1582        <img src="/static/icons/pro-flight.svg" />
1583        <div>
1584          <p class="font-medium text-[#3B3B3C]">Airport Lounges</p>
1585          <p class="text-sm text-gray-500">Unlimited access worldwide</p>
1586        </div>
1587      </li>
1588      <li class="flex items-center gap-3">
1589        <img src="/static/icons/pro-esim.svg" />
1590        <div>
1591          <p class="font-medium text-[#3B3B3C]">eSIM Data</p>
1592          <p class="text-sm text-gray-500">20% off via Airalo</p>
1593        </div>
1594      </li>
1595      <li class="flex items-center gap-3">
1596        <img src="/static/icons/pro-evisa.svg" />
1597        <div>
1598          <p class="font-medium text-[#3B3B3C]">eVisa Services</p>
1599          <p class="text-sm text-gray-500">20% off via SimpleVisa</p>
1600        </div>
1601      </li>
1602      <li class="flex items-center gap-3">
1603        <img src="/static/icons/pro-insurance.svg" />
1604        <div>
1605          <p class="font-medium text-[#3B3B3C]">Travel Insurance</p>
1606          <p class="text-sm text-gray-500">SafetyWing coverage available</p>
1607        </div>
1608      </li>
1609      <li class="flex items-center gap-3">
1610        <img src="/static/icons/pro-miles.svg" />
1611        <div>
1612          <p class="font-medium text-[#3B3B3C]">AI Travel Assistant</p>
1613          <p class="text-sm text-gray-500">Unlimited conversations with Miles</p>
1614        </div>
1615      </li>
1616      <li class="flex items-center gap-3">
1617        <img src="/static/icons/pro-journey.svg" />
1618        <div>
1619          <p class="font-medium text-[#3B3B3C]">Journey Planning</p>
1620          <p class="text-sm text-gray-500">Unlimited saved itineraries</p>
1621        </div>
1622      </li>
1623    </ul>
1624  </div>
1625</div>
1626
1627<p class="text-xs text-gray-400 text-center mt-6">Cancel anytime · Secure payment · Instant access</p>
1628
1629    </div>
1630  </div>
1631</div>
1632
1633<!-- Destination Detail is now handled by Nomados Panel System -->
1634
1635<!-- Filters Modal - Moved outside main-container to avoid stacking context issues -->
1636<div id="filtersModal" class="fixed inset-0 hidden items-center justify-center opacity-0 scale-95 transition-all duration-200 ease-out" style="z-index: 2147483647; backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); background: rgba(0,0,0,0.5); display: none; pointer-events: auto;">
1637  <div class="bg-white rounded-3xl shadow-2xl border border-gray-200 p-6 w-[92%] max-w-3xl mx-auto transform transition-all duration-200 max-h-[90vh] overflow-y-auto">
1638    <div class="flex items-center justify-between mb-4">
1639      <div>
1640        <h3 class="text-xl font-semibold color-black-100">Filters</h3>
1641        <p class="text-sm text-gray-600 mt-1">Refine destinations in the list view</p>
1642      </div>
1643      <button onclick="closeFiltersModal()" class="text-gray-400 hover:text-gray-600 transition-colors" type="button" aria-label="Close filters">
1644        <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1645          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
1646        </svg>
1647      </button>
1648    </div>
1649
1650    <div class="mb-4 flex justify-between items-center">
1651      <h4 class="text-lg font-bold color-black-100 leading-none">Select Your Priorities</h4>
1652      <button type="button" onclick="resetAllFilters()" class="text-xs color-gray-100 hover:text-blue-600 cursor-pointer transition-colors">Reset</button>
1653    </div>
1654
1655    <form id="modalFilterForm" hx-get="/destinations/filter/" hx-target="#citiesListContainer" hx-swap="innerHTML" onsubmit="return false;">
1656      <input type="hidden" name="continent" value="" />
1657      <input type="hidden" name="climate" value="" />
1658      <input type="hidden" name="budget" value="" />
1659      <input type="hidden" name="internet" value="" />
1660      <input type="hidden" name="safety" value="" />
1661      <input type="hidden" name="nomads" value="" />
1662      <input type="hidden" name="english" value="" />
1663      <input type="hidden" name="visa" value="" />
1664      <input type="hidden" name="political" value="" />
1665      <input type="hidden" name="healthcare" value="" />
1666
1667      <div class="space-y-6">
1668        <div class="filter-group">
1669          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Continent</label>
1670          <div class="filter-options flex flex-wrap gap-2">
1671            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="continent" data-value="europe" onclick="toggleFilter(this)">Europe</button>
1672            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="continent" data-value="asia" onclick="toggleFilter(this)">Asia</button>
1673            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="continent" data-value="north-america" onclick="toggleFilter(this)">North America</button>
1674            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="continent" data-value="south-america" onclick="toggleFilter(this)">South America</button>
1675            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="continent" data-value="africa" onclick="toggleFilter(this)">Africa</button>
1676          </div>
1677        </div>
1678
1679        <div class="filter-group">
1680          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Budget (USD)</label>
1681          <div class="filter-options flex flex-wrap gap-2">
1682            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="budget" data-value="0-1000" onclick="toggleFilter(this)">&lt;$1k</button>
1683            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="budget" data-value="1000-2000" onclick="toggleFilter(this)">$1k-$2k</button>
1684            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="budget" data-value="2000-3000" onclick="toggleFilter(this)">$2k-$3k</button>
1685            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="budget" data-value="3000+" onclick="toggleFilter(this)">&gt;$3k</button>
1686          </div>
1687        </div>
1688
1689        <div class="filter-group">
1690          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Climate</label>
1691          <div class="filter-options flex flex-wrap gap-2">
1692            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="TROPICAL" onclick="toggleFilter(this)">Tropical</button>
1693            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="TEMPERATE" onclick="toggleFilter(this)">Temperate</button>
1694            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="MEDITERRANEAN" onclick="toggleFilter(this)">Mediterranean</button>
1695            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="SUBTROPICAL" onclick="toggleFilter(this)">Subtropical</button>
1696            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="ARID" onclick="toggleFilter(this)">Arid</button>
1697            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="COLD" onclick="toggleFilter(this)">Cold</button>
1698            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="CONTINENTAL" onclick="toggleFilter(this)">Continental</button>
1699            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="DESERT" onclick="toggleFilter(this)">Desert</button>
1700            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="MOUNTAIN" onclick="toggleFilter(this)">Mountain</button>
1701            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="climate" data-value="WARM" onclick="toggleFilter(this)">Warm</button>
1702          </div>
1703        </div>
1704
1705        <div class="filter-group">
1706          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Internet Speed</label>
1707          <div class="filter-options flex flex-wrap gap-2">
1708            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="internet" data-value="ULTRA_FAST" onclick="toggleFilter(this)">Ultra Fast</button>
1709            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="internet" data-value="FAST" onclick="toggleFilter(this)">Fast</button>
1710            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="internet" data-value="BASIC" onclick="toggleFilter(this)">Basic</button>
1711          </div>
1712        </div>
1713
1714        <div class="filter-group">
1715          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Safety</label>
1716          <div class="filter-options flex flex-wrap gap-2">
1717            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="safety" data-value="HIGH" onclick="toggleFilter(this)">High</button>
1718            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="safety" data-value="MOD" onclick="toggleFilter(this)">Moderate</button>
1719            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="safety" data-value="LOW" onclick="toggleFilter(this)">Low</button>
1720          </div>
1721        </div>
1722
1723        <div class="filter-group">
1724          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Nomad Community</label>
1725          <div class="filter-options flex flex-wrap gap-2">
1726            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="nomads" data-value="HIGH" onclick="toggleFilter(this)">Large</button>
1727            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="nomads" data-value="MOD" onclick="toggleFilter(this)">Growing</button>
1728            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="nomads" data-value="LOW" onclick="toggleFilter(this)">Small</button>
1729          </div>
1730        </div>
1731
1732        <div class="filter-group">
1733          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">English Speaking</label>
1734          <div class="filter-options flex flex-wrap gap-2">
1735            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="english" data-value="HIGH" onclick="toggleFilter(this)">High</button>
1736            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="english" data-value="MOD" onclick="toggleFilter(this)">Moderate</button>
1737            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="english" data-value="LOW" onclick="toggleFilter(this)">Low</button>
1738          </div>
1739        </div>
1740
1741        <div class="filter-group">
1742          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Healthcare Quality</label>
1743          <div class="filter-options flex flex-wrap gap-2">
1744            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="healthcare" data-value="HIGH" onclick="toggleFilter(this)">High</button>
1745            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="healthcare" data-value="MOD" onclick="toggleFilter(this)">Moderate</button>
1746            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="healthcare" data-value="LOW" onclick="toggleFilter(this)">Low</button>
1747          </div>
1748        </div>
1749
1750        <div class="filter-group">
1751          <label class="filter-label flex items-center gap-2 text-sm font-semibold text-black uppercase">Visa Requirements</label>
1752          <div class="filter-options flex flex-wrap gap-2">
1753            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="visa" data-value="required" onclick="toggleFilter(this)">Required</button>
1754            <button type="button" class="filter-option px-3 py-1.5 border border-gray-300 rounded-full text-xs" data-filter="visa" data-value="not_required" onclick="toggleFilter(this)">Not Required</button>
1755          </div>
1756        </div>
1757      </div>
1758
1759      <div class="mt-6 flex justify-end gap-2">
1760        <button 
1761          type="button" 
1762          class="flex bg-white items-center hover:-translate-y-0.5 color-black-100 py-1.5 px-4 border border-solid border-[#bbb] rounded-full font-medium transition duration-200 gap-1" 
1763          onclick="closeFiltersModal()"
1764        >
1765          Close
1766        </button>
1767        <button 
1768        type="button" 
1769        class="flex bg-dark-100 text-white items-center hover:-translate-y-0.5 py-1.5 px-4 roun
1769ded-full font-medium transition duration-200 gap-1" 
1770        onclick="applyCurrentFiltersFromModal()"
1771        >
1772          Apply
1773        </button>
1774      </div>
1775    </form>
1776  </div>
1777</div>
1778
1779
1780<!-- Chat Widget Styles -->
1781<style>
1782  .main-container { width: 100%; max-width: 100%; }
1783  .chat-widget {
1784    font-family: inherit;
1785    display: flex;
1786    flex-direction: column;
1787    position: relative;
1788    overflow: visible;
1789  }
1790
1791  .chat-blur-overlay {
1792    position: absolute;
1793    inset: 0;
1794    z-index: 10;
1795    display: flex;
1796    align-items: center;
1797    justify-content: center;
1798    border-radius: 16px;
1799    background: rgba(255, 255, 255, 0.5);
1800    backdrop-filter: blur(6px);
1801    pointer-events: all;
1802  }
1803
1804  .chat-blur-overlay .chat-blur-content {
1805    text-align: center;
1806    padding: 2rem 1rem;
1807    max-width: 90%;
1808  }
1809
1810  .chat-messages {
1811    flex: 1;
1812    min-height: 120px;
1813    overflow-x: hidden;
1814    overflow-y: auto;
1815    transition: max-height 0.2s;
1816    scroll-behavior: smooth;
1817  }
1818
1819  /* Mobile responsive layout */
1820  @media (max-width: 768px) {
1821    .main-container {
1822      flex-direction: column !important;
1823      position: relative !important;
1824    }
1825    
1826    /* Content layer (map/list/table) - full screen on mobile */
1827    .mobile-content-layer {
1828      width: 100% !important;
1829      height: 100vh !important;
1830      position: absolute !important;
1831      top: 0 !important;
1832      left: 0 !important;
1833      z-index: 1 !important;
1834    }
1835
1836    /* Go Pro view - fixed overlay on mobile, below mobile header (z-2500) */
1837    #go-pro-view {
1838      position: fixed !important;
1839      top: 0 !important;
1840      left: 0 !important;
1841      right: 0 !important;
1842      bottom: 0 !important;
1843      z-index: 2400 !important;
1844    }
1845
1846    /* Hide Go Pro close button on mobile - navigation via mobile header hamburger */
1847    #go-pro-view .go-pro-close {
1848      display: none !important;
1849    }
1850    
1851    /* Chat overlay - fixed at bottom, top layer */
1852    .mobile-chat-overlay {
1853      position: fixed !important;
1854      bottom: 0 !important;
1855      left: 0 !important;
1856      right: 0 !important;
1857      width: 100% !important;
1858      height: 100vh !important;
1859      z-index: 150 !important;
1860      border: none !important;
1861      transform: translateY(0);
1862      transition: transform 0.3s ease-in-out;
1863      pointer-events: auto !important;
1864      overflow-y: auto !important;
1865      -webkit-overflow-scrolling: touch !important;
1866    }
1867    
1868    .mobile-chat-overlay.expanded {
1869      transform: translateY(0) !important;
1870      pointer-events: auto !important;
1871      visibility: visible !important;
1872      opacity: 1 !important;
1873    }
1874    
1875    .mobile-chat-overlay.collapsed {
1876      transform: translateY(100%) !important;
1877      pointer-events: none !important;
1878    }
1879    
1880    /* Chat toggle button - base styles */
1881    .mobile-chat-toggle {
1882      display: flex !important;
1883      background: white !important;
1884      border: 1px solid #e5e7eb !important;
1885      border-radius: 50% !important;
1886      align-items: center !important;
1887      justify-content: center !important;
1888      cursor: pointer !important;
1889      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
1890      transition: all 0.3s ease !important;
1891    }
1892    
1893    .mobile-chat-toggle:hover {
1894      transform: scale(1.1);
1895      box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2) !important;
1896    }
1897    
1898    /* Toggle button in content layer when chat is collapsed */
1899    #content-chat-toggle-btn {
1900      position: fixed !important;
1901      bottom: 20px !important;
1902      right: 20px !important;
1903      width: 56px !important;
1904      height: 56px !important;
1905      z-index: 210 !important;
1906      display: none !important;
1907      pointer-events: auto !important;
1908    }
1909    
1910    .mobile-content-layer.has-collapsed-chat #content-chat-toggle-btn {
1911      display: flex !important;
1912    }
1913    
1914    /* Show chat button only on mobile */
1915    #chatViewButton {
1916      display: inline-flex !important;
1917    }
1918    
1919    /* Ensure filter modal appears above chat container on mobile */
1920    #filtersModal {
1921      z-index: 2147483647 !important;
1922    }
1923    
1924    /* Adjust toggle buttons for mobile - ensure they're always visible and above chat */
1925    .toggle-buttons {
1926      position: fixed !important;
1927      left: 50% !important;
1928      transform: translateX(-50%) !important;
1929      gap: 8px !important;
1930      z-index: 210 !important;
1931      flex-wrap: nowrap !important;
1932      pointer-events: auto !important;
1933    }
1934
1935    .toggle-buttons button {
1936      padding: 6px 12px !important;
1937      font-size: 12px !important;
1938      align-items: center !important;
1939      pointer-events: auto !important;
1940    }
1941  }
1942  
1943  /* View Button Styles */
1944  .view-button {
1945    width: 55px;
1946    padding: 5px 12px;
1947    font-size: 14px;
1948    font-weight: 500;
1949    border-radius: 50px;
1950    transition: all 0.2s ease;
1951    justify-content: center;
1952    align-items: center;
1953    cursor: pointer;
1954    border: none;
1955    background: #f7f7f7;
1956    color: #3B3B3C;
1957  }
1958  
1959  .view-button:hover {
1960    background: #D9D9D9;
1961    transform: translateY(-1px);
1962    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
1963  }
1964  
1965  .view-button.active {
1966    background: #3B3B3C;
1967    color: white;
1968    box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
1969  }
1970  
1971  .view-button:disabled {
1972    opacity: 0.5;
1973    cursor: not-allowed;
1974  }
1975  
1976  @media (max-width: 768px) {
1977    .view-button {
1978      padding: 6px 12px;
1979      font-size: 12px;
1980    }
1981  }
1982  
1983  /* Hide toggle button on desktop */
1984  @media (min-width: 769px) {
1985    .mobile-chat-toggle {
1986      display: none !important;
1987    }
1988    /* Hide chat button on desktop */
1989    #chatViewButton {
1990      display: none !important;
1991    }
1992    /* Ensure filter modal appears above chat container on desktop */
1993    #filtersModal {
1994      z-index: 2147483647 !important;
1995    }
1996  }
1997  
1998  /* Ensure filter modal always appears above chat container (all screen sizes) */
1999  #filtersModal {
2000    z-index: 2147483647 !important;
2001    position: fixed !important;
2002  }
2003
2004  .message {
2005    display: flex;
2006    flex-direction: column;
2007    animation: messageSlideIn 0.5s ease-in-out;
2008  }
2009
2010  .message.user {
2011    align-items: flex-end;
2012  }
2013
2014  .message.bot {
2015    align-items: flex-start;
2016  }
2017
2018  .message-content {
2019    max-width: 100%;
2020    padding-top: 12px;
2021    border-radius: 20px;
2022    font-size: 16px;
2023    line-height: 1.25;
2024    position: relative;
2025    backdrop-filter: blur(10px);
2026    transition: all 0.3s ease;
2027    word-wrap: break-word;
2028    overflow-wrap: break-word;
2029    margin-top: 16px;
2030  }
2031
2032  .message-content:hover {
2033    transform: translateY(-1px);
2034  }
2035
2036  .message.user .message-content {
2037    background: #f3f3f3;
2038    border: 1px solid #eee;
2039    color: black;
2040    border-bottom-right-radius: 0px;
2041    position: relative;
2042    padding: 12px 16px;
2043  }
2044
2045  .message.user .message-content::before {
2046    content: "";
2047    position: absolute;
2048    top: 0;
2049    left: 0;
2050    right: 0;
2051    bottom: 0;
2052    background: linear-gradient(
2053      135deg,
2054      rgba(255, 255, 255, 0.1) 0%,
2055      rgba(255, 255, 255, 0.05) 100%
2056    );
2057    border-radius: 20px;
2058    border-bottom-right-radius: 0px;
2059    pointer-events: none;
2060  }
2061
2062  .message.bot .message-content {
2063    background: rgba(255, 255, 255, 0.95);
2064    color: #1f2937;
2065    border-bottom-left-radius: 8px;
2066  }
2067
2068  .message-time {
2069    font-size: 11px;
2070    color: #6b7280;
2071    margin-top: 4px;
2072    font-weight: 500;
2073    opacity: 0.8;
2074  }
2075
2076  .chat-input {
2077    border-radius: 16px;
2078    position: sticky;
2079    z-index: 2;
2080    border: 1px solid #d9d9d9;
2081    box-shadow: 0 6px 9px 2px rgba(0, 0, 0, 0.09);
2082
2083    @media (max-width: 768px) {
2084      box-shadow: none;
2085    }
2086  }
2087
2088
2089
2090  .chat-input-form {
2091    display: flex;
2092    gap: 10px;
2093    align-items: center;
2094  }
2095
2096  .chat-input-field {
2097    flex: 1;
2098    border-radius: 16px;
2099    font-size: 16px;
2100    outline: none;
2101    background: #fff;
2102  }
2103
2104  .chat-input-field:disabled {
2105    color: #9ca3af;
2106    cursor: not-allowed;
2107  }
2108
2109  .send-button {
2110    cursor: pointer;
2111    display: flex;
2112    align-items: center;
2113    justify-content: center;
2114  }
2115
2116  .send-button:active {
2117    transform: translateY(0);
2118  }
2119
2120  .send-button:disabled {
2121    background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%);
2122    cursor: not-allowed;
2123    transform: none;
2124    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2125    opacity: 0.6;
2126  }
2127
2128  .typing-indicator {
2129    display: none;
2130    padding: 12px 16px;
2131    background: rgba(255, 255, 255, 0.95);
2132    border: 1px solid rgba(229, 231, 235, 0.8);
2133    border-radius: 20px;
2134    border-bottom-left-radius: 8px;
2135    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
2136    backdrop-filter: blur(10px);
2137    width: fit-content;
2138    flex-shrink: 0;
2139    margin: 0 0 4px 4px;
2140  }
2141
2142  .typing-indicator.show {
2143    display: block;
2144    animation: typingSlideIn 0.3s ease-out;
2145  }
2146
2147  .typing-dots {
2148    display: flex;
2149    gap: 4px;
2150    align-items: center;
2151  }
2152
2153  .typing-dot {
2154    width: 6px;
2155    height: 6px;
2156    border-radius: 50%;
2157    background: linear-gradient(135deg, #3b3b3c 0%, #969696 100%);
2158    animation: typing 1.4s infinite ease-in-out;
2159  }
2160
2161  .typing-dot:nth-child(1) {
2162    animation-delay: -0.32s;
2163  }
2164  .typing-dot:nth-child(2) {
2165    animation-delay: -0.16s;
2166  }
2167
2168  @keyframes typing {
2169    0%,
2170    80%,
2171    100% {
2172      transform: scale(0.8);
2173      opacity: 0.5;
2174    }
2175    40% {
2176      transform: scale(1);
2177      opacity: 1;
2178    }
2179  }
2180
2181  @keyframes messageSlideIn {
2182    from {
2183      opacity: 0;
2184      transform: translateY(10px);
2185    }
2186    to {
2187      opacity: 1;
2188      transform: translateY(0);
2189    }
2190  }
2191
2192  @keyframes typingSlideIn {
2193    from {
2194      opacity: 0;
2195      transform: translateY(5px);
2196    }
2197    to {
2198      opacity: 1;
2199      transform: translateY(0);
2200    }
2201  }
2202
2203  /* Minimal styles to ensure filter buttons are clearly interactive */
2204  .filter-option { cursor: pointer; user-select: none; }
2205  .filter-active { background-color: #3b3b3c !important; color: #fff !important; border-color: #3b3b3c !important; }
2206
2207  #citiesListContainer {
2208    grid-auto-rows: max-content;
2209  }
2210
2211  .avatar {
2212    box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px, rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px, rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
2213  }
2214
2215  .sample-questions {
2216    margin: 16px 0;
2217    padding: 0;
2218    background: transparent;
2219    border: none;
2220    animation: messageSlideIn 0.5s ease-in-out;
2221  }
2222
2223  .sample-questions-title {
2224    font-size: 15px;
2225    font-weight: 500;
2226    color: #9ca3af;
2227    margin-bottom: 8px;
2228    text-align: left;
2229  }
2230
2231  .sample-questions-list {
2232    display: flex;
2233    flex-direction: column;
2234    gap: 6px;
2235  }
2236
2237  .sample-question {
2238    padding: 8px 12px;
2239    background: transparent;
2240    border: 1px solid #e5e7eb;
2241    border-radius: 6px;
2242    font-size: 15px;
2243    color: #374151;
2244    cursor: pointer;
2245    transition: all 0.15s ease;
2246    text-align: left;
2247  }
2248
2249  .sample-question:hover {
2250    background: transparent;
2251    border-color: #9ca3af;
2252    color: #374151;
2253  }
2254
2255  .sample-question:active {
2256    border-color: #6b7280;
2257    color: #1f2937;
2258  }
2259
2260  /* Compact destination widget styling */
2261  .destination-widget-compact {
2262    word-wrap: break-word;
2263    overflow-wrap: break-word;
2264  }
2265
2266  .destination-widget-compact .flex {
2267    flex-wrap: nowrap;
2268  }
2269
2270  .destination-widget-compact .min-w-0 {
2271    min-width: 0;
2272  }
2273
2274  /* Google Maps specific styles */
2275  #map {
2276    width: 100% !important;
2277    height: 100% !important;
2278    min-height: 100% !important;
2279    background-color: #f0f0f0;
2280    position: absolute !important;
2281    top: 0 !important;
2282    left: 0 !important;
2283  }
2284
2285  /* Ensure the map container has proper dimensions */
2286  .map-container {
2287    position: relative;
2288    width: 100%;
2289    height: 100%;
2290    overflow: hidden;
2291  }
2292  
2293  /* On mobile, when in map view, make map container fixed and full-screen */
2294  @media (max-width: 768px) {
2295    .map-container.map-view-active {
2296      position: fixed !important;
2297      top: 0 !important;
2298      left: 0 !important;
2299      width: 100vw !important;
2300      height: 100vh !important;
2301      z-index: 100 !important;
2302    }
2303  }
2304
2305  /* Ensure full height layout */
2306  .main-container {
2307    height: 100vh;
2308    max-height: 100vh;
2309    overflow: hidden;
2310  }
2311
2312  /* Ensure chat widget takes full height */
2313  .chat-widget {
2314    height: 100%;
2315    max-height: 100%;
2316  }
2317
2318  /* List view styles */
2319  #list-container {
2320    height: 100%;
2321    max-height: 100%;
2322    overflow-y: auto;
2323    overflow-x: hidden;
2324    position: relative;
2325    -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
2326  }
2327
2328  #citiesListContainer {
2329    max-width: 1200px;
2330    margin: 0 auto;
2331    min-height: min-content;
2332    padding-bottom: 2rem; /* Extra padding at bottom for better scrolling */
2333  }
2334
2335  /* Make list view items scroll smoothly */
2336  #list-container .destination-widget-compact,
2337  #list-container .city-card {
2338    transition: transform 0.2s ease, box-shadow 0.2s ease;
2339  }
2340
2341  #list-container .destination-widget-compact:hover,
2342  #list-container .city-card:hover {
2343    transform: translateY(-2px);
2344    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
2345  }
2346
2347  /* Destination Detail Panel Styles */
2348  .destination-detail-panel {
2349    position: fixed;
2350    top: 0;
2351    left: 0;
2352    right: 0;
2353    bottom: 0;
2354    width: 100%;
2355    height: 100%;
2356    background: rgba(0, 0, 0, 0.5);
2357    z-index: 20000 !important;
2358    opacity: 0;
2359    pointer-events: none;
2360    transition: opacity 0.3s ease;
2361  }
2362
2363  .destination-detail-panel.open {
2364    opacity: 1;
2365    pointer-events: all;
2366  }
2367
2368  .destination-detail-content {
2369    position: absolute;
2370    top: 0;
2371    right: 0;
2372    width: 60%;
2373    height: 100%;
2374    background: white;
2375    box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
2376    transform: translateX(100%);
2377    transition: transform 0.3s ease;
2378    z-index: 20001 !important;
2379  }
2380
2381  .destination-detail-panel.open .destination-detail-content {
2382    transform: translateX(0);
2383  }
2384
2385  .destination-detail-close {
2386    position: absolute;
2387    top: 16px;
2388    right: 16px;
2389    z-index: 10001;
2390    background: white;
2391    border: 1px solid #e5e7eb;
2392    border-radius: 50%;
2393    width: 40px;
2394    height: 40px;
2395    display: flex;
2396    align-items: center;
2397    justify-content: center;
2398    cursor: pointer;
2399    transition: all 0.2s ease;
2400    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2401  }
2402
2403  .destination-detail-close:hover {
2404    background: #f3f4f6;
2405    transform: scale(1.1);
2406  }
2407
2408  /* Mobile responsive for destination detail panel */
2409  @media (max-width: 1024px) {
2410    .destination-detail-content {
2411      width: 100%;
2412    }
2413  }
2414</style>
2415
2416<!-- HTMX Event Handlers -->
2417<script>
2418  document.body.addEventListener('htmx:beforeRequest', function(evt) {
2419    const target = evt.detail.target;
2420    const form = evt.detail.elt;
2421    if (form && form.id === 'chatForm') {
2422      document.getElementById('typingIndicator').classList.add('show');
2423      changeChatGif("thinking");
2424      
2425      const input = document.getElementById('messageInput');
2426      const sendButton = document.getElementById('sendButton');
2427      input.disabled = true;
2428      sendButton.disabled = true;
2429      
2430      const logoButton = document.getElementById('sidebar-logo-btn');
2431      const mobileLogoButton = document.getElementById('mobile-sidebar-logo-btn');
2432      if (logoButton) {
2433        logoButton.disabled = true;
2434      }
2435      if (mobileLogoButton) {
2436        mobileLogoButton.disabled = true;
2437      }
2438    }
2439  });
2440
2441  document.body.addEventListener('htmx:afterRequest', function(evt) {
2442    const form = evt.detail.elt;
2443    if (form && form.id === 'chatForm') {
2444      document.getElementById('typingIndicator').classList.remove('show');
2445      
2446      const input = document.getElementById('messageInput');
2447      const sendButton = document.getElementById('sendButton');
2448      input.disabled = false;
2449      sendButton.disabled = false;
2450      input.value = '';
2451      input.focus();
2452      
2453      const logoButton = document.getElementById('sidebar-logo-btn');
2454      const mobileLogoButton = document.getElementById('mobile-sidebar-logo-btn');
2455      if (logoButton) {
2456        logoButton.disabled = false;
2457      }
2458      if (mobileLogoButton) {
2459        mobileLogoButton.disabled = false;
2460      }
2461      
2462      const chatMessages = document.getElementById('chatMessages');
2463      chatMessages.scrollTop = chatMessages.scrollHeight;
2464      
2465      setTimeout(() => {
2466        chatMessages.scrollTop = chatMessages.scrollHeight;
2467      }, 300);
2468    }
2469  });
2470
2471  document.body.addEventListener('htmx:responseError', function(evt) {
2472    const form = evt.detail.elt;
2473    if (form && form.id === 'chatForm') {
2474      document.getElementById('typingIndicator').classList.remove('show');
2475      
2476      const input = document.getElementById('messageInput');
2477      const sendButton = document.getElementById('sendButton');
2478      input.disabled = false;
2479      sendButton.disabled = false;
2480      
2481      const logoButton = document.getElementById('sidebar-logo-btn');
2482      const mobileLogoButton = document.getElementById('mobile-sidebar-logo-btn');
2483      if (logoButton) {
2484        logoButton.disabled = false;
2485      }
2486      if (mobileLogoButton) {
2487        mobileLogoButton.disabled = false;
2488      }
2489      
2490      addMessage("Sorry, I encountered an error. Please try again.", false);
2491      changeChatGif("concerned");
2492      setTimeout(() => {
2493        changeChatGif("idle");
2494      }, 5000);
2495    }
2496  });
2497
2498
2499  document.body.addEventListener('htmx:afterSwap', function(evt) {
2500    if (evt.detail.target.id === 'chatMessages') {
2501      const messages = document.querySelectorAll('#chatMessages .message');
2502      const lastMessage = messages[messages.length - 1];
2503      
2504      if (lastMessage && lastMessage.classList.contains('bot')) {
2505        const chatStyle = lastMessage.getAttribute('data-chat-style');
2506        if (chatStyle) {
2507          changeChatGif(chatStyle);
2508          setTimeout(() => {
2509            changeChatGif("idle");
2510          }, 5000);
2511        } else {
2512          changeChatGif("talking");
2513          setTimeout(() => {
2514            changeChatGif("idle");
2515          }, 3000);
2516        }
2517
2518        // Show sample questions after bot response (unless filter wizard was used)
2519        if (!filterWorkflowActive && !filterWizardUsed) {
2520          var sqEl = document.getElementById('sampleQuestions');
2521          if (sqEl) sqEl.style.display = '';
2522        }
2523      }
2524
2525      try {
2526        const xhr = evt.detail.xhr;
2527        if (xhr && xhr.responseText) {
2528          const temp = document.createElement('div');
2529          temp.innerHTML = xhr.responseText;
2530          const from = temp.querySelector('#destinationsTableContainer');
2531          const to = document.getElementById('destinationsTableContainer');
2532          if (from && to) {
2533            to.innerHTML = from.innerHTML;
2534          }
2535          const fromList = temp.querySelector('#citiesListContainer');
2536          const toList = document.getElementById('citiesListContainer');
2537          if (fromList && toList) {
2538            toList.innerHTML = fromList.innerHTML;
2539          }
2540
2541          try {
2542            console.log("[chat] Map update: parsing response for city pins...");
2543            const pinScope = temp.querySelector('[data-city-pins]') || temp;
2544            const pinEls = pinScope ? pinScope.querySelectorAll('[data-city-pin]') : [];
2545            console.log(`[chat] Found ${pinEls.length} city pin(s) in the response.`);
2546
2547            if (typeof clearMapMarkers === 'function') {
2548              console.log("[chat] Clearing map markers...");
2549              clearMapMarkers();
2550            } else {
2551              console.warn("[chat] clearMapMarkers() not found.");
2552            }
2553            
2554            pinEls.forEach(el => {
2555              const data = {
2556                lat: parseFloat(el.getAttribute('data-lat')),
2557                lng: parseFloat(el.getAttribute('data-lng')),
2558                title: el.getAttribute('data-title'),
2559                comment: el.getAttribute('data-comment'),
2560                cityName: el.getAttribute('data-city-name'),
2561                countryName: el.getAttribute('data-country-name'),
2562                slug: el.getAttribute('data-city-slug'),
2563                coverPhoto: el.getAttribute('data-cover-photo')
2564              };
2565              if (!isNaN(data.lat) && !isNaN(data.lng)) {
2566                try { 
2567                  console.log(`[chat] Adding city pin for ${data.cityName} (${data.lat}, ${data.lng})`);
2568                  addCityPin(data); 
2569                } catch (err) {
2570                  console.error(`[chat] Failed to add city pin for ${data.cityName}:`, err);
2571                }
2572              } else {
2573                console.warn(`[chat] Invalid coordinates for city pin:`, data);
2574              }
2575            });
2576            
2577            // When pins are returned, switch to map view on desktop only
2578            // On mobile, user switches to map manually via the Map button
2579            if (pinEls.length > 0 && typeof switchView === 'function' && window.innerWidth > 768) {
2580              switchView('map');
2581            }
2582
2583            // Fit map to show all pins when cities are listed (both mobile and desktop)
2584            if (pinEls.length > 0) {
2585              const fitMapToAllPins = () => {
2586                if (typeof google !== 'undefined' && google.maps && map && mapInitialized && typeof fitMapToMarkers === 'function') {
2587                  try {
2588                    console.log(`[chat] Fitting map to show all ${markers.length} pin(s)`);
2589                    fitMapToMarkers();
2590                    google.maps.event.trigger(map, 'resize');
2591                    return true;
2592                  } catch (err) {
2593                    console.error("[chat] Error fitting map to all pins:", err);
2594                    return false;
2595                  }
2596                }
2597                return false;
2598              };
2599              
2600              // Try immediately after a short delay to ensure all markers are added
2601              setTimeout(() => {
2602                if (!fitMapToAllPins()) {
2603                  // If map not ready, retry with increasing delays (especially important for mobile)
2604                  let retryCount = 0;
2605                  const maxRetries = 15;
2606                  const retryInterval = setInterval(() => {
2607                    if (fitMapToAllPins() || retryCount >= maxRetries) {
2608                      clearInterval(retryInterval);
2609                      if (retryCount >= maxRetries) {
2610                        console.warn("[chat] Failed to fit map to all pins after max retries");
2611                      }
2612                    }
2613                    retryCount++;
2614                  }, 200);
2615                }
2616              }, 300); // Delay to ensure all markers are added
2617            } else if (window.currentView === 'map' && pinEls.length === 0) {
2618              setTimeout(() => {
2619                if (typeof google !== 'undefined' && map) {
2620                  try { 
2621                    console.log("[chat] Triggering google.maps.event.resize");
2622                    google.maps.event.trigger(map, 'resize'); 
2623                  } catch (err) {
2624                    console.error("[chat] Error on google.maps.event.resize:", err);
2625                  }
2626                }
2627              }, 50);
2628            }
2629            
2630            const hiddenWidgetContainer = temp.querySelector('.hidden-full-widgets');
2631            if (hiddenWidgetContainer) {
2632              const fullWidgets = hiddenWidgetContainer.querySelectorAll('[data-city-widget]');
2633              allCities = [];
2634              fullWidgets.forEach(widget => {
2635                const slug = widget.getAttribute('data-city-slug');
2636                if (slug) allCities.push({ slug, html: widget.outerHTML });
2637              });
2638            } else {
2639              if (pinEls.length > 0) {
2640                allCities = Array.from(pinEls).map(pin => ({
2641                  slug: pin.getAttribute('data-city-slug') || '',
2642                  html: pin.outerHTML
2643                })).filter(city => city.slug);
2644              }
2645            }
2646            
2647            // Update mobile view buttons visibility after cities are added
2648            updateMobileViewButtonsVisibility();
2649          } catch (err) {
2650            console.error("[chat] Error during map update from response HTML:", err);
2651          }
2652        }
2653      } catch (err) {
2654        console.error("[chat] Error while handling response swap:", err);
2655      }
2656        
2657        const chatMessages = document.getElementById('chatMessages');
2658        chatMessages.scrollTop = chatMessages.scrollHeight;
2659      setTimeout(() => { chatMessages.scrollTop = chatMessages.scrollHeight; }, 200);
2660    }
2661  });
2662
2663  document.body.addEventListener('switchView', function(e) {
2664    try {
2665      const detail = e && e.detail ? e.detail : null;
2666      const view = (detail && (detail.view || detail)) || 'list';
2667      switchView(view);
2668    } catch (_) {}
2669  });
2670
2671  document.getElementById('chatForm').addEventListener('submit', function(e) {
2672    const input = document.getElementById('messageInput');
2673    const message = input.value.trim();
2674
2675    if (message) {
2676      addMessage(message, true);
2677      // Permanently remove sample questions once user starts chatting
2678      var sqEl = document.getElementById('sampleQuestions');
2679      if (sqEl) sqEl.remove();
2680    }
2681  });
2682
2683  function addMessage(text, isUser) {
2684    const chatMessages = document.getElementById('chatMessages');
2685    const messageDiv = document.createElement('div');
2686    messageDiv.className = `message ${isUser ? 'user' : 'bot'}`;
2687    
2688    const now = new Date();
2689    const timeString = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
2690    
2691    messageDiv.innerHTML = `
2692      <div class="message-content">${text}</div>
2693      <div class="message-time">${timeString}</div>
2694    `;
2695    
2696    chatMessages.appendChild(messageDiv);
2697    chatMessages.scrollTop = chatMessages.scrollHeight;
2698  }
2699
2700  function changeChatGif(style) {
2701    const desktopGif = document.getElementById('chatGif');
2702    const mobileGif = document.getElementById('mobileChatGif');
2703    const desktopGifPath = `/static/chat/${style}.gif`;
2704    const mobileGifPath = `/static/chat/mobile_${style}.gif`;
2705    desktopGif.src = desktopGifPath;
2706    mobileGif.src = mobileGifPath;
2707  }
2708
2709  window.submitQuestion = function(questionText) {
2710    const input = document.getElementById('messageInput');
2711    const form = document.getElementById('chatForm');
2712    
2713    var sqEl = document.getElementById('sampleQuestions');
2714    if (sqEl) sqEl.remove();
2715    
2716    input.value = questionText;
2717    htmx.trigger(form, 'submit');
2718  };
2719
2720  function storeInitialChatMessage() {
2721    const chatMessages = document.getElementById('chatMessages');
2722    if (chatMessages) {
2723      const initialMessage = chatMessages.querySelector('.message.bot');
2724      if (initialMessage && !chatMessages.getAttribute('data-initial-message')) {
2725        chatMessages.setAttribute('data-initial-message', initialMessage.outerHTML);
2726      }
2727    }
2728  }
2729
2730  window.refreshChat = function() {
2731    const chatMessages = document.getElementById('chatMessages');
2732    const chatGif = document.getElementById('chatGif');
2733    const mobileChatGif = document.getElementById('mobileChatGif');
2734    
2735    if (!chatMessages) return;
2736    
2737    storeInitialChatMessage();
2738    
2739    const initialMessageHTML = chatMessages.getAttribute('data-initial-message');
2740    if (initialMessageHTML) {
2741      chatMessages.innerHTML = initialMessageHTML;
2742    }
2743    
2744    if (chatGif | mobileChatGif) {
2745      chatGif.src = `/static/chat/idle.gif`;
2746      mobileChatGif.src = `/static/chat/mobile_idle.gif`;
2747    }
2748    
2749    const sampleQuestions = document.getElementById('sampleQuestions');
2750    if (sampleQuestions) {
2751      sampleQuestions.style.display = 'none';
2752    }
2753
2754    // Reset wizard flags so sample questions can show again
2755    window.filterWorkflowActive = false;
2756    window.filterWizardUsed = false;
2757
2758    if (typeof resetAllFilters === 'function') {
2759      resetAllFilters();
2760    }
2761    
2762    const messageInput = document.getElementById('messageInput');
2763    if (messageInput) {
2764      messageInput.value = '';
2765    }
2766    
2767    // Clear cities list container
2768    const citiesListContainer = document.getElementById('citiesListContainer');
2769    if (citiesListContainer) {
2770      citiesListContainer.innerHTML = `
2771        <div class="text-center text-gray-500 py-12">
2772          <p class="text-lg font-semibold mb-2">No destinations yet</p>
2773          <p class="text-sm">Start chatting to see recommended destinations</p>
2774        </div>
2775      `;
2776    }
2777    
2778    // Clear destinations table container
2779    const destinationsTableContainer = document.getElementById('destinationsTableContainer');
2780    if (destinationsTableContainer) {
2781      destinationsTableContainer.innerHTML = `
2782        <div class="text-center text-gray-500 py-12">
2783          <p class="text-lg font-semibold mb-2">No destinations yet</p>
2784          <p class="text-sm">Start chatting to see recommended destinations</p>
2785        </div>
2786      `;
2787    }
2788    
2789    // Clear allCities array
2790    if (typeof allCities !== 'undefined') {
2791      allCities = [];
2792    }
2793    
2794    // Clear map markers
2795    if (typeof clearMapMarkers === 'function') {
2796      clearMapMarkers();
2797    }
2798    
2799    // Clear city pins data
2800    const cityPins = document.getElementById('cityPins');
2801    if (cityPins) {
2802      cityPins.innerHTML = '';
2803    }
2804    
2805    // Update mobile view buttons visibility after refresh (will hide since cities are cleared)
2806    updateMobileViewButtonsVisibility();
2807    
2808    chatMessages.scrollTop = 0;
2809  };
2810
2811  if (document.readyState === 'loading') {
2812    document.addEventListener('DOMContentLoaded', storeInitialChatMessage);
2813  } else {
2814    storeInitialChatMessage();
2815  }
2816
2817  // Google Maps initialization
2818  let map;
2819  let markers = [];
2820  let mapInitialized = false;
2821  
2822  function initMap() {
2823    try {
2824      // Check if Google Maps API is loaded
2825      if (typeof google === 'undefined' || !google.maps || !google.maps.Map) {
2826        // Retry after a short delay
2827        setTimeout(initMap, 100);
2828        return;
2829      }
2830
2831      // Check if the map element exists
2832      const mapElement = document.getElementById("map");
2833      if (!mapElement) {
2834        return;
2835      }
2836
2837      // Default center focused on Europe and Asia
2838      const defaultCenter = { lat: 50.0, lng: 50.0 }; // Center between Europe and Asia
2839      const isMobile = window.innerWidth <= 768;
2840      
2841      map = new google.maps.Map(mapElement, {
2842        zoom: isMobile ? 2 : 3, // Higher initial zoom for mobile devices
2843        minZoom: isMobile ? 2 : 3, // Prevent zooming out too far to avoid gray areas
2844        center: defaultCenter,
2845        mapTypeId: google.maps.MapTypeId.ROADMAP,
2846        restriction: {
2847          latLngBounds: {
2848            north: 85,
2849            south: -85,
2850            east: 180,
2851            west: -180
2852          },
2853          strictBounds: false
2854        },
2855        styles: [
2856          {
2857            featureType: "poi",
2858            elementType: "labels",
2859            stylers: [{ visibility: "off" }]
2860          }
2861        ],
2862        fullscreenControl: false,
2863        streetViewControl: false,
2864        mapTypeControl: false,
2865        zoomControl: true,
2866        zoomControlOptions: {
2867          position: google.maps.ControlPosition.RIGHT_CENTER
2868        },
2869        cameraControl: false,
2870        gestureHandling: 'greedy'
2871      });
2872      
2873      mapInitialized = true;
2874      
2875      // Trigger multiple resize events to ensure the map fills the container
2876      setTimeout(() => {
2877        if (map) {
2878          google.maps.event.trigger(map, 'resize');
2879        }
2880      }, 100);
2881      
2882      setTimeout(() => {
2883        if (map) {
2884          google.maps.event.trigger(map, 'resize');
2885        }
2886      }, 500);
2887      
2888      setTimeout(() => {
2889        if (map) {
2890          google.maps.event.trigger(map, 'resize');
2891        }
2892      }, 1000);
2893      
2894    } catch (error) {
2895      // Retry after a short delay
2896      setTimeout(initMap, 500);
2897    }
2898  }
2899
2900  // Function to add a city pin to the map
2901  window.addCityPin = function(cityData) {
2902    if (!map || !mapInitialized) {
2903      // Retry after a short delay
2904      setTimeout(() => addCityPin(cityData), 500);
2905      return;
2906    }
2907
2908
2909    try {
2910      const position = { lat: cityData.lat, lng: cityData.lng };
2911      
2912      // Create custom marker icon
2913      const markerIcon = {
2914        url: 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(`
2915          <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
2916            <circle cx="16" cy="16" r="12" fill="#3B82F6" stroke="#1E40AF" stroke-width="2"/>
2917            <circle cx="16" cy="16" r="6" fill="white"/>
2918            <text x="16" y="20" text-anchor="middle" font-family="Arial" font-size="10" fill="#1E40AF">📍</text>
2919          </svg>
2920        `),
2921        scaledSize: new google.maps.Size(32, 32),
2922        anchor: new google.maps.Point(16, 16)
2923      };
2924
2925      // Create marker
2926      const marker = new google.maps.Marker({
2927        position: position,
2928        map: map,
2929        title: cityData.title,
2930        icon: markerIcon,
2931        animation: google.maps.Animation.DROP
2932      });
2933      
2934
2935      // Create info window content
2936      const infoWindowContent = `
2937        <div style="max-width: 250px;">
2938          <img src="${cityData.coverPhoto}" alt="${cityData.cityName}" class="w-full h-48 object-cover ps-2">
2939          <div class="ps-4 p-2">
2940            <h3 style="margin: 0 0 8px 0; color: #1E40AF; font-size: 16px;">${cityData.cityName}</h3>
2941            <p style="margin: 0 0 8px 0; color: #6B7280; font-size: 14px;">${cityData.countryName}</p>
2942            <p style="margin: 0 8px 8px 0; color: #374151; font-size: 13px; line-height: 1.4;">${cityData.comment}</p>
2943            ${cityData.slug ? `<a href="#" id="infowin-open-${cityData.slug}" style="color:#2563EB; font-size:13px; text-decoration:none;">View details ▶</a>` : ''}
2944          </div>
2945        </div>
2946      `;
2947
2948      const infoWindow = new google.maps.InfoWindow({
2949        content: infoWindowContent
2950      });
2951
2952      // Add click listener to marker
2953      marker.addListener('click', function() {
2954        // Close other info windows
2955        markers.forEach(m => {
2956          if (m.infoWindow) {
2957            m.infoWindow.close();
2958          }
2959        });
2960        
2961        infoWindow.open(map, marker);
2962      });
2963
2964      // Bind a click handler to the InfoWindow content once it's rendered
2965      google.maps.event.addListener(infoWindow, 'domready', function() {
2966        try {
2967          if (cityData.slug) {
2968            const anchor = document.getElementById(`infowin-open-${cityData.slug}`);
2969            if (anchor) {
2970              anchor.addEventListener('click', function(e) {
2971                e.preventDefault();
2972                try { openDestinationDetail(cityData.slug); } catch (_) {}
2973              });
2974            }
2975          }
2976        } catch (_) {}
2977      });
2978
2979      marker.infoWindow = infoWindow;
2980      markers.push(marker);
2981
2982    } catch (error) {
2983    }
2984  };
2985
2986  // Function to fit map to show all markers
2987  function fitMapToMarkers() {
2988    if (markers.length === 0) return;
2989
2990    const bounds = new google.maps.LatLngBounds();
2991    markers.forEach(marker => {
2992      bounds.extend(marker.getPosition());
2993    });
2994
2995    // Cap zoom so fitBounds never over-zooms on a single / clustered markers
2996    map.setOptions({ maxZoom: 6 });
2997    map.fitBounds(bounds);
2998    google.maps.event.addListenerOnce(map, 'idle', function() {
2999      map.setOptions({ maxZoom: undefined });
3000    });
3001  };
3002
3003  // Function to clear all markers
3004  window.clearMapMarkers = function() {
3005    markers.forEach(marker => {
3006      marker.setMap(null);
3007    });
3008    markers = [];
3009  };
3010
3011  // Function to process city pins in a message
3012  function processCityPinsInMessage(messageElement) {
3013    if (!messageElement) {
3014      return;
3015    }
3016
3017    // Look for divs with city pin data
3018    let cityPinContainers = [];
3019    try {
3020      // Preferred: pins provided inside the latest message
3021      cityPinContainers = Array.from(messageElement.querySelectorAll('[data-city-pins]'));
3022      // Fallback: a global container with id="cityPins" if present
3023      const globalPins = document.getElementById('cityPins');
3024      if (globalPins) {
3025        cityPinContainers.push(globalPins);
3026      }
3027    } catch (_) {
3028      cityPinContainers = [];
3029    }
3030
3031    cityPinContainers.forEach(container => {
3032      const cityPins = container.querySelectorAll('[data-city-pin]');
3033
3034      cityPins.forEach(pinElement => {
3035        try {
3036          const cityData = {
3037            lat: parseFloat(pinElement.getAttribute('data-lat')),
3038            lng: parseFloat(pinElement.getAttribute('data-lng')),
3039            title: pinElement.getAttribute('data-title'),
3040            comment: pinElement.getAttribute('data-comment'),
3041            cityName: pinElement.getAttribute('data-city-name'),
3042            countryName: pinElement.getAttribute('data-country-name'),
3043            slug: pinElement.getAttribute('data-city-slug'),
3044            coverPhoto: pinElement.getAttribute('data-cover-photo')
3045          };
3046
3047          if (cityData.lat && cityData.lng && !isNaN(cityData.lat) && !isNaN(cityData.lng)) {
3048            addCityPin(cityData);
3049          }
3050        } catch (error) {
3051          // Silent error handling
3052        }
3053      });
3054    });
3055  }
3056
3057  // Handle Google Maps API loading errors
3058  window.gm_authFailure = function() {
3059    // Silent error handling
3060  };
3061
3062  // Fallback initialization in case the callback doesn't work
3063  document.addEventListener('DOMContentLoaded', function() {
3064    if (typeof google !== 'undefined' && google.maps && !mapInitialized) {
3065      initMap();
3066    }
3067  });
3068
3069  // Additional fallback - check periodically if map is not initialized
3070  let initRetryCount = 0;
3071  const maxRetries = 20; // 10 seconds max
3072  
3073  function checkMapInitialization() {
3074    if (mapInitialized) {
3075      return;
3076    }
3077    
3078    if (initRetryCount >= maxRetries) {
3079      return;
3080    }
3081    
3082    initRetryCount++;
3083    
3084    if (typeof google !== 'undefined' && google.maps && !mapInitialized) {
3085      initMap();
3086    } else {
3087      setTimeout(checkMapInitialization, 500);
3088    }
3089  }
3090  
3091  // Start checking after a short delay
3092  setTimeout(checkMapInitialization, 1000);
3093
3094  // Function to scroll chat to bottom
3095  function scrollChatToBottom() {
3096    const chatMessages = document.getElementById('chatMessages');
3097    if (chatMessages) {
3098      // Use setTimeout to ensure DOM is updated
3099      setTimeout(() => {
3100        chatMessages.scrollTop = chatMessages.scrollHeight;
3101      }, 100);
3102      setTimeout(() => {
3103        chatMessages.scrollTop = chatMessages.scrollHeight;
3104      }, 300);
3105    }
3106  }
3107
3108  // Function to update mobile view buttons visibility based on screen size, cities, and current view
3109  function updateMobileViewButtonsVisibility() {
3110    const mobileViewButtons = document.getElementById('mobileViewButtonsContainer');
3111    if (!mobileViewButtons) return;
3112    
3113    const isMobile = window.innerWidth <= 768;
3114    const hasCities = typeof allCities !== 'undefined' && allCities.length > 0;
3115    const isMapView = window.currentView === 'map';
3116    
3117    // ALWAYS hide on desktop (screen width > 768px)
3118    if (!isMobile) {
3119      mobileViewButtons.style.display = 'none';
3120      mobileViewButtons.classList.add('hidden');
3121      return;
3122    }
3123    
3124    // On mobile: show Map button when:
3125    // - Cities exist
3126    // - NOT already in map view (so user can switch to it)
3127    // The button is to switch TO map view, so show it whenever we're not in map view and have cities
3128    if (isMobile && hasCities && !isMapView) {
3129      mobileViewButtons.style.display = 'flex';
3130      mobileViewButtons.classList.remove('hidden');
3131    } else {
3132      mobileViewButtons.style.display = 'none';
3133      mobileViewButtons.classList.add('hidden');
3134    }
3135  }
3136
3137  // Handle window resize to ensure map fills container and scroll chat on mobile
3138  let lastMobileState = window.innerWidth <= 768;
3139  let generalResizeTimeout;
3140  window.addEventListener('resize', function() {
3141    // Check if we switched to mobile view
3142    const isMobileNow = window.innerWidth <= 768;
3143    if (isMobileNow && !lastMobileState) {
3144      // Just switched to mobile - scroll chat to bottom
3145      scrollChatToBottom();
3146    }
3147    lastMobileState = isMobileNow;
3148
3149    // Debounce and update mobile view buttons visibility on resize
3150    clearTimeout(generalResizeTimeout);
3151    generalResizeTimeout = setTimeout(() => {
3152      updateMobileViewButtonsVisibility();
3153    }, 100);
3154
3155    // Handle map resize
3156    if (map && mapInitialized) {
3157      setTimeout(() => {
3158        google.maps.event.trigger(map, 'resize');
3159      }, 100);
3160    }
3161  });
3162
3163  // Scroll chat to bottom on initial load if in mobile view
3164  if (window.innerWidth <= 768) {
3165    setTimeout(scrollChatToBottom, 500);
3166  }
3167
3168  // Function to restore the map view
3169  window.showMap = function() {
3170    const mapContainer = document.getElementById('map-container');
3171    if (!mapContainer) return;
3172
3173    mapContainer.innerHTML = `
3174      <div class="h-screen md:h-full map-container">
3175        <div id="map"></div>
3176      </div>
3177    `;
3178
3179    // Reinitialize the map
3180    setTimeout(() => {
3181      if (typeof google !== 'undefined' && google.maps) {
3182        initMap();
3183      }
3184    }, 100);
3185  };
3186
3187  // Function to update map location (can be called from chat responses)
3188  window.updateMapLocation = function(lat, lng, title = "New Location") {
3189    if (!map || !mapInitialized) {
3190      return;
3191    }
3192    
3193    const newCenter = { lat: parseFloat(lat), lng: parseFloat(lng) };
3194    map.setCenter(newCenter);
3195    if (window.innerWidth <= 768) {
3196      map.setZoom(2.5);
3197    } else {
3198      map.setZoom(3.5);
3199    }
3200    
3201    // Clear existing markers
3202    clearMapMarkers();
3203    
3204    // Add new marker
3205    new google.maps.Marker({
3206      position: newCenter,
3207      map: map,
3208      title: title
3209    });
3210  };
3211
3212  // ============================================
3213  // View Button Management - Refactored
3214  // ============================================
3215  
3216  // Current active view state
3217  window.currentView = 'map';
3218  
3219  // Get all view buttons
3220  function getViewButtons() {
3221    return {
3222      chat: document.getElementById('chatViewButton'),
3223      map: document.getElementById('mapViewButton'),
3224      list: document.getElementById('listViewButton'),
3225      table: document.getElementById('tableViewButton')
3226    };
3227  }
3228
3229  function getMobileViewButtons() {
3230    return {
3231      chat: document.getElementById('mobileChatViewButton'),
3232      map: document.getElementById('mobileMapViewButton'),
3233    };
3234  }
3235  
3236  // Get view containers
3237  function getViewContainers() {
3238    return {
3239      map: document.getElementById('map-container'),
3240      list: document.getElementById('list-container'),
3241      table: document.getElementById('table-container')
3242    };
3243  }
3244  
3245  // Set active button - ensures only one button is active at a time
3246  function setActiveButton(view) {
3247    const buttons = getViewButtons();
3248    
3249    // Remove active class from all buttons
3250    Object.values(buttons).forEach(button => {
3251      if (button) {
3252        button.classList.remove('active');
3253        button.disabled = false;
3254      }
3255    });
3256    
3257    // Add active class to the selected button (if view is not null)
3258    if (view && buttons[view]) {
3259      buttons[view].classList.add('active');
3260    }
3261  }
3262  
3263  // Show the selected view container
3264  function showView(view) {
3265    const containers = getViewContainers();
3266    
3267    // Hide all containers
3268    Object.values(containers).forEach(container => {
3269      if (container) {
3270        container.style.display = 'none';
3271      }
3272    });
3273    
3274    // Show the selected container
3275    if (containers[view]) {
3276      containers[view].style.display = 'block';
3277    }
3278    
3279    // Special handling for map view
3280    if (view === 'map') {
3281      const mapContainer = document.getElementById('map-container');
3282      const isMobile = window.innerWidth <= 768;
3283      
3284      // On mobile, ensure map container is properly positioned
3285      if (isMobile && mapContainer) {
3286        mapContainer.classList.add('map-view-active');
3287      }
3288      
3289      setTimeout(() => {
3290        if (!mapInitialized) {
3291          if (typeof showMap === 'function') { showMap(); }
3292          else if (typeof initMap === 'function') { initMap(); }
3293        }
3294        if (typeof google !== 'undefined' && map) {
3295          try { 
3296            google.maps.event.trigger(map, 'resize'); 
3297          } catch (_) {}
3298        }
3299      }, 100);
3300    } else {
3301      const mapContainer = document.getElementById('map-container');
3302      if (mapContainer) {
3303        mapContainer.classList.remove('map-view-active');
3304      }
3305    }
3306  }
3307  
3308  // Main function to switch views
3309  window.switchView = function(view) {
3310    // Validate view
3311    const validViews = ['chat', 'map', 'list', 'table'];
3312    if (!validViews.includes(view)) {
3313      console.warn('Invalid view:', view);
3314      return;
3315    }
3316    
3317    const isMobile = window.innerWidth <= 768;
3318    const mobileHeader = document.querySelector('header.md\\:hidden');
3319    const mobileFooter = document.getElementById('nomados-mobile-footer');
3320    const mobileViewButtons = document.getElementById('mobileViewButtonsContainer');
3321    const mobileBackToChatButton = document.getElementById('mobileBackToChatButton');
3322    
3323    // On mobile, if switching to a non-chat view and chat is expanded, collapse it
3324    if (view !== 'chat' && isMobile) {
3325      const chatContainer = document.getElementById('chat-container');
3326      if (chatContainer && mobileChatState === 'expanded') {
3327        const contentLayer = document.querySelector('.mobile-content-layer');
3328        const contentToggleBtn = document.getElementById('content-chat-toggle-btn');
3329        
3330        // Collapse chat
3331        chatContainer.classList.remove('expanded');
3332        chatContainer.classList.add('collapsed');
3333        mobileChatState = 'collapsed';
3334        
3335        // Show toggle button in content layer
3336        if (contentLayer) {
3337          contentLayer.classList.add('has-collapsed-chat');
3338        }
3339        if (contentToggleBtn) {
3340          contentToggleBtn.style.display = 'flex';
3341        }
3342      }
3343    }
3344    
3345    // On mobile, handle header and footer visibility based on view
3346    if (isMobile) {
3347      const mapContainer = document.getElementById('map-container');
3348      
3349      if (view === 'map') {
3350        // Hide header and footer in map view
3351        if (mobileHeader) {
3352          mobileHeader.style.display = 'none';
3353        }
3354        if (mobileFooter) {
3355          mobileFooter.style.display = 'none';
3356        }
3357        // Make map container fixed and full-screen on mobile
3358        if (mapContainer) {
3359          mapContainer.classList.add('map-view-active');
3360        }
3361        // Hide mobile view buttons, show back to chat button
3362        if (mobileViewButtons) {
3363          mobileViewButtons.style.display = 'none';
3364          mobileViewButtons.classList.add('hidden');
3365        }
3366        if (mobileBackToChatButton) {
3367          mobileBackToChatButton.style.display = 'flex';
3368        }
3369      } else {
3370        // Show header and footer in other views
3371        if (mobileHeader) {
3372          mobileHeader.style.display = 'block';
3373        }
3374        if (mobileFooter) {
3375          mobileFooter.style.display = 'block';
3376        }
3377        // Remove fixed positioning from map container
3378        if (mapContainer) {
3379          mapContainer.classList.remove('map-view-active');
3380        }
3381        // Update mobile view buttons visibility (will check screen size, cities, and view)
3382        updateMobileViewButtonsVisibility();
3383        if (mobileBackToChatButton) {
3384          mobileBackToChatButton.style.display = 'none';
3385        }
3386      }
3387    }
3388    
3389    // Update current view state
3390    window.currentView = view;
3391    
3392    // Update previous view before chat when switching to non-chat views
3393    // This ensures we can restore the correct view when collapsing chat
3394    if (view !== 'chat' && typeof previousViewBeforeChat !== 'undefined') {
3395      previousViewBeforeChat = view;
3396    }
3397    
3398    // Update button states
3399    setActiveButton(view);
3400    
3401    // Show the corresponding view (skip for chat as it's handled separately)
3402    if (view !== 'chat') {
3403      showView(view);
3404    }
3405  };
3406  
3407  // Initialize view buttons with event listeners
3408  function initializeViewButtons() {
3409    const buttons = getViewButtons();
3410    
3411    // Add click handlers to all buttons
3412    Object.entries(buttons).forEach(([view, button]) => {
3413      if (button) {
3414        button.addEventListener('click', function(e) {
3415          e.preventDefault();
3416          
3417          if (view === 'chat') {
3418            // Save the current view before switching to chat (if not already chat)
3419            // This is needed so we can restore it when collapsing chat
3420            if (window.currentView && window.currentView !== 'chat') {
3421              previousViewBeforeChat = window.currentView;
3422            }
3423            
3424            // First, switch view to deactivate other buttons
3425            switchView('chat');
3426            // Then toggle mobile chat (expand/collapse)
3427            if (typeof toggleMobileChat === 'function') {
3428              toggleMobileChat();
3429            }
3430          } else {
3431            // Switch to the selected view (this will deactivate chat button)
3432            switchView(view);
3433          }
3434        });
3435        
3436        // Ensure button is enabled
3437        button.disabled = false;
3438      }
3439    });
3440    
3441    // Set initial active state (map is default)
3442    setActiveButton('map');
3443    showView('map');
3444  }
3445  // Fallback client-side right panel rendering
3446  let allCities = [];
3447
3448  function cleanWidgetHTML(html) {
3449    const temp = document.createElement('div');
3450    temp.innerHTML = html;
3451    temp.querySelectorAll('script').forEach(s => s.remove());
3452    return temp.innerHTML;
3453  }
3454
3455  function extractCitiesFromMessage(messageElement) {
3456    if (!messageElement) return 0;
3457    const hiddenWidgetContainer = messageElement.querySelector('.hidden-full-widgets');
3458    allCities = [];
3459    if (hiddenWidgetContainer) {
3460      const fullWidgets = hiddenWidgetContainer.querySelectorAll('[data-city-widget]');
3461      fullWidgets.forEach(widget => {
3462        const slug = widget.getAttribute('data-city-slug');
3463        if (slug) allCities.push({ slug, html: widget.outerHTML });
3464      });
3465      return fullWidgets.length;
3466    }
3467    return 0;
3468  }
3469
3470  function updateListView() {
3471    const listContainer = document.getElementById('citiesListContainer');
3472    if (!listContainer) return;
3473    if (!allCities.length) {
3474      listContainer.innerHTML = `
3475        <div class="text-center text-gray-500 py-12">
3476          <p class="text-lg font-semibold mb-2">No destinations yet</p>
3477          <p class="text-sm">Start chatting to see recommended destinations</p>
3478        </div>
3479      `;
3480      return;
3481    }
3482    listContainer.innerHTML = allCities.map(c => cleanWidgetHTML(c.html)).join('');
3483  }
3484
3485  // Initialize with map view
3486  window.currentView = 'map';
3487  
3488  // Mobile chat toggle state (for chat.html page only)
3489  let mobileChatState = 'expanded'; // 'expanded' or 'collapsed'
3490  
3491  // Track the previous view before switching to chat (used when collapsing chat)
3492  let previousViewBeforeChat = 'map'; // Default to 'map'
3493  
3494  // Function to go back to chat from map view (closes any open panels f
3494irst)
3495  window.goBackToChatFromMap = function () {
3496    const isMobile = window.innerWidth <= 768;
3497    if (!isMobile) return; // Only works on mobile
3498
3499    function performBackToChat() {
3500      // Ensure panel is fully closed
3501      const panelElement = document.getElementById("nomados-panel");
3502      if (panelElement) {
3503        panelElement.classList.remove("open");
3504        if (typeof NomadosPanel !== "undefined") {
3505          NomadosPanel.isOpen = false;
3506        }
3507      }
3508
3509      // Hide map, list, and table containers
3510      const mapContainer = document.getElementById("map-container");
3511      const listContainer = document.getElementById("list-container");
3512      const tableContainer = document.getElementById("table-container");
3513      if (mapContainer) {
3514        mapContainer.style.display = "none";
3515        mapContainer.classList.remove("map-view-active");
3516      }
3517      if (listContainer) listContainer.style.display = "none";
3518      if (tableContainer) tableContainer.style.display = "none";
3519
3520      // Expand chat container to ensure it's visible
3521      const chatContainer = document.getElementById("chat-container");
3522      if (chatContainer) {
3523        chatContainer.classList.remove("collapsed");
3524        chatContainer.classList.add("expanded");
3525        mobileChatState = "expanded";
3526        // Force visibility and ensure it's on top
3527        chatContainer.style.display = "block";
3528        chatContainer.style.visibility = "visible";
3529        chatContainer.style.opacity = "1";
3530        chatContainer.style.zIndex = "150";
3531        chatContainer.style.transform = "translateY(0)";
3532        chatContainer.style.pointerEvents = "auto";
3533      }
3534
3535      // Switch view to chat (this handles header/footer/buttons)
3536      switchView("chat");
3537
3538      // Hide content layer toggle button
3539      const contentLayer = document.querySelector(".mobile-content-layer");
3540      const contentToggleBtn = document.getElementById("content-chat-toggle-btn");
3541      if (contentLayer) {
3542        contentLayer.classList.remove("has-collapsed-chat");
3543      }
3544      if (contentToggleBtn) {
3545        contentToggleBtn.style.display = "none";
3546      }
3547
3548      // Update mobile view buttons visibility when going back to chat
3549      setTimeout(() => {
3550        updateMobileViewButtonsVisibility();
3551      }, 100);
3552    }
3553
3554    // Close panel if open
3555    if (
3556      typeof NomadosPanel !== "undefined" &&
3557      NomadosPanel.isOpen &&
3558      typeof closeNomadosPanel === "function"
3559    ) {
3560      closeNomadosPanel();
3561      // Wait for panel to fully close (animation + cleanup)
3562      setTimeout(() => {
3563        performBackToChat();
3564      }, 400);
3565    } else {
3566      // No panel open, switch to chat immediately
3567      performBackToChat();
3568    }
3569  };
3570  
3571  // Toggle mobile chat - compatible with both base.html MobileModalState and chat.html local implementation
3572  // This function detects which system is available and uses the appropriate implementation
3573  window.toggleMobileChat = function() {
3574    // Check if we're on the chat.html page (has chat-container)
3575    const chatContainer = document.getElementById('chat-container');
3576    const mobileModalContainer = document.getElementById('mobile-modal-container');
3577    
3578    // If chat-container exists, use chat.html's local implementation
3579    if (chatContainer) {
3580      const contentLayer = document.querySelector('.mobile-content-layer');
3581      const contentToggleBtn = document.getElementById('content-chat-toggle-btn');
3582      const chatViewButton = document.getElementById('chatViewButton');
3583      
3584      if (mobileChatState === 'expanded') {
3585        // Collapse chat (hide completely)
3586        chatContainer.classList.remove('expanded');
3587        chatContainer.classList.add('collapsed');
3588        mobileChatState = 'collapsed';
3589        chatContainer.style.transform = 'translateY(100%)';
3590        chatContainer.style.pointerEvents = 'none';
3591        
3592        // Update button states - use previous view before chat, not currentView (which is 'chat')
3593        const viewToShow = previousViewBeforeChat || window.currentView || 'map';
3594        window.currentView = viewToShow;
3595        if (typeof setActiveButton === 'function') {
3596          setActiveButton(viewToShow);
3597        }
3598        if (typeof showView === 'function') {
3599          showView(viewToShow);
3600        }
3601        
3602        // Show toggle button in content layer
3603        if (contentLayer) {
3604          contentLayer.classList.add('has-collapsed-chat');
3605        }
3606        if (contentToggleBtn) {
3607          contentToggleBtn.style.display = 'flex';
3608        }
3609      } else {
3610        // Expand chat (full screen)
3611        chatContainer.classList.remove('collapsed');
3612        chatContainer.classList.add('expanded');
3613        mobileChatState = 'expanded';
3614        chatContainer.style.transform = 'translateY(0)';
3615        chatContainer.style.pointerEvents = 'auto';
3616        chatContainer.style.visibility = 'visible';
3617        chatContainer.style.opacity = '1';
3618        chatContainer.style.zIndex = '150';
3619        
3620        // Set chat button as active when chat is expanded (this will deactivate other buttons)
3621        if (typeof setActiveButton === 'function') {
3622          setActiveButton('chat');
3623        }
3624        
3625        // Hide toggle button in content layer
3626        if (contentLayer) {
3627          contentLayer.classList.remove('has-collapsed-chat');
3628        }
3629        if (contentToggleBtn) {
3630          contentToggleBtn.style.display = 'none';
3631        }
3632        
3633        // Update mobile view buttons visibility when chat is expanded
3634        setTimeout(() => {
3635          updateMobileViewButtonsVisibility();
3636        }, 100);
3637      }
3638      return; // Exit early, handled by chat.html implementation
3639    }
3640    
3641    // Otherwise, check if MobileModalState exists (from base.html)
3642    if (window.MobileModalState && mobileModalContainer) {
3643      // Use base.html's MobileModalState implementation
3644      console.log("toggleMobileChat: Using MobileModalState");
3645      window.MobileModalState.switchContent('chat');
3646      window.MobileModalState.cycle();
3647      return; // Exit early, handled by base.html implementation
3648    }
3649    
3650    // Fallback: if neither system is available, log a warning
3651    console.warn('toggleMobileChat: No compatible mobile chat system found');
3652  };
3653  
3654  // Initialize view buttons and mobile chat state on load
3655  document.addEventListener('DOMContentLoaded', function() {
3656    // Only initialize if we're on the chat.html page (has chat-container)
3657    const chatContainer = document.getElementById('chat-container');
3658    if (!chatContainer) {
3659      // Not on chat.html page, don't initialize chat.html-specific features
3660      return;
3661    }
3662    
3663    // Immediately ensure mobile view buttons are hidden on desktop, shown correctly on mobile
3664    updateMobileViewButtonsVisibility();
3665    
3666    // Initialize view buttons
3667    if (typeof initializeViewButtons === 'function') {
3668      initializeViewButtons();
3669    }
3670    
3671    // Handle mobile-specific initialization
3672    if (window.innerWidth <= 768) {
3673      const contentLayer = document.querySelector('.mobile-content-layer');
3674      const contentToggleBtn = document.getElementById('content-chat-toggle-btn');
3675      const mobileViewButtons = document.getElementById('mobileViewButtonsContainer');
3676      const mobileBackToChatButton = document.getElementById('mobileBackToChatButton');
3677      
3678      chatContainer.classList.add('expanded');
3679      mobileChatState = 'expanded';
3680      window.currentView = 'chat';
3681      // Ensure initial styles are set
3682      chatContainer.style.transform = 'translateY(0)';
3683      chatContainer.style.pointerEvents = 'auto';
3684      chatContainer.style.visibility = 'visible';
3685      chatContainer.style.opacity = '1';
3686      chatContainer.style.zIndex = '150';
3687      
3688      // Set chat button as active on mobile when chat is expanded (this will deactivate other buttons)
3689      if (typeof setActiveButton === 'function') {
3690        setActiveButton('chat');
3691      }
3692      
3693      // Ensure toggle button in content layer is hidden initially
3694      if (contentLayer) {
3695        contentLayer.classList.remove('has-collapsed-chat');
3696      }
3697      if (contentToggleBtn) {
3698        contentToggleBtn.style.display = 'none';
3699      }
3700      
3701      // Update mobile view buttons visibility (will hide initially since no cities yet)
3702      updateMobileViewButtonsVisibility();
3703      
3704      // Hide back to chat button initially (only shown in map view)
3705      if (mobileBackToChatButton) {
3706        mobileBackToChatButton.style.display = 'none';
3707      }
3708    }
3709  });
3710  
3711  // Handle window resize to maintain state (only for chat.html page)
3712  // Use a debounced resize handler to avoid too many calls
3713  let resizeTimeout;
3714  window.addEventListener('resize', function() {
3715    const chatContainer = document.getElementById('chat-container');
3716    
3717    // Only handle resize if we're on the chat.html page
3718    if (!chatContainer) return;
3719    
3720    // Debounce resize events
3721    clearTimeout(resizeTimeout);
3722    resizeTimeout = setTimeout(() => {
3723      const contentLayer = document.querySelector('.mobile-content-layer');
3724      const contentToggleBtn = document.getElementById('content-chat-toggle-btn');
3725      
3726      if (window.innerWidth <= 768) {
3727        // Mobile view
3728        if (!chatContainer.classList.contains('expanded') && !chatContainer.classList.contains('collapsed')) {
3729          chatContainer.classList.add('expanded');
3730          mobileChatState = 'expanded';
3731        }
3732        
3733        // If we have cities and are in map view, switch to chat view to show the map button
3734        const hasCities = typeof allCities !== 'undefined' && allCities.length > 0;
3735        if (hasCities && window.currentView === 'map' && mobileChatState === 'expanded') {
3736          // Don't actually switch view, but ensure chat is the active view for button visibility
3737          window.currentView = 'chat';
3738          if (typeof setActiveButton === 'function') {
3739            setActiveButton('chat');
3740          }
3741        }
3742        
3743        // Update button states based on mobile chat state
3744        if (mobileChatState === 'collapsed') {
3745          if (contentLayer) contentLayer.classList.add('has-collapsed-chat');
3746          if (contentToggleBtn) contentToggleBtn.style.display = 'flex';
3747          // Show current view button as active
3748          const currentView = window.currentView || 'map';
3749          if (typeof setActiveButton === 'function') {
3750            setActiveButton(currentView);
3751          }
3752        } else {
3753          if (contentLayer) contentLayer.classList.remove('has-collapsed-chat');
3754          if (contentToggleBtn) contentToggleBtn.style.display = 'none';
3755          // Show chat button as active (this will deactivate other buttons)
3756          if (typeof setActiveButton === 'function') {
3757            setActiveButton('chat');
3758          }
3759          // Ensure currentView is set to chat when chat is expanded
3760          if (hasCities) {
3761            window.currentView = 'chat';
3762          }
3763        }
3764      } else {
3765        // Desktop view - remove mobile classes
3766        chatContainer.classList.remove('expanded', 'collapsed');
3767        if (contentLayer) contentLayer.classList.remove('has-collapsed-chat');
3768        if (contentToggleBtn) contentToggleBtn.style.display = 'none';
3769        // Show current view button as active
3770        const currentView = window.currentView || 'map';
3771        if (typeof setActiveButton === 'function') {
3772          setActiveButton(currentView);
3773        }
3774      }
3775      
3776      // Update mobile view buttons visibility on resize (with a small delay to ensure state is updated)
3777      setTimeout(() => {
3778        updateMobileViewButtonsVisibility();
3779      }, 50);
3780    }, 150);
3781  });
3782
3783  // Destination Detail Panel Functions
3784  window.openDestinationDetail = function(citySlug) {
3785    const panel = document.getElementById('destination-detail-panel');
3786    const content = document.getElementById('destination-detail-content');
3787    
3788    if (!panel || !content) {
3789      console.error('Panel or content element not found');
3790      return;
3791    }
3792    
3793    console.log('Opening destination detail for:', citySlug);
3794    
3795    // Show loading state
3796    content.innerHTML = '<div class="text-center py-12"><p class="text-gray-500">Loading destination details...</p></div>';
3797    panel.classList.add('open');
3798    
3799    // Fetch destination details
3800    fetch(`/chat/destinations/${citySlug}/`, {
3801      headers: {
3802        'X-Requested-With': 'XMLHttpRequest',
3803      }
3804    })
3805    .then(response => {
3806      if (!response.ok) {
3807        throw new Error(`HTTP error! status: ${response.status}`);
3808      }
3809      return response.text();
3810    })
3811    .then(html => {
3812      console.log('Received HTML, length:', html.length);
3813      
3814      // Extract just the main content (without navigation, etc.)
3815      const temp = document.createElement('div');
3816      temp.innerHTML = html;
3817      
3818      // Find the main content area - look for destination-content ID
3819      let mainContent = temp.querySelector('#destination-content');
3820      
3821      // Debug: log what we found
3822      if (mainContent) {
3823        console.log('Found #destination-content');
3824      } else {
3825        console.log('Searching in temp:', temp);
3826        console.log('All IDs in temp:', Array.from(temp.querySelectorAll('[id]')).map(el => el.id));
3827        
3828        // Try to find it in a different way
3829        const body = temp.querySelector('body');
3830        if (body) {
3831          mainContent = body.querySelector('#destination-content');
3832        }
3833      }
3834      
3835      // If still not found, try other selectors
3836      if (!mainContent) {
3837        mainContent = temp.querySelector('[class*="destination-content"]');
3838      }
3839      
3840      // If still not found, look for the main content wrapper
3841      if (!mainContent) {
3842        const wrappers = temp.querySelectorAll('.w-full');
3843        mainContent = wrappers[0];
3844      }
3845      
3846      // If still not found, just use the body content
3847      if (!mainContent) {
3848        mainContent = temp.querySelector('body') || temp;
3849      }
3850      
3851      console.log('Found content element:', mainContent);
3852      
3853      // Clean and inject content
3854      if (mainContent && mainContent.innerHTML) {
3855        content.innerHTML = mainContent.innerHTML;
3856        console.log('Content injected, length:', content.innerHTML.length);
3857      } else {
3858        console.warn('No content found in response');
3859        content.innerHTML = '<div class="text-center py-12"><p class="text-red-500">No content available</p></div>';
3860      }
3861    })
3862    .catch(error => {
3863      console.error('Error loading destination details:', error);
3864      content.innerHTML = '<div class="text-center py-12"><p class="text-red-500">Error loading destination details: ' + error.message + '</p></div>';
3865    });
3866  };
3867
3868  // -------------------------------
3869  // Filters Modal Logic (reused)
3870  // -------------------------------
3871  const selectedFilters = {
3872    continent: [],
3873    climate: [],
3874    budget: [],
3875    internet: [],
3876    safety: [],
3877    nomads: [],
3878    english: [],
3879    visa: [],
3880    political: [],
3881    healthcare: []
3882  };
3883
3884  window.openFiltersModal = function() {
3885    const modal = document.getElementById('filtersModal');
3886    if (!modal) return;
3887    modal.classList.remove('hidden');
3888    modal.style.display = 'flex';
3889    setTimeout(() => {
3890      modal.classList.remove('opacity-0', 'scale-95');
3891      modal.classList.add('opacity-100', 'scale-100');
3892    }, 10);
3893  };
3894
3895  window.closeFiltersModal = function() {
3896    const modal = document.getElementById('filtersModal');
3897    if (!modal) return;
3898    modal.classList.add('opacity-0', 'scale-95');
3899    modal.classList.remove('opacity-100', 'scale-100');
3900    setTimeout(() => {
3901      modal.classList.add('hidden');
3902      modal.style.display = 'none';
3903    }, 200);
3904  };
3905
3906  function disableAllFilters() {
3907    const filterButtons = document.querySelectorAll('#filtersModal .filter-option');
3908    filterButtons.forEach(b => { b.disabled = true; b.style.opacity = '0.5'; b.style.cursor = 'not-allowed'; });
3909  }
3910
3911  // Build a simple table view from currently extracted cities (uses data attributes in widgets)
3912  function renderTableView() {
3913    console.log('renderTableView');
3914    const container = document.getElementById('destinationsTableContainer');
3915    console.log('destinationsTableContainer', container);
3916    if (!container) return;
3917
3918    // If no cities, show empty state
3919    if (!allCities || allCities.length === 0) {
3920      container.innerHTML = `
3921        <div class="text-center text-gray-500 py-12">
3922          <p class="text-lg font-semibold mb-2">No destinations yet</p>
3923          <p class="text-sm">Start chatting to see recommended destinations</p>
3924        </div>
3925      `;
3926      return;
3927    }
3928
3929    // Create a temporary wrapper to parse the city widgets
3930    const temp = document.createElement('div');
3931    temp.innerHTML = allCities.map(c => cleanWidgetHTML(c.html)).join('');
3932
3933    const rows = Array.from(temp.querySelectorAll('[data-city-widget]')).map(widget => {
3934      const name = widget.getAttribute('data-city-name') || '';
3935      const country = widget.getAttribute('data-country-name') || '';
3936      const budget = widget.getAttribute('data-budget') || '';
3937      const climate = widget.getAttribute('data-climate') || '';
3938      const internet = widget.getAttribute('data-internet') || '';
3939      const safety = widget.getAttribute('data-safety') || '';
3940      const nomads = widget.getAttribute('data-nomads') || '';
3941      const slug = widget.getAttribute('data-city-slug') || '';
3942      const coverImg = widget.querySelector('div[style*="background-image:"]');
3943      const photo = coverImg ? (coverImg.getAttribute('style') || '').replace(/.*url\(['"]?(.*?)['"]?\).*/, '$1') : '';
3944      return { name, country, budget, climate, internet, safety, nomads, slug, photo };
3945    });
3946
3947    const tableHead = `
3948      <thead class="bg-gray-50">
3949        <tr>
3950          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">City</th>
3951          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Country</th>
3952          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Budget</th>
3953          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Climate</th>
3954          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Internet</th>
3955          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Safety</th>
3956          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nomads</th>
3957          <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
3958        </tr>
3959      </thead>
3960    `;
3961
3962    const rowHtml = rows.map(r => `
3963      <tr class="hover:bg-gray-50 transition-colors duration-150">
3964        <td class="px-4 py-3">
3965          <div class="flex items-center">
3966            ${r.photo ? `<img class=\"h-8 w-8 rounded-md object-cover mr-2\" src=\"${r.ph
3966oto}\" alt=\"${r.name}\">` : ''}
3967            <span class="text-sm font-medium text-gray-900">${r.name}</span>
3968          </div>
3969        </td>
3970        <td class="px-4 py-3 text-sm text-gray-700">${r.country}</td>
3971        <td class="px-4 py-3 text-sm text-gray-700">${r.budget ? `~$${Number(r.budget).toFixed(0)}/mo` : ''}</td>
3972        <td class="px-4 py-3 text-sm text-gray-700">${r.climate || ''}</td>
3973        <td class="px-4 py-3 text-sm text-gray-700">${r.internet || ''}</td>
3974        <td class="px-4 py-3 text-sm text-gray-700">${r.safety || ''}</td>
3975        <td class="px-4 py-3 text-sm text-gray-700">${r.nomads || ''}</td>
3976        <td class="px-4 py-3 text-sm font-medium">
3977          ${r.slug ? `<button type=\"button\" class=\"text-blue-600 hover:text-blue-800\" onclick=\"openDestinationDetail('${r.slug}')\">View</button>` : ''}
3978        </td>
3979      </tr>
3980    `).join('');
3981
3982    const tableHtml = `
3983      <div class="table-responsive overflow-x-auto overflow-y-auto" style="height: 100%; max-height: 100%; overflow-y: auto; overflow-x: auto;">
3984        <table class="w-full bg-white rounded-lg shadow-sm border border-gray-200 min-w-max">
3985          ${tableHead}
3986          <tbody class="bg-white divide-y divide-gray-200">
3987            ${rowHtml}
3988          </tbody>
3989        </table>
3990      </div>
3991    `;
3992
3993    container.innerHTML = tableHtml;
3994  }
3995
3996  function enableAllFilters() {
3997    const filterButtons = document.querySelectorAll('#filtersModal .filter-option');
3998    filterButtons.forEach(b => { b.disabled = false; b.style.opacity = '1'; b.style.cursor = 'pointer'; });
3999  }
4000
4001  function updateFormInputs() {
4002    const form = document.getElementById('modalFilterForm');
4003    if (!form) return;
4004    Object.keys(selectedFilters).forEach(type => {
4005      const input = form.querySelector(`input[name="${type}"]`);
4006      if (input) input.value = selectedFilters[type].join(',');
4007    });
4008  }
4009
4010  window.toggleFilter = function(button) {
4011    try {
4012      const filterType = button.dataset.filter;
4013      const filterValue = button.dataset.value;
4014      if (!filterType || !filterValue) return;
4015
4016      disableAllFilters();
4017      button.classList.toggle('filter-active');
4018
4019      if (button.classList.contains('filter-active')) {
4020        if (!selectedFilters[filterType].includes(filterValue)) {
4021          selectedFilters[filterType].push(filterValue);
4022        }
4023      } else {
4024        const idx = selectedFilters[filterType].indexOf(filterValue);
4025        if (idx > -1) selectedFilters[filterType].splice(idx, 1);
4026      }
4027
4028      updateFormInputs();
4029
4030      // Optimistic apply without closing; user can press Apply for fetch
4031      enableAllFilters();
4032    } catch (e) {
4033      enableAllFilters();
4034    }
4035  };
4036
4037  window.resetAllFilters = function() {
4038    Object.keys(selectedFilters).forEach(k => { selectedFilters[k] = []; });
4039    document.querySelectorAll('#filtersModal .filter-option').forEach(btn => btn.classList.remove('filter-active'));
4040    updateFormInputs();
4041  };
4042
4043  window.applyCurrentFiltersFromModal = function() {
4044    try {
4045      disableAllFilters();
4046      const form = document.getElementById('modalFilterForm');
4047      if (!form) { enableAllFilters(); return; }
4048
4049      // Ensure inputs are in sync
4050      updateFormInputs();
4051
4052      // Build a natural-language question from selected filters
4053      const question = buildFiltersQuestion();
4054
4055      // Inject into chat input and submit via HTMX
4056      const chatInput = document.getElementById('messageInput');
4057      const chatForm = document.getElementById('chatForm');
4058      if (chatInput && chatForm) {
4059        chatInput.value = question;
4060        // On mobile, ensure chat is expanded so the input/submission works
4061        if (window.innerWidth <= 768 && typeof toggleMobileChat === 'function') {
4062          const chatContainer = document.getElementById('chat-container');
4063          if (chatContainer && mobileChatState === 'collapsed') {
4064            toggleMobileChat();
4065          }
4066        }
4067        // Close filters and submit
4068        closeFiltersModal();
4069        htmx.trigger(chatForm, 'submit');
4070      }
4071
4072      enableAllFilters();
4073    } catch (e) {
4074      enableAllFilters();
4075    }
4076  };
4077
4078  function buildFiltersQuestion() {
4079    const parts = [];
4080
4081    const labelMap = {
4082      continent: {
4083        key: 'continents',
4084        value: {
4085          'europe': 'Europe', 'asia': 'Asia', 'north-america': 'North America', 'south-america': 'South America', 'africa': 'Africa'
4086        }
4087      },
4088      budget: { key: 'monthly budget', value: { '0-1000': '< $1k', '1000-2000': '$1k–$2k', '2000-3000': '$2k–$3k', '3000+': '> $3k' } },
4089      climate: { key: 'climate', value: { 'TROPICAL': 'Tropical', 'TEMPERATE': 'Temperate', 'MEDITERRANEAN': 'Mediterranean', 'SUBTROPICAL': 'Subtropical', 'ARID': 'Arid', 'COLD': 'Cold', 'CONTINENTAL': 'Continental', 'DESERT': 'Desert', 'MOUNTAIN': 'Mountain', 'WARM': 'Warm' } },
4090      internet: { key: 'internet', value: { 'ULTRA_FAST': 'Ultra Fast', 'FAST': 'Fast', 'BASIC': 'Basic' } },
4091      safety: { key: 'safety', value: { 'HIGH': 'High', 'MOD': 'Moderate', 'LOW': 'Low' } },
4092      nomads: { key: 'nomad community', value: { 'HIGH': 'Large', 'MOD': 'Growing', 'LOW': 'Small' } },
4093      english: { key: 'english speaking', value: { 'HIGH': 'High', 'MOD': 'Moderate', 'LOW': 'Low' } },
4094      healthcare: { key: 'healthcare', value: { 'HIGH': 'High', 'MOD': 'Moderate', 'LOW': 'Low' } },
4095      visa: { key: 'visa', value: { 'required': 'Required', 'not_required': 'Not Required' } }
4096    };
4097
4098    Object.keys(selectedFilters).forEach(type => {
4099      const values = selectedFilters[type];
4100      if (!values || values.length === 0 || !labelMap[type]) return;
4101      const human = values.map(v => (labelMap[type].value && labelMap[type].value[v]) ? labelMap[type].value[v] : v).join(', ');
4102      parts.push(`${labelMap[type].key}: ${human}`);
4103    });
4104
4105    if (parts.length === 0) {
4106      return 'Recommend digital nomad destinations that are great for me.';
4107    }
4108
4109    return `Recommend digital nomad destinations matching these preferences: ${parts.join('; ')}.`;
4110  }
4111
4112  // =============================================
4113  // Text Truncation for Destination Widgets
4114  // =============================================
4115  
4116  function truncateTextByScreenWidth() {
4117    const descriptionElements = document.querySelectorAll('.description-text');
4118    const screenWidth = window.innerWidth;
4119    
4120    descriptionElements.forEach(element => {
4121      const fullText = element.getAttribute('data-full-text');
4122      if (!fullText) return;
4123      
4124      let charLimit;
4125      if (screenWidth >= 1920) {
4126        charLimit = 115;
4127      } else if (screenWidth >= 1440) {
4128        charLimit = 80;
4129      } else {
4130        charLimit = 40;
4131      }
4132      
4133      const chars = fullText.split('');
4134      if (chars.length > charLimit) {
4135        const truncatedChars = chars.slice(0, charLimit);
4136        element.textContent = truncatedChars.join('') + '...';
4137      } else {
4138        element.textContent = fullText;
4139      }
4140    });
4141  }
4142  
4143  // Make it globally available
4144  window.truncateTextByScreenWidth = truncateTextByScreenWidth;
4145  
4146  // Also store in DestinationWidget namespace for compatibility
4147  if (!window.DestinationWidget) {
4148    window.DestinationWidget = {};
4149  }
4150  window.DestinationWidget.truncateTextByScreenWidth = truncateTextByScreenWidth;
4151  
4152  // Run truncation on page load
4153  document.addEventListener('DOMContentLoaded', function() {
4154    setTimeout(truncateTextByScreenWidth, 100);
4155  });
4156  
4157  // Run truncation on window resize (debounced)
4158  let truncateResizeTimeout;
4159  window.addEventListener('resize', function() {
4160    clearTimeout(truncateResizeTimeout);
4161    truncateResizeTimeout = setTimeout(truncateTextByScreenWidth, 150);
4162  });
4163
4164  // =============================================
4165  // Destination Filter Workflow Functions
4166  // =============================================
4167  
4168  window.filterWorkflowActive = false;
4169  window.filterWizardUsed = false;
4170
4171  // Start the destination filter workflow
4172  window.startDestinationFilter = function() {
4173    if (filterWorkflowActive) return;
4174    filterWorkflowActive = true;
4175    filterWizardUsed = true;
4176    
4177    const chatMessages = document.getElementById('chatMessages');
4178    const typingIndicator = document.getElementById('typingIndicator');
4179    const startButton = document.getElementById('startFilterWorkflow');
4180    
4181    
4182    // Disable the start button and swap icon to static SVG
4183    if (startButton) {
4184      startButton.disabled = true;
4185      startButton.style.opacity = '0.5';
4186      startButton.style.cursor = 'not-allowed';
4187      var btnIcon = startButton.querySelector('img');
4188      if (btnIcon) btnIcon.src = '/static/icons/chevron_right_dark.svg';
4189    }
4190
4191    // Hide sample questions when filter wizard starts
4192    var sampleQEl = document.getElementById('sampleQuestions');
4193    if (sampleQEl) {
4194      sampleQEl.style.display = 'none';
4195    }
4196    
4197    // Show typing indicator (use class like HTMX does)
4198    if (typingIndicator) {
4199      typingIndicator.classList.add('show');
4200    }
4201    // Change chat gif to thinking
4202    if (typeof changeChatGif === 'function') {
4203      changeChatGif('thinking');
4204    }
4205    
4206    // Get CSRF token
4207    const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
4208    
4209    // Call the filter start endpoint
4210    fetch('/chat/filter/start/', {
4211      method: 'POST',
4212      headers: {
4213        'X-CSRFToken': csrfToken,
4214        'Content-Type': 'application/x-www-form-urlencoded',
4215      },
4216      body: ''
4217    })
4218    .then(response => response.text())
4219    .then(html => {
4220      // Hide typing indicator
4221      if (typingIndicator) {
4222        typingIndicator.classList.remove('show');
4223      }
4224      // Reset chat gif
4225      if (typeof changeChatGif === 'function') {
4226        changeChatGif('idle');
4227      }
4228      
4229      // Append the filter step to chat
4230      if (chatMessages) {
4231        chatMessages.insertAdjacentHTML('beforeend', html);
4232        chatMessages.scrollTop = chatMessages.scrollHeight;
4233        setTimeout(function() { chatMessages.scrollTop = chatMessages.scrollHeight; }, 100);
4234      }
4235    })
4236    .catch(error => {
4237      console.error('Error starting filter workflow:', error);
4238      filterWorkflowActive = false;
4239      
4240      // Hide typing indicator
4241      if (typingIndicator) {
4242        typingIndicator.classList.remove('show');
4243      }
4244      // Reset chat gif
4245      if (typeof changeChatGif === 'function') {
4246        changeChatGif('idle');
4247      }
4248      
4249      // Re-enable the start button
4250      if (startButton) {
4251        startButton.disabled = false;
4252        startButton.style.opacity = '1';
4253        startButton.style.cursor = 'pointer';
4254      }
4255    });
4256  };
4257  
4258  // Toggle a filter option on/off (multi-select)
4259  window.toggleFilterOption = function(button) {
4260    const step = button.dataset.filterStep;
4261
4262    const optionsContainer = document.getElementById(`filterOptions-${step}`);
4263    const isSelected = button.classList.contains('selected');
4264    const maxSelect = optionsContainer ? parseInt(optionsContainer.dataset.maxSelect || '0', 10) : 0;
4265
4266    // Enforce max selection limit — if at max and trying to select a new one, block it
4267    if (!isSelected && maxSelect > 0) {
4268      const currentCount = optionsContainer.querySelectorAll('.selected').length;
4269      if (currentCount >= maxSelect) return;
4270    }
4271
4272    button.classList.toggle('selected', !isSelected);
4273
4274    // Enable / disable the Continue button based on whether anything is selected
4275    const nextBtn = document.getElementById(`filterNext-${step}`);
4276    if (nextBtn && optionsContainer) {
4277      const hasSelection = optionsContainer.querySelectorAll('.selected').length > 0;
4278      const continueBtn = nextBtn.querySelector('button');
4279      if (continueBtn) {
4280        continueBtn.disabled = !hasSelection;
4281      }
4282    }
4283
4284    // Scroll chat to bottom smoothly
4285    const chatMessages = document.getElementById('chatMessages');
4286    if (chatMessages) {
4287      setTimeout(function() { chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' }); }, 50);
4288    }
4289  };
4290
4291  // Submit all selected options for a filter step
4292  window.submitFilterStep = function(step, filterType) {
4293    const optionsContainer = document.getElementById(`filterOptions-${step}`);
4294    if (!optionsContainer) return;
4295
4296    const selectedButtons = Array.from(optionsContainer.querySelectorAll('.selected'));
4297    if (selectedButtons.length === 0) return;
4298
4299    const values = selectedButtons.map(b => b.dataset.filterValue);
4300    const labels = selectedButtons.map(b => b.dataset.filterLabel);
4301
4302    const chatMessages = document.getElementById('chatMessages');
4303    const typingIndicator = document.getElementById('typingIndicator');
4304
4305    // Lock all option buttons and disable Continue
4306    const allButtons = optionsContainer.querySelectorAll('button');
4307    allButtons.forEach(btn => { btn.disabled = true; });
4308    const nextBtn = document.getElementById(`filterNext-${step}`);
4309    if (nextBtn) {
4310      var continueBtn = nextBtn.querySelector('button');
4311      if (continueBtn) continueBtn.disabled = true;
4312    }
4313
4314    if (typingIndicator) typingIndicator.classList.add('show');
4315    if (typeof changeChatGif === 'function') changeChatGif('thinking');
4316    if (chatMessages) chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
4317
4318    const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
4319    const formData = new URLSearchParams();
4320    formData.append('step', step);
4321    formData.append('filter_type', filterType);
4322    formData.append('filter_values', JSON.stringify(values));
4323    formData.append('filter_labels', JSON.stringify(labels));
4324
4325    fetch('/chat/filter/step/', {
4326      method: 'POST',
4327      headers: {
4328        'X-CSRFToken': csrfToken,
4329        'Content-Type': 'application/x-www-form-urlencoded',
4330      },
4331      body: formData.toString()
4332    })
4333    .then(response => response.text())
4334    .then(html => {
4335      if (typingIndicator) typingIndicator.classList.remove('show');
4336      if (typeof changeChatGif === 'function') changeChatGif('idle');
4337
4338      const isFinalStep = html.includes('data-city-pins') || html.includes('data-chat-style');
4339
4340      let cleanHtml = html;
4341      if (isFinalStep) {
4342        cleanHtml = processFilterResponse(html);
4343        filterWorkflowActive = false;
4344        if (typeof updateMobileViewButtonsVisibility === 'function') {
4345          updateMobileViewButtonsVisibility();
4346        }
4347      }
4348
4349      if (chatMessages) {
4350        chatMessages.insertAdjacentHTML('beforeend', cleanHtml);
4351        chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
4352        setTimeout(function() { chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' }); }, 100);
4353        setTimeout(function() { chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' }); }, 300);
4354        setTimeout(truncateTextByScreenWidth, 50);
4355        setTimeout(truncateTextByScreenWidth, 150);
4356        setTimeout(truncateTextByScreenWidth, 300);
4357      }
4358    })
4359    .catch(error => {
4360      console.error('Error processing filter step:', error);
4361      if (typingIndicator) typingIndicator.classList.remove('show');
4362      if (typeof changeChatGif === 'function') changeChatGif('idle');
4363      // Re-enable buttons on error
4364      allButtons.forEach(btn => { btn.disabled = false; });
4365      if (nextBtn) {
4366        var cBtn = nextBtn.querySelector('button');
4367        if (cBtn) cBtn.disabled = false;
4368      }
4369    });
4370  };
4371  
4372  // Process and clean HTML response - extract OOB elements and return clean chat HTML
4373  function processFilterResponse(html) {
4374    const temp = document.createElement('div');
4375    temp.innerHTML = html;
4376    
4377    // Process OOB updates for destinations table - move to right panel
4378    const tableContainer = temp.querySelector('#destinationsTableContainer');
4379    if (tableContainer) {
4380      const existingTable = document.getElementById('destinationsTableContainer');
4381      if (existingTable) {
4382        existingTable.innerHTML = tableContainer.innerHTML;
4383      }
4384      // Remove from temp so it doesn't get inserted into chat
4385      tableContainer.remove();
4386    }
4387    
4388    // Process OOB updates for cities list - move to right panel
4389    const listContainer = temp.querySelector('#citiesListContainer');
4390    if (listContainer) {
4391      const existingList = document.getElementById('citiesListContainer');
4392      if (existingList) {
4393        existingList.innerHTML = listContainer.innerHTML;
4394        
4395        // Trigger text truncation for destination widgets in the list
4396        setTimeout(truncateTextByScreenWidth, 50);
4397        setTimeout(truncateTextByScreenWidth, 150);
4398        setTimeout(truncateTextByScreenWidth, 300);
4399      }
4400      // Remove from temp so it doesn't get inserted into chat
4401      listContainer.remove();
4402    }
4403    
4404    // Process city pins for map
4405    const cityPinsWrapper = temp.querySelector('#cityPins');
4406    if (cityPinsWrapper) {
4407      const cityPinsContainer = cityPinsWrapper.querySelector('[data-city-pins]');
4408      if (cityPinsContainer) {
4409        const pins = cityPinsContainer.querySelectorAll('[data-city-pin]');
4410        if (pins.length > 0) {
4411          const cities = Array.from(pins).map(pin => ({
4412            lat: parseFloat(pin.dataset.lat),
4413            lng: parseFloat(pin.dataset.lng),
4414            title: pin.dataset.title,
4415            comment: pin.dataset.comment,
4416            cityName: pin.dataset.cityName,
4417            countryName: pin.dataset.countryName,
4418            citySlug: pin.dataset.citySlug,
4419            coverPhoto: pin.dataset.coverPhoto
4420          }));
4421          
4422          // Store cities for view switching
4423          if (typeof allCities === 'undefined') {
4424            window.allCities = [];
4425          }
4426          
4427          // Add new cities to the list
4428          cities.forEach(city => {
4429            if (!allCities.some(c => c.citySlug === city.citySlug)) {
4430              allCities.push({
4431                lat: city.lat,
4432                lng: city.lng,
4433                title: city.title,
4434                citySlug: city.citySlug,
4435                cityName: city.cityName,
4436                countryName: city.countryName,
4437                coverPhoto: city.coverPhoto,
4438                comment: city.comment
4439              });
4440            }
4441          });
4442          
4443          // Add pins to map and switch to map view
4444          if (typeof clearMapMarkers === 'function') {
4445            clearMapMarkers();
4446          }
4447          cities.forEach(city => {
4448            if (typeof addCityPin === 'function') {
4449              addCityPin(city);
4450            }
4451          });
4452          // Switch to map view on desktop only; mobile stays in chat
4453          if (typeof switchView === 'function' && window.innerWidth > 768) {
4454            switchView('map');
4455          }
4456          if (typeof fitMapToMarkers === 'function') {
4457            setTimeout(fitMapToMarkers, 300);
4458          }
4459        }
4460      }
4461      // Remove from temp so it doesn't get inserted into chat
4462      cityPinsWrapper.remove();
4463    }
4464    
4465    // Return the cleaned HTML (only the chat message part)
4466    return temp.innerHTML;
4467  }
4468
4469  // handleJourneyClick — defined here so it's always available on mobile
4470  // (destination_detail.html re-defines it on desktop after panel opens — that's fine)
4471  if (!window.handleJourneyClick) {
4472    window.handleJourneyClick = function(citySlug, cityName, countryName) {
4473      if (typeof showPlanMyTripTimeframeStep === 'function') {
4474        showPlanMyTripTimeframeStep(citySlug, cityName, countryName || '');
4475      } else if (typeof NomadosPanel !== 'undefined') {
4476        NomadosPanel.open('journey-create', {
4477          isSubView: true,
4478          citySlug: citySlug,
4479          cityName: cityName,
4480          previousTitle: 'Destination Detail'
4481        });
4482      }
4483    };
4484  }
4485
4486  // Mobile compact destination selection: single-select toggle (check icon + dark border)
4487  window.selectMobileDestination = function(el) {
4488    var wasSelected = el.classList.contains('mobile-selected');
4489
4490    // Deselect all other compact widgets (single-select)
4491    document.querySelectorAll('.destination-widget-compact.mobile-selected').forEach(function(w) {
4492      if (w !== el) w.classList.remove('mobile-selected');
4493    });
4494
4495    // Toggle current card
4496    el.classList.toggle('mobile-selected', !wasSelected);
4497
4498    // Show Plan my Trip prompt when selecting a destination
4499    if (!wasSelected) {
4500      var slug = el.getAttribute('data-city-slug');
4501      var name = el.getAttribute('data-city-name');
4502      var country = el.getAttribute('data-country-name');
4503      if (typeof window.appendPlanMyTripPrompt === 'function') {
4504        window.appendPlanMyTripPrompt(slug, name, country);
4505      }
4506    }
4507  };
4508
4509  // Go Pro full-screen view (panel-like, fills main area) with slide animation
4510  window.openGoProView = function() {
4511    var el = document.getElementById('go-pro-view');
4512    if (!el) return;
4513    // Close NomadosPanel if it's open
4514    if (typeof NomadosPanel !== 'undefined' && NomadosPanel.isOpen) NomadosPanel.close();
4515    el.classList.remove('hidden');
4516    el.classList.add('flex', 'go-pro-view--closed');
4517    el.setAttribute('aria-hidden', 'false');
4518    // On mobile, show "Memberships" heading in the mobile header
4519    if (window.innerWidth < 768) {
4520      var headerLogo = document.getElementById('mobile-header-logo');
4521      var headerViewTitle = document.getElementById('mobile-header-view-title');
4522      if (headerLogo) headerLogo.classList.add('hidden');
4523      if (headerViewTitle) { headerViewTitle.textContent = 'Memberships'; headerViewTitle.classList.remove('hidden'); }
4524    }
4525    requestAnimationFrame(function() {
4526      requestAnimationFrame(function() {
4527        el.classList.remove('go-pro-view--closed');
4528      });
4529    });
4530  };
4531  window.closeGoProView = function() {
4532    var el = document.getElementById('go-pro-view');
4533    if (!el) return;
4534    el.classList.add('go-pro-view--closed');
4535    // On mobile, restore logo and hide view title in the mobile header
4536    if (window.innerWidth < 768) {
4537      var headerLogo = document.getElementById('mobile-header-logo');
4538      var headerViewTitle = document.getElementById('mobile-header-view-title');
4539      if (headerLogo) headerLogo.classList.remove('hidden');
4540      if (headerViewTitle) { headerViewTitle.classList.add('hidden'); headerViewTitle.textContent = ''; }
4541    }
4542    var onEnd = function() {
4543      el.removeEventListener('transitionend', onEnd);
4544      el.classList.add('hidden');
4545      el.classList.remove('flex', 'go-pro-view--closed');
4546      el.setAttribute('aria-hidden', 'true');
4547    };
4548    el.addEventListener('transitionend', onEnd);
4549  };
4550
4551  // Go Pro billing toggle (panel + modal): keep data-selected-plan in sync for checkout URL
4552  (function() {
4553    var goProBilling = 'monthly';
4554    window.setGoProBilling = function(plan) {
4555      goProBilling = plan;
4556      var ctaBlock = document.getElementById('go-pro-cta-block');
4557      if (ctaBlock) ctaBlock.setAttribute('data-selected-plan', plan);
4558      var modal = document.getElementById('goProModal');
4559      if (modal) modal.setAttribute('data-selected-plan', plan);
4560      if (typeof setGoProModalPlan === 'function') setGoProModalPlan(plan);
4561      var monthlyBlock = document.getElementById('goProPriceMonthly');
4562      var annualBlock = document.getElementById('goProPriceAnnual');
4563      var btnMonthly = document.getElementById('goProToggleMonthly');
4564      var btnAnnual = document.getElementById('goProToggleAnnual');
4565      if (!monthlyBlock || !annualBlock || !btnMonthly || !btnAnnual) return;
4566      if (plan === 'monthly') {
4567        monthlyBlock.classList.remove('hidden');
4568        annualBlock.classList.add('hidden');
4569        btnMonthly.classList.remove('text-[#777777]');
4570        btnMonthly.classList.add('bg-white', 'text-[#3B3B3C]');
4571        btnAnnual.classList.remove('bg-white', 'text-[#3B3B3C]');
4572        btnAnnual.classList.add('text-[#777777]');
4573      } else {
4574        monthlyBlock.classList.add('hidden');
4575        annualBlock.classList.remove('hidden');
4576        btnAnnual.classList.remove('text-[#777777]');
4577        btnAnnual.classList.add('bg-white', 'text-[#3B3B3C]');
4578        btnMonthly.classList.remove('bg-white', 'text-[#3B3B3C]');
4579        btnMonthly.classList.add('text-[#777777]');
4580      }
4581    };
4582  })();
4583</script>
4583
4584
4585        
4586          
4587
4588<!-- Nomados 2.0 Mobile Footer -->
4589<div id="nomados-mobile-footer" class="block md:hidden fixed bottom-0 left-0 h-auto md:h-16 w-full z-[2500] bg-white">
4590  <nav class="w-full">
4591      <div class="flex items-center justify-center gap-4 w-full h-full">
4592        <!-- Miles Button -->
4593        <button
4594          id="sidebar-miles-btn"
4595          class="sidebar-btn flex flex-col items-center justify-center gap-1 md:gap-2 cursor-pointer transition-all duration-200 w-full min-h-auto md:min-h-[80px] hover:bg-black/5 active py-3 md:py-0"
4596          data-panel-view="miles"
4597          onclick="closeNomadosPanel()"
4598          aria-label="Miles"
4599        >
4600          <div class="flex items-center justify-center">
4601            <img
4602              src="/static/icons/miles_filled.svg"
4603              alt="Miles"
4604              draggable="false"
4605              class="w-full h-full object-contain miles-icon-filled"
4606            />
4607            <img
4608              src="/static/icons/miles_blank.svg"
4609              alt="Miles"
4610              draggable="false"
4611              class="w-full h-full object-contain miles-icon-blank hidden"
4612            />
4613          </div>
4614          <span class="sidebar-label text-xs font-bold text-center leading-tight text-[#3B3B3C] miles-text">Miles</span>
4615        </button>
4616
4617        <!-- Launch Button -->
4618        <button
4619          id="sidebar-launch-btn"
4620          class="sidebar-btn flex flex-col items-center justify-center gap-1 md:gap-2 cursor-pointer transition-all duration-200 w-full min-h-auto md:min-h-[80px] hover:bg-black/5 py-3 md:py-0py-3 md:py-0"
4621          data-panel-view="journey"
4622          onclick="openNomadosPanel('journey')"
4623          aria-label="Launch - Journey List"
4624        >
4625          <div class="flex items-center justify-center">
4626            <img
4627              src="/static/icons/launch_filled.svg"
4628              alt="Launch"
4629              draggable="false"
4630              class="w-full h-full object-contain launch-icon-filled hidden"
4631            />
4632            <img
4633              src="/static/icons/launch_blank.svg"
4634              alt="Launch"
4635              draggable="false"
4636              class="w-full h-full object-contain launch-icon-blank"
4637            />
4638          </div>
4639          <span class="sidebar-label text-xs font-normal text-center leading-tight text-[#3B3B3C] launch-text">Launch</span>
4640        </button>
4641
4642        <!-- Market Button -->
4643        <button
4644          id="sidebar-market-btn"
4645          class="sidebar-btn flex flex-col items-center justify-center gap-1 md:gap-2 cursor-pointer transition-all duration-200 w-full min-h-auto md:min-h-[80px] hover:bg-black/5 py-3 md:py-0"
4646          data-panel-view="market"
4647          onclick="openNomadosPanel('market')"
4648          aria-label="Market"
4649        >
4650          <div class="flex items-center justify-center">
4651            <img
4652              src="/static/icons/market_filled.svg"
4653              alt="Market"
4654              draggable="false"
4655              class="w-full h-full object-contain market-icon-filled hidden"
4656            />
4657            <img
4658              src="/static/icons/market_blank.svg"
4659              alt="Market"
4660              draggable="false"
4661              class="w-full h-full object-contain market-icon-blank"
4662            />
4663          </div>
4664          <span class="sidebar-label text-xs font-normal text-center leading-tight text-[#3B3B3C] market-text">Market</span>
4665        </button>
4666      </div>
4667  </nav>
4668</div>
4669
4670        
4671      </div>
4672    </div>
4673    <!-- Nomados 2.0 Right Panel - No Modal, Direct Panel -->
4674    <div id="nomados-panel" class="nomados-panel">
4675      <div class="nomados-panel-content">
4676        <!-- Panel Header with Back Button -->
4677        <div id="nomados-panel-header" class="nomados-panel-header mt-2 md:mt-0">
4678          <button
4679            class="nomados-panel-back-btn"
4680            onclick="nomadosPanelGoBack()"
4681            aria-label="Go back"
4682          >
4683            <img
4684              src="/static/icons/arrow-left_no_circle.svg"
4685              alt="Back"
4686              draggable="false"
4687            />
4688          </button>
4689          <h2 id="nomados-panel-header-title" class="nomados-panel-header-title"></h2>
4690        </div>
4691        
4692        <!-- Close Button (only when header is hidden) -->
4693        <button
4694          id="nomados-panel-close-btn"
4695          class="nomados-panel-close"
4696          onclick="closeNomadosPanel()"
4697          aria-label="Close panel"
4698        >
4699          <img
4700            src="/static/icons/close-dark.svg"
4701            alt="Close"
4702            draggable="false"
4703          />
4704        </button>
4705        
4706        <!-- Panel Content Container -->
4707        <div id="nomados-panel-body" class="nomados-panel-body">
4708          <!-- Main Views -->
4709          <!-- Journey List View -->
4710          <div id="nomados-panel-journey" class="nomados-panel-view" style="display: none;">
4711            
4712
4713
4714
4715<style>
4716  .overlay-badge {
4717      text-align: center;
4718      text-shadow: 0 0 1px rgba(0, 0, 0, 0.55);
4719      font-style: normal;
4720      font-weight: 500;
4721      padding: 5px 10px;
4722      border-radius: 99px;
4723      background: #C4C4C438;
4724      backdrop-filter: blur(4px);
4725      -webkit-backdrop-filter: blur(4px);
4726      color: #fff;
4727  }
4728  .city-name {
4729      color: #fff;
4730      text-shadow: 0 0 4px #000;
4731      font-style: normal;
4732      font-weight: 800;
4733      opacity: 0.87;
4734      text-align: center;
4735      line-height: 100%;
4736  }
4737  .panel-tab-pane { display: none; }
4738  .panel-tab-pane.active { display: block; }
4739  @media (min-width: 768px) {
4740    #panel-content-journeys { display: block !important; }
4741  }
4742</style>
4743
4744<div class="journey-list-view h-full overflow-y-auto pt-4 md:pt-10">
4745  <div class="p-0 md:p-6 pb-20 md:pb-6">
4746    <div class="mb-6 hidden md:block">
4747      <h2 class="text-2xl font-bold color-black-100">Ready for your next journey?</h2>
4748      <h3 class="text-lg text-dark pt-2 pb-4">Work anywhere. Live everywhere.</h3>
4749    </div>
4750
4751    <!-- Journeys Tab Content (default, always visible on desktop) -->
4752    <div id="panel-content-journeys" class="panel-tab-pane active">
4753      <div class="w-full grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
4754        <!-- New Journey Button -->
4755        
4756        <!-- Not authenticated message -->
4757        <div class="col-span-full text-center py-12">
4758          <p class="text-gray-500">Please log in to view your journeys</p>
4759        </div>
4760        
4761      </div>
4762    </div>
4763
4764    <!-- Favorites Tab Content (mobile only) -->
4765    <div id="panel-content-favorites" class="panel-tab-pane">
4766      
4767        <div class="text-center py-12">
4768          <div class="text-gray-400 mb-4">
4769            <svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4770              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path>
4771            </svg>
4772          </div>
4773          <h3 class="text-lg font-medium text-gray-900 mb-2">No favorites yet</h3>
4774          <p class="text-gray-500">Start exploring destinations and add them to your favorites!</p>
4775        </div>
4776      
4777    </div>
4778
4779    <!-- Featured Add-Ons Tab Content (mobile only) -->
4780    <div id="panel-content-addons" class="panel-tab-pane">
4781      <div class="space-y-4">
4782        <!-- Visa Card -->
4783        <div
4784          class="bg-white rounded-2xl p-4 border border-[#D9D9D9] add-ons-card relative cursor-pointer hover:-translate-y-1 transition-all duration-300 ease-in-out"
4785          onclick="if(typeof openMarketVisa === 'function') openMarketVisa()"
4786        >
4787          <div class="bg-purple-200 rounded-xl p-4 mb-2 flex justify-center relative">
4788            <img src="/static/icons/market_visa.jpg" alt="Visa" draggable="false" class="max-h-52" />
4789          </div>
4790          <div>
4791            <div class="flex items-start justify-between mb-0">
4792              <h3 class="text-lg font-semibold color-black-100 mb-1">Visa</h3>
4793              <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Required</span>
4794            </div>
4795            <div class="flex items-center gap-2 text-sm color-gray-100">
4796              <span>90 Day Duration</span>
4797              <span>|</span>
4798              <span>Work Visa</span>
4799            </div>
4800            <p class="text-sm text-gray-600 leading-tight py-2">
4801              Handle your paperwork with ease. Get the right quickly and stress-free, so you can focus on your next adventure.
4802            </p>
4803          </div>
4804          <div class="flex items-center justify-end pt-0 pb-0">
4805            <div class="bg-dark-100 text-white ps-4 pe-2 py-1 rounded-full flex items-center">
4806              <span class="text-sm font-medium">Add</span>
4807              <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
4808            </div>
4809          </div>
4810        </div>
4811
4812        <!-- Health Insurance Card -->
4813        <div
4814          class="bg-white rounded-2xl p-4 border border-[#D9D9D9] add-ons-card relative cursor-pointer hover:-translate-y-1 transition-all duration-300 ease-in-out"
4815          onclick="if(typeof openMarketHealthcare === 'function') openMarketHealthcare()"
4816        >
4817          <div class="bg-blue-100 rounded-xl p-4 mb-2 flex justify-center relative">
4818            <img src="/static/icons/market_health.jpg" alt="Health" draggable="false" class="max-h-52" />
4819          </div>
4820          <div>
4821            <div class="flex items-start justify-between mb-0">
4822              <h3 class="text-lg font-semibold color-black-100 mb-1">Health Insurance</h3>
4823              <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Essential</span>
4824            </div>
4825            <div class="flex items-center gap-2 text-sm color-gray-100">
4826              <span>Annual Coverage</span>
4827            </div>
4828            <p class="text-sm text-gray-600 leading-tight py-2">
4829              Comprehensive global coverage in 175+ countries, with 24/7 support and the freedom to see any doctor.
4830            </p>
4831          </div>
4832          <div class="flex items-center justify-end pt-0 pb-0">
4833            <div class="bg-dark-100 text-white ps-4 pe-2 py-1 rounded-full flex items-center">
4834              <span class="text-sm font-medium">Add</span>
4835              <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
4836            </div>
4837          </div>
4838        </div>
4839
4840        <!-- Airport Lounge Card -->
4841        <div
4842          class="bg-white rounded-2xl p-4 border border-[#D9D9D9] add-ons-card relative cursor-pointer hover:-translate-y-1 transition-all duration-300 ease-in-out"
4843          onclick="if(typeof openMarketLounge === 'function') openMarketLounge()"
4844        >
4845          <div class="bg-green-100 rounded-xl p-4 mb-2 flex justify-center relative">
4846            <img src="/static/icons/market_airport_lounge.jpg" alt="Lounge" draggable="false" class="max-h-52" />
4847          </div>
4848          <div>
4849            <div class="flex items-start justify-between mb-0">
4850              <h3 class="text-lg font-semibold color-black-100 mb-1">Airport Lounge</h3>
4851              <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Premium</span>
4852            </div>
4853            <div class="flex items-center gap-2 text-sm color-gray-100">
4854              <span>One Adult</span>
4855              <span>|</span>
4856              <span>Participating Lounges</span>
4857            </div>
4858            <p class="text-sm text-gray-600 leading-tight py-2">
4859              Begin your trip in comfort. Enjoy airport lounge access worldwide at one of our participating locations.
4860            </p>
4861          </div>
4862          <div class="flex items-center justify-end pt-0 pb-0">
4863            <div class="bg-dark-100 text-white ps-4 pe-2 py-1 rounded-full flex items-center">
4864              <span class="text-sm font-medium">Add</span>
4865              <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
4866            </div>
4867          </div>
4868        </div>
4869
4870        <!-- Data Connectivity (eSIM) Card -->
4871        <div
4872          class="bg-white rounded-2xl p-4 border border-[#D9D9D9] add-ons-card relative cursor-pointer hover:-translate-y-1 transition-all duration-300 ease-in-out"
4873          onclick="if(typeof openMarketESim === 'function') openMarketESim()"
4874        >
4875          <div class="bg-purple-200 rounded-xl p-4 mb-2 flex justify-center relative">
4876            <img src="/static/icons/market_esim.jpg" alt="eSIM" draggable="false" class="max-h-52" />
4877          </div>
4878          <div>
4879            <div class="flex items-start justify-between mb-0">
4880              <h3 class="text-lg font-semibold color-black-100 mb-1">Data Connectivity</h3>
4881              <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Flexible</span>
4882            </div>
4883            <div class="flex items-center gap-2 text-sm color-gray-100">
4884              <span>Global</span>
4885              <span>|</span>
4886              <span>eSIM</span>
4887            </div>
4888            <p class="text-sm text-gray-600 leading-tight py-2">
4889              Stay connected anywhere without breaking the bank from expensive roaming charges.
4890            </p>
4891          </div>
4892          <div class="flex items-center justify-end pt-0 pb-0">
4893            <div class="bg-dark-100 text-white ps-4 pe-2 py-1 rounded-full flex items-center">
4894              <span class="text-sm font-medium">Add</span>
4895              <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
4896            </div>
4897          </div>
4898        </div>
4899      </div>
4900    </div>
4901
4902  </div>
4903</div>
4904
4905<script>
4906  function togglePanelJourneyDropdown() {
4907    var dropdown = document.getElementById('panel-journey-dropdown');
4908    var arrow = document.getElementById('panel-dropdown-arrow');
4909    if (dropdown) {
4910      dropdown.classList.toggle('hidden');
4911      if (arrow) {
4912        arrow.style.transform = dropdown.classList.contains('hidden') ? '' : 'rotate(180deg)';
4913      }
4914    }
4915  }
4916
4917  function selectPanelJourneyTab(tab) {
4918    // Hide all tab panes
4919    document.querySelectorAll('.panel-tab-pane').forEach(function(el) {
4920      el.classList.remove('active');
4921    });
4922    // Show selected tab pane
4923    var pane = document.getElementById('panel-content-' + tab);
4924    if (pane) pane.classList.add('active');
4925
4926    // Update dropdown label
4927    var label = document.getElementById('panel-selected-tab');
4928    var labels = { journeys: 'Journeys', favorites: 'Favorites', addons: 'Featured Add-Ons' };
4929    if (label) label.textContent = labels[tab] || tab;
4930
4931    // Close dropdown
4932    var dropdown = document.getElementById('panel-journey-dropdown');
4933    var arrow = document.getElementById('panel-dropdown-arrow');
4934    if (dropdown) dropdown.classList.add('hidden');
4935    if (arrow) arrow.style.transform = '';
4936  }
4937
4938  // Close panel dropdown when clicking outside
4939  document.addEventListener('click', function(e) {
4940    var dropdown = document.getElementById('panel-journey-dropdown');
4941    if (!dropdown || dropdown.classList.contains('hidden')) return;
4942    var container = document.getElementById('mobile-header-journey-dropdown');
4943    if (container && !container.contains(e.target)) {
4944      dropdown.classList.add('hidden');
4945      var arrow = document.getElementById('panel-dropdown-arrow');
4946      if (arrow) arrow.style.transform = '';
4947    }
4948  });
4949</script>
4949
4950
4951          </div>
4952          <!-- Market View -->
4953          <div id="nomados-panel-market" class="nomados-panel-view" style="display: none;">
4954             
4955
4956
4957<style>
4958  .tooltip {
4959    position: relative;
4960  }
4961  
4962  .tooltip::after {
4963    content: attr(data-tooltip);
4964    position: absolute;
4965    bottom: 100%;
4966    left: 0%;
4967    transform: translateX(-60%);
4968    background-color: #333;
4969    color: white;
4970    padding: 6px 10px;
4971    border-radius: 4px;
4972    font-size: 12px;
4973    white-space: nowrap;
4974    opacity: 0;
4975    visibility: hidden;
4976    transition: opacity 0.3s, visibility 0.3s;
4977    z-index: 1000;
4978    margin-bottom: 6px;
4979    pointer-events: none;
4980  }
4981  
4982  .tooltip::before {
4983    content: '';
4984    position: absolute;
4985    bottom: 100%;
4986    left: 50%;
4987    transform: translateX(-50%);
4988    border: 4px solid transparent;
4989    border-top-color: #333;
4990    opacity: 0;
4991    visibility: hidden;
4992    transition: opacity 0.3s, visibility 0.3s;
4993    z-index: 1000;
4994    margin-bottom: 2px;
4995    pointer-events: none;
4996  }
4997  
4998  .tooltip:hover::after,
4999  .tooltip:hover::before {
5000    opacity: 1;
5001    visibility: visible;
5002  }
5003</style>
5004
5005<div class="market-view h-full overflow-y-auto pt-4 md:pt-10">
5006  <div class="p-0 md:p-6 pb-20 md:pb-6">
5007    <div class="hidden md:block mb-4">
5008      <h2 class="text-2xl font-bold color-black-100">Featured Add-Ons</h2>
5009      <h3 class="text-lg text-dark pt-2 pb-2">Your essentials, one tap away.</h3>
5010    </div>
5011    
5012    <div class="w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4 md:gap-0">
5013      <!-- Visa Card -->
5014      <div
5015        class="bg-white rounded-2xl m-0 md:m-2 p-4 border border-[#D9D9D9] add-ons-card relative hover:-translate-y-1 transition-all duration-300 ease-in-out cursor-not-allowed"
5016        
5017      >
5018        <div class="bg-purple-200 rounded-xl p-4 mb-2 xl:mb-4 flex justify-center relative">
5019          <img src="/static/icons/market_visa.jpg" alt="Visa" draggable="false" class="max-h-52" />
5020        </div>
5021        <div>
5022          <div class="flex items-start justify-between mb-0 xl:mb-2">
5023            <h3 class="text-lg font-semibold color-black-100 mb-1">Visa</h3>
5024            <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Required</span>
5025          </div>
5026          <div class="flex items-center gap-2 text-sm color-gray-100">
5027            <span>90 Day Duration</span>
5028            <span>|</span>
5029            <span>Work Visa</span>
5030          </div>
5031          <p class="text-sm xl:text-base text-gray-600 leading-tight xl:leading-relaxed py-2 xl:py-4">
5032            Handle your paperwork with ease. Get the right quickly and stress-free, so you can focus on your next adventure.
5033          </p>
5034        </div>
5035        <div class="flex items-center justify-end pt-0 xl:pt-2 pb-0 lg:pb-2 xl:pb-6">
5036          <div class="bg-gray-300 text-gray-500 tooltip ps-4 pe-2 py-1 rounded-full flex items-center"
5037            data-tooltip="Only available to registered users.">
5038            <span class="text-sm font-medium">Add</span>
5039            <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
5040          </div>
5041        </div>
5042      </div>
5043
5044      <!-- Health Insurance Card -->
5045      <div
5046        class="bg-white rounded-2xl m-0 md:m-2 p-4 border border-[#D9D9D9] add-ons-card relative hover:-translate-y-1 transition-all duration-300 ease-in-out cursor-not-allowed"
5047        
5048      >
5049        <div class="bg-blue-100 rounded-xl p-4 mb-2 xl:mb-4 flex justify-center relative">
5050          <img src="/static/icons/market_health.jpg" alt="Health" draggable="false" class="max-h-52" />
5051        </div>
5052        <div>
5053          <div class="flex items-start justify-between mb-0 xl:mb-2">
5054            <h3 class="text-lg font-semibold color-black-100 mb-1">Health Insurance</h3>
5055            <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Essential</span>
5056          </div>
5057          <div class="flex items-center gap-2 text-sm color-gray-100">
5058            <span>Annual Coverage</span>
5059          </div>
5060          <p class="text-sm xl:text-base text-gray-600 leading-tight xl:leading-relaxed py-2 xl:py-4">
5061            Comprehensive global coverage in 175+ countries, with 24/7 support and the freedom to see any doctor.
5062          </p>
5063        </div>
5064        <div class="flex items-center justify-end pt-0 xl:pt-2 pb-0 lg:pb-2 xl:pb-6">
5065          <div class="bg-gray-300 text-gray-500 tooltip ps-4 pe-2 py-1 rounded-full flex items-center"
5066            data-tooltip="Only available to registered users.">
5067            <span class="text-sm font-medium">Add</span>
5068            <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
5069          </div>
5070        </div>
5071      </div>
5072
5073      <!-- Airport Lounge Card -->
5074      <div
5075        class="bg-white rounded-2xl m-0 md:m-2 p-4 border border-[#D9D9D9] add-ons-card relative hover:-translate-y-1 transition-all duration-300 ease-in-out cursor-not-allowed"
5076        
5077      >
5078        <div class="bg-green-100 rounded-xl p-4 mb-2 xl:mb-4 flex justify-center relative">
5079          <img src="/static/icons/market_airport_lounge.jpg" alt="Lounge" draggable="false" class="max-h-52" />
5080        </div>
5081        <div>
5082          <div class="flex items-start justify-between mb-0 xl:mb-2">
5083            <h3 class="text-lg font-semibold color-black-100 mb-1">Airport Lounge</h3>
5084            <span class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full">Premium</span>
5085          </div>
5086          <div class="flex items-center gap-2 text-sm color-gray-100">
5087            <span>One Adult</span>
5088            <span>|</span>
5089            <span>Participating Lounges</span>
5090          </div>
5091          <p class="text-sm xl:text-base text-gray-600 leading-tight xl:leading-relaxed py-2 xl:py-4">
5092            Begin your trip in comfort. Enjoy airport lounge access worldwide at one of our participating locations.
5093          </p>
5094        </div>
5095        <div class="flex items-center justify-end pt-0 xl:pt-2 pb-0 lg:pb-2 xl:pb-6">
5096          <div class="bg-gray-300 text-gray-500 tooltip ps-4 pe-2 py-1 rounded-full flex items-center"
5097            data-tooltip="Only available to registered users.">
5098            <span class="text-sm font-medium">Add</span>
5099            <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
5100          </div>
5101        </div>
5102      </div>
5103
5104      <!-- eSIM Card -->
5105      <div
5106        class="bg-white rounded-2xl m-0 md:m-2 p-4 border border-[#D9D9D9] add-ons-card relative  hover:-translate-y-1 transition-all duration-300 ease-in-out"
5107        
5108      >
5109        <!-- eSIM - Top Section - Illustration -->
5110        <div class="bg-purple-200 rounded-xl p-4 mb-2 xl:mb-4 flex justify-center relative">
5111          <img
5112            src="/static/icons/market_esim.jpg"
5113            alt="eSIM"
5114            class="max-h-52"
5115            draggable="false"
5116          />
5117        </div>
5118
5119        <!-- eSIM - Middle Section - Details -->
5120        <div>
5121          <div class="flex items-start justify-between mb-0 xl:mb-2">
5122            <h3 class="text-lg font-semibold color-black-100 mb-1">
5123              Data Connectivity
5124            </h3>
5125            <span
5126              class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full"
5127              >Flexible</span
5128            >
5129          </div>
5130
5131          <div class="flex items-center gap-2 text-sm color-gray-100">
5132            <span>Global</span>
5133            <span>|</span>
5134            <span>eSIM</span>
5135          </div>
5136
5137          <p class="text-sm xl:text-base text-gray-600 leading-tight xl:leading-relaxed py-2 xl:py-4">
5138            Stay connected anywhere without breaking the bank from expensive roaming charges.
5139          </p>
5140        </div>
5141
5142        <!-- eSIM - Bottom Section - Pricing and Action -->
5143        <div class="flex items-center justify-end pt-0 xl:pt-2 pb-0 lg:pb-2 xl:pb-6">
5144          <div class="bg-gray-300 text-gray-500 tooltip ps-4 pe-2 py-1 rounded-full flex items-center"
5145            data-tooltip="Only available to registered users.">
5146            <span class="text-sm font-medium">Add</span>
5147            <img src="/static/icons/arrow_right.svg" alt="Arrow right" draggable="false" />
5148          </div>
5149        </div>
5150      </div>
5151
5152      <!-- Stay Card -->
5153      <div
5154        class="bg-white rounded-2xl m-0 md:m-2 p-4 border border-[#D9D9D9] add-ons-card relative  hover:-translate-y-1 transition-all duration-300 ease-in-out"
5155      >
5156        <!-- Stay - Top Section - Illustration -->
5157        <div class="bg-blue-100 rounded-xl p-4 mb-2 xl:mb-4 flex justify-center relative">
5158          <img
5159            src="/static/icons/market_stay.jpg"
5160            alt="Stay"
5161            class="max-h-52"
5162            draggable="false"
5163          />
5164          <h2 class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl font-bold color-dark-100 text-center">Coming Soon</h2>
5165        </div>
5166
5167        <!-- Stay - Middle Section - Details -->
5168        <div>
5169          <div class="flex items-start justify-between mb-0 xl:mb-2">
5170            <h3 class="text-lg font-semibold color-black-100 mb-1">
5171              Stay
5172            </h3>
5173            <span
5174              class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full"
5175              >Recommended</span
5176            >
5177          </div>
5178
5179          <div class="flex items-center gap-2 text-sm color-gray-100">
5180            <span>2 Nights</span>
5181            <span>|</span>
5182            <span>Non-Cancellable</span>
5183          </div>
5184
5185          <p class="text-sm xl:text-base text-gray-600 leading-tight xl:leading-relaxed py-2 xl:py-4">
5186            Find a place to rest. Convenient short-term stays to fit your travel plans.
5187          </p>
5188        </div>
5189
5190        <!-- Stay - Bottom Section - Pricing and Action -->
5191        <div class="flex items-center justify-end pt-0 xl:pt-2 pb-0 lg:pb-2 xl:pb-6">
5192          <div
5193            class="bg-dark-100 text-white ps-4 pe-2 py-1 rounded-full flex items-center"
5194          >
5195            <span class="text-sm font-medium">Add</span>
5196            <img
5197              src="/static/icons/arrow_right.svg"
5198              alt="Arrow right"
5199              class=""
5200              draggable="false"
5201            />
5202          </div>
5203        </div>
5204        
5205        <div
5206          class="absolute inset-0 bg-white/40 rounded-2xl flex items-center justify-center z-10"
5207        >
5208        </div>
5209        
5210
5211        <!-- 
5212        <div
5213          class="absolute inset-0 bg-transparent backdrop-blur-sm rounded-2xl flex items-center justify-center z-10"
5214        >
5215        </div>
5216         -->
5217      </div>
5218
5219      <!-- Flight Card -->
5220      <div
5221        class="bg-white rounded-2xl m-0 md:m-2 p-4 border border-[#D9D9D9] add-ons-card relative  hover:-translate-y-1 transition-all duration-300 ease-in-out"
5222      >
5223        <!-- Flight - Top Section - Illustration -->
5224        <div class="bg-green-100 rounded-xl p-4 mb-2 xl:mb-4 flex justify-center relative">
5225          <img
5226            src="/static/icons/market_flight.jpg"
5227            alt="Flight"
5228            class="max-h-52"
5229            draggable="false"
5230          />
5231          <h2 class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl font-bold color-dark-100 text-center">Coming Soon</h2>
5232        </div>
5233
5234        <!-- Flight - Middle Section - Details -->
5235        <div>
5236          <div class="flex items-start justify-between mb-0 xl:mb-2">
5237            <h3 class="text-lg font-semibold color-black-100 mb-1">
5238              Flight
5239            </h3>
5240            <span
5241              class="text-xs bg-white-100 text-black px-2 py-1.5 rounded-full"
5242              >One-Way</span
5243            >
5244          </div>
5245
5246          <div class="flex items-center gap-2 text-sm color-gray-100">
5247            <span>One-Adult</span>
5248            <span>|</span>
5249            <span>International</span>
5250          </div>
5251
5252          <p class="text-sm xl:text-base text-gray-600 leading-tight xl:leading-relaxed py-2 xl:py-4">
5253            Book your sear with ease. Simple one-way tickets to get you moving on your journey.
5254          </p>
5255        </div>
5256
5257        <!-- Flight - Bottom Section - Pricing and Action -->
5258        <div class="flex items-center justify-end pt-0 xl:pt-2 pb-0 lg:pb-2 xl:pb-6">
5259          <div
5260            class="bg-dark-100 text-white ps-4 pe-2 py-1 rounded-full flex items-center"
5261          >
5262            <span class="text-sm font-medium">Add</span>
5263            <img
5264              src="/static/icons/arrow_right.svg"
5265              alt="Arrow right"
5266              class=""
5267              draggable="false"
5268            />
5269          </div>
5270        </div>
5271        
5272        <div
5273          class="absolute inset-0 bg-white/40 rounded-2xl flex items-center justify-center z-10"
5274        >
5275        </div>
5276        
5277
5278        <!-- 
5279        <div
5280          class="absolute inset-0 bg-transparent backdrop-blur-sm rounded-2xl flex items-center justify-center z-10"
5281        >
5282        </div>
5283         -->
5284      </div>
5285    </div>
5286  </div>
5287</div>
5288          </div>
5289          <!-- Profile View -->
5290          <div id="nomados-panel-profile" class="nomados-panel-view" style="display: none;">
5291             
5292
5293
5294<style>
5295  .recent-journey:hover {
5296    box-shadow: 0 8px 25px #3B3B3C1A;
5297  }
5298
5299  .checkbox-input {
5300    width: 16px;
5301    height: 16px;
5302    border-radius: 4px;
5303    border: 1.5px solid #bbb;
5304    background: #fff;
5305    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.12) inset;
5306  }
5307
5308  .profile-tab.active {
5309    color: #3B3B3C;
5310    border-bottom-color: #3B3B3C;
5311    background-color: #F4F7F9;
5312    border-radius: 8px 8px 0 0;
5313  }
5314
5315  .profile-tab {
5316    color: #5D5D5D;
5317    transition: all 0.2s ease;
5318  }
5319
5320  .profile-tab:hover {
5321    color: #3B3B3C;
5322  }
5323</style>
5324
5325<script>
5326  function copyReferralLink(event) {
5327    const referralLink = document.getElementById("referral-link");
5328    if (!referralLink) return;
5329    referralLink.select();
5330    referralLink.setSelectionRange(0, 99999);
5331
5332    try {
5333      document.execCommand("copy");
5334      const button = event ? event.target.closest("button") : document.querySelector('button[onclick*="copyReferralLink"]');
5335      if (button) {
5336        const originalText = button.innerHTML;
5337        button.innerHTML = `<span>Copied!</span>`;
5338        setTimeout(() => {
5339          button.innerHTML = originalText;
5340        }, 2000);
5341      }
5342    } catch (err) {
5343      console.error("Failed to copy: ", err);
5344      if (typeof showNotification === 'function') showNotification("Failed to copy referral link. Please copy manually.");
5345      else console.error("Failed to copy referral link.");
5346    }
5347  }
5348
5349  function switchProfileTab(tabName) {
5350    // Hide all tab contents
5351    const tabContents = document.querySelectorAll('.profile-tab-content');
5352    tabContents.forEach(content => {
5353      content.style.display = 'none';
5354    });
5355
5356    // Remove active class from all tabs
5357    const tabs = document.querySelectorAll('.profile-tab');
5358    tabs.forEach(tab => {
5359      tab.classList.remove('active');
5360      tab.classList.remove('border-[#3B3B3C]');
5361      tab.classList.add('border-transparent');
5362    });
5363
5364    // Show selected tab content
5365    const selectedContent = document.getElementById(`${tabName}-tab-content`);
5366    if (selectedContent) {
5367      selectedContent.style.display = 'block';
5368    }
5369
5370    // Add active class to selected tab
5371    const selectedTab = document.querySelector(`[data-tab="${tabName}"]`);
5372    if (selectedTab) {
5373      selectedTab.classList.add('active');
5374      selectedTab.classList.remove('border-transparent');
5375      selectedTab.classList.add('border-[#3B3B3C]');
5376    }
5377  }
5378
5379  function saveProfile(e) {
5380    e.preventDefault();
5381    var form = document.getElementById('profileForm');
5382    var btn = document.getElementById('profileSaveBtn');
5383    var alert = document.getElementById('profile-alert');
5384    var formData = new FormData(form);
5385
5386    // Disable button while saving
5387    btn.disabled = true;
5388    btn.style.opacity = '0.5';
5389    btn.style.cursor = 'not-allowed';
5390    btn.querySelector('span').textContent = 'Saving...';
5391
5392    // Hide any existing alert
5393    alert.className = 'hidden rounded-lg px-4 py-3 mb-3 text-sm font-medium';
5394    alert.textContent = '';
5395
5396    fetch('/chat/panel/profile/', {
5397      method: 'POST',
5398      headers: {
5399        'X-CSRFToken': formData.get('csrfmiddlewaretoken'),
5400      },
5401      body: formData,
5402    })
5403    .then(function(response) { return response.json().then(function(data) { return { ok: response.ok, data: data }; }); })
5404    .then(function(result) {
5405      if (result.ok && result.data.success) {
5406        alert.className = 'rounded-lg px-4 py-3 mb-3 text-sm font-medium bg-green-50 border border-green-200 text-green-800';
5407        alert.textContent = result.data.message || 'Profile updated successfully!';
5408      } else {
5409        alert.className = 'rounded-lg px-4 py-3 mb-3 text-sm font-medium bg-red-50 border border-red-200 text-red-800';
5410        alert.textContent = result.data.message || 'Failed to update profile. Please try again.';
5411      }
5412      // Auto-hide success alert after 4s
5413      if (result.ok && result.data.success) {
5414        setTimeout(function() {
5415          alert.className = 'hidden rounded-lg px-4 py-3 mb-3 text-sm font-medium';
5416        }, 4000);
5417      }
5418    })
5419    .catch(function() {
5420      alert.className = 'rounded-lg px-4 py-3 mb-3 text-sm font-medium bg-red-50 border border-red-200 text-red-800';
5421      alert.textContent = 'Something went wrong. Please try again.';
5422    })
5423    .finally(function() {
5424      btn.disabled = false;
5425      btn.style.opacity = '1';
5426      btn.style.cursor = 'pointer';
5427      btn.querySelector('span').textContent = 'Save Changes';
5428    });
5429
5430    return false;
5431  }
5432
5433  // Initialize: show profile tab by default
5434  document.addEventListener('DOMContentLoaded', function() {
5435    switchProfileTab('profile');
5436  });
5437
5438  // Real-time membership updates via WebSocket
5439  (function() {
5440    var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
5441    var wsUrl = protocol + '//' + window.location.host + '/ws/status/';
5442    try {
5443      var ws = new WebSocket(wsUrl);
5444      ws.onmessage = function(event) {
5445        var data = JSON.parse(event.data);
5446        if (data.type !== 'status_update' || !data.payload) return;
5447        var p = data.payload;
5448        var isPro = (p.tier === 'pro') || (p.event === 'granted' || p.event === 'renewed');
5449        var tierEl = document.getElementById('membership-tier-badge');
5450        var planEl = document.getElementById('membership-plan-text');
5451        var renewalEl = document.getElementById('membership-renewal-text');
5452        if (tierEl) {
5453          tierEl.textContent = isPro ? 'Pro' : 'Registered';
5454          tierEl.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ' + (isPro ? 'bg-[#14B8A6]/20 text-[#0D9488]' : 'bg-gray-100 text-gray-700');
5455        }
5456        if (planEl) {
5457          if (p.plan_type === 'monthly') planEl.textContent = 'Monthly plan';
5458          else planEl.textContent = isPro ? 'Pro' : 'Free account';
5459        }
5460        if (renewalEl && p.current_period_end) {
5461          try {
5462            var d = new Date(p.current_period_end);
5463            renewalEl.textContent = 'Renews ' + d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
5464          } catch (e) {
5465            renewalEl.textContent = 'Active subscription';
5466          }
5467        } else if (renewalEl && p.event) {
5468          renewalEl.textContent = p.event === 'revoked' ? 'Upgrade to Pro for unlimited journeys and more.' : 'Active subscription';
5469        }
5470      };
5471      ws.onclose = function() { };
5472    } catch (e) { }
5473  })();
5474</script>
5474
5475<div class="profile-view h-full overflow-y-auto pt-4 md:pt-10">
5476  <div class="p-0 md:p-6 pb-20 md:pb-6">
5477    <!-- Header -->
5478    <div class="mb-6">
5479      <h1 class="text-2xl font-bold color-black-100 mb-2">Nomad Profile</h1>
5480      <div class="flex items-center justify-between">
5481        <div class="w-3/4 md:w-auto">
5482          <p class="text-sm md:text-base text-black">
5483            Manage your account settings and view your travel statistics
5484          </p>
5485        </div>
5486        <a
5487          href="/chat/panel/logout/"
5488          class="flex bg-white items-center hover:bg-gray-200 color-black-100 ps-2 py-1.5 pe-4 border border-solid border-[#E5E5E5] rounded-full font-medium transition duration-200 gap-1 w-[100px]"
5489        >
5490          <img src="/static/icons/logout.svg" alt="Logout" draggable="false" />
5491          Logout
5492        </a>
5493      </div>
5494    </div>
5495
5496    <!-- Statistics Cards -->
5497    <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
5498      <div class="bg-[#F4F7F9] flex flex-col gap-2 p-4 rounded-xl">
5499        <div class="flex items-center justify-between">
5500          <img src="/static/icons/total_journeys.svg" alt="Journey" />
5501          <span class="text-xl font-medium">0</span>
5502        </div>
5503        <h3 class="text-xl font-medium text-black">Total Journeys</h3>
5504        <p class="text-sm color-gray-100">Planned adventures around the world</p>
5505      </div>
5506
5507      <div class="bg-[#F4F7F9] flex flex-col gap-2 p-4 rounded-xl">
5508        <div class="flex items-center justify-between">
5509          <img src="/static/icons/favorite.svg" alt="Favorite" />
5510          <span class="text-xl font-medium"></span>
5511        </div>
5512        <h3 class="text-xl font-medium text-black">Favorite Cities</h3>
5513        <p class="text-sm color-gray-100">Cities you've marked as favorites</p>
5514      </div>
5515
5516      <div class="bg-[#F4F7F9] flex flex-col gap-2 p-4 rounded-xl">
5517        <div class="flex items-center justify-between">
5518          <img src="/static/icons/calendar.svg" alt="Member Since" />
5519          <span class="text-xl font-medium"></span>
5520        </div>
5521        <h3 class="text-xl font-medium text-black">Member Since</h3>
5522        <p class="text-sm color-gray-100">When you joined Nomados</p>
5523      </div>
5524    </div>
5525
5526    <!-- Membership / Subscription status (updates in real time via WebSocket) -->
5527    <div id="membership-status-card" class="bg-white rounded-xl border border-[#D9D9D9] p-4 mb-8 relative">
5528      <!-- Processing loader overlay -->
5529      <div id="membership-processing-loader" class="hidden absolute inset-0 z-10 bg-white/80 rounded-xl flex flex-col items-center justify-center">
5530        <svg class="animate-spin h-6 w-6 text-[#3B3B3C] mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
5531        <span id="membership-loader-text" class="text-sm text-gray-600">Processing...</span>
5532      </div>
5533
5534      <h2 class="text-xl font-bold text-black mb-3">Membership</h2>
5535      <div class="flex flex-wrap items-center gap-2 mb-2">
5536        <span id="membership-tier-badge" class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-700">
5537          Registered
5538        </span>
5539        <span id="membership-plan-text" class="text-sm text-gray-600">
5540          Free account
5541        </span>
5542        <!-- Status sub-badge (hidden for active) -->
5543        <span id="membership-status-badge" class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
5544          hidden">
5545          
5546        </span>
5547      </div>
5548
5549      <!-- Status-specific messaging -->
5550      
5551        <div id="membership-status-msg">
5552          <p id="membership-renewal-text" class="text-sm text-gray-500">
5553            
5554              Upgrade to Pro for unlimited journeys and more.
5555            
5556          </p>
5557        </div>
5558      
5559
5560      <!-- ── Actions: Canceled Pro users (scheduled cancel — still has Pro until period end) ── -->
5561      
5562
5563      <!-- ── Actions: Active / Past Due Pro users ── -->
5564      
5565    </div>
5566
5567    <!-- Referral Link Section -->
5568    
5569
5570    <!-- Tab Navigation -->
5571    <div class="mb-6 border-b border-[#D9D9D9]">
5572      <div class="flex gap-4">
5573        <button
5574          class="profile-tab px-3 md:px-4 py-2 text-base font-medium border-b-2 border-[#3B3B3C] hover:text-gray-700 transition-colors active"
5575          data-tab="profile"
5576          onclick="switchProfileTab('profile')"
5577        >
5578          Profile
5579        </button>
5580        <button
5581          class="profile-tab px-3 md:px-4 py-2 text-base font-medium border-b-2 border-transparent hover:text-gray-700 transition-colors"
5582          data-tab="addons"
5583          onclick="switchProfileTab('addons')"
5584        >
5585          Add-Ons
5586        </button>
5587        <button
5588          class="profile-tab px-3 md:px-4 py-2 text-base font-medium border-b-2 border-transparent hover:text-gray-700 transition-colors"
5589          data-tab="journeys"
5590          onclick="switchProfileTab('journeys')"
5591        >
5592          Journeys
5593        </button>
5594          <button
5595            class="profile-tab px-4 py-2 text-base font-medium border-b-2 border-transparent hover:text-gray-700 transition-colors"
5596          data-tab="favorites"
5597          onclick="switchProfileTab('favorites')"
5598        >
5599          Favorites
5600        </button>
5601      </div>
5602    </div>
5603
5604    <!-- Tab Content -->
5605    <!-- Profile Tab Content -->
5606    <div id="profile-tab-content" class="profile-tab-content">
5607      <div class="bg-white rounded-xl border border-[#D9D9D9] px-4 py-5">
5608        <h2 class="text-xl font-bold text-black mb-3">Profile Information</h2>
5609        <!-- Alert message -->
5610        <div id="profile-alert" class="hidden rounded-lg px-4 py-3 mb-3 text-sm font-medium"></div>
5611        <form id="profileForm" class="space-y-3" onsubmit="return saveProfile(event)">
5612          <input type="hidden" name="csrfmiddlewaretoken" value="wW5XRNroFwTKrAvYlp7o0ZIlcpfIHaiR8jbqrSOG95se38pTkp2zbUYkj50vDt3a">
5613          <div class="flex flex-col">
5614            <label for="first_name" class="color-gray-100 text-sm font-semibold">First Name</label>
5615            <input type="text" id="first_name" name="first_name" value="" class="w-full px-4 py-3 border border-solid border-[#bbb] rounded-full pr-12" style="box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.09) inset" required />
5616          </div>
5617          <div class="flex flex-col">
5618            <label for="last_name" class="color-gray-100 text-sm font-semibold">Last Name</label>
5619            <input type="text" id="last_name" name="last_name" value="" class="w-full px-4 py-3 border border-solid border-[#bbb] rounded-full pr-12" style="box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.09) inset" required />
5620          </div>
5621          <div class="flex flex-col">
5622            <label for="email" class="color-gray-100 text-sm font-semibold">Email Address<sup style="color: #ff735d; font-size: 10px" class="font-semibold">✱</sup></label>
5623            <input type="email" id="email" name="email" value="" class="w-full px-4 py-3 border border-solid border-[#bbb] rounded-full pr-12" style="box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.09) inset" required />
5624          </div>
5625          
5626          <div class="flex justify-end pt-2">
5627            <button type="submit" id="profileSaveBtn" class="flex bg-dark-100 text-white items-center ps-4 pe-2 py-1.5 rounded-full font-medium transition duration-200 gap-1">
5628              <span>Save Changes</span>
5629              <img src="/static/icons/arrow_right.svg" alt="Arrow" draggable="false" />
5630            </button>
5631          </div>
5632        </form>
5633      </div>
5634    </div>
5635
5636    <!-- Add-Ons Tab Content (empty as requested) -->
5637    <div id="addons-tab-content" class="profile-tab-content" style="display: none;">
5638      <!-- Add-Ons content skipped as requested -->
5639    </div>
5640
5641    <!-- Journeys Tab Content -->
5642    <div id="journeys-tab-content" class="profile-tab-content" style="display: none;">
5643      
5644        <div class="bg-white rounded-xl border border-[#D9D9D9] p-8 text-center">
5645          <p class="text-gray-500">No journeys yet. Start planning your next adventure!</p>
5646        </div>
5647      
5648    </div>
5649
5650    <!-- Favorites Tab Content -->
5651    <div id="favorites-tab-content" class="profile-tab-content" style="display: none;">
5652      
5653        <div class="bg-white rounded-xl border border-[#D9D9D9] p-8 text-center">
5654          <p class="text-gray-500">No favorite cities yet. Start exploring destinations!</p>
5655        </div>
5656      
5657    </div>
5658
5659    <!-- Terms and Privacy -->
5660    <div class="mt-6 flex items-center justify-center gap-3 text-xs color-gray-200">
5661      <a href="https://nomados.ai/terms-and-conditions/" target="_blank" class="hover:font-bold transition-all duration-200 cursor-pointer">
5662        Terms of Service
5663      </a>
5664      <span>|</span>
5665      <a href="https://nomados.ai/privacy-policy/" target="_blank" class="hover:font-bold transition-all duration-200 cursor-pointer">
5666        Privacy Policy
5667      </a>
5668    </div>
5669  </div>
5670</div>
5671
5672          </div>
5673          
5674          <!-- Sub-Views (loaded dynamically) -->
5675          <!-- Journey Create View -->
5676          <div id="nomados-panel-journey-create" class="nomados-panel-view" style="display: none;"></div>
5677          <!-- Journey Detail View -->
5678          <div id="nomados-panel-journey-detail" class="nomados-panel-view" style="display: none;"></div>
5679          <!-- Journey Edit View -->
5680          <div id="nomados-panel-journey-edit" class="nomados-panel-view" style="display: none;"></div>
5681          <!-- Market Visa View -->
5682          <div id="nomados-panel-market-visa" class="nomados-panel-view" style="display: none;"></div>
5683          <!-- Market Healthcare View -->
5684          <div id="nomados-panel-market-healthcare" class="nomados-panel-view" style="display: none;"></div>
5685          <!-- Market Lounge View -->
5686          <div id="nomados-panel-market-lounge" class="nomados-panel-view" style="display: none;"></div>
5687          <!-- Market eSIM View -->
5688          <div id="nomados-panel-market-esim" class="nomados-panel-view" style="display: none;"></div>
5689          <!-- Destination Detail View -->
5690          <div id="nomados-panel-destination-detail" class="nomados-panel-view" style="display: none;"></div>
5691          <div id="nomados-panel-journey-preview" class="nomados-panel-view" style="display: none;"></div>
5692        </div>
5693      </div>
5694    </div>
5695    
5696    
5697 
5698
5699<!-- Feedback Modal -->
5700<div
5701  id="feedbackModal"
5702  class="fixed inset-0 z-[99999] flex items-center justify-center bg-black/50 hidden opacity-0 scale-95 transition-all duration-200 ease-out"
5703  style="backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px)"
5704>
5705  <div
5706    class="bg-white rounded-3xl shadow-2xl border border-gray-200 p-8 max-w-2xl w-full mx-4 transform transition-all duration-200"
5707  >
5708    <div class="flex items-center justify-between mb-6">
5709      <h3 class="text-xl font-semibold color-black-100">Send Us Feedback</h3>
5710      <button
5711        onclick="closeFeedbackModal()"
5712        class="text-gray-400 hover:text-gray-600 transition-colors"
5713      >
5714        <svg
5715          class="w-6 h-6"
5716          fill="none"
5717          stroke="currentColor"
5718          viewBox="0 0 24 24"
5719        >
5720          <path
5721            stroke-linecap="round"
5722            stroke-linejoin="round"
5723            stroke-width="2"
5724            d="M6 18L18 6M6 6l12 12"
5725          ></path>
5726        </svg>
5727      </button>
5728    </div>
5729
5730    <!-- Feedback Form -->
5731    <div id="feedbackFormContent">
5732      <form
5733        id="feedbackModalForm"
5734        onsubmit="submitFeedbackForm(event)"
5735        class="space-y-4"
5736      >
5737        <input type="hidden" name="csrfmiddlewaretoken" value="wW5XRNroFwTKrAvYlp7o0ZIlcpfIHaiR8jbqrSOG95se38pTkp2zbUYkj50vDt3a">
5738
5739        <div>
5740          <label
5741            for="feedbackMessage"
5742            class="block text-sm font-medium color-gray-100 mb-1"
5743            >Your Message<sup
5744              style="color: #FF735D; font-size: 10px;"
5745              class="font-semibold"
5746              >✱</sup
5747            ></label
5748          >
5749          <textarea
5750            id="feedbackMessage"
5751            name="message"
5752            rows="4"
5753            class="w-full px-4 py-3 border border-solid border-[#bbb] rounded-3xl transition-colors"
5754            style="box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.09) inset"
5755            placeholder="Tell us what you think, what we can improve, or any issues you've found..."
5756            required
5757          ></textarea>
5758        </div>
5759
5760        <div class="flex justify-end gap-4 pt-2">
5761          <button
5762            type="button"
5763            onclick="closeFeedbackModal()"
5764            class="flex bg-white items-center hover:bg-gray-200 color-black-100 ps-2 py-1.5 pe-4 border border-solid border-[#bbb] hover:border-gray-200 rounded-full font-medium transition duration-200 gap-1"
5765          >
5766            <img
5767              src="/static/icons/cancel.svg"
5768              alt="Cancel"
5769              draggable="false"
5770            />
5771            Cancel
5772          </button>
5773          <button
5774            type="submit"
5775            class="flex bg-white items-center hover:bg-gray-200 color-black-100 ps-2 py-1.5 pe-4 border border-solid border-[#bbb] hover:border-gray-200 rounded-full font-medium transition duration-200 gap-1"
5776          >
5777            <img
5778              src="/static/icons/create_journey.svg"
5779              alt="Create Journey"
5780              draggable="false"
5781            />
5782            Send Feedback
5783          </button>
5784        </div>
5785      </form>
5786    </div>
5787
5788    <!-- Thank You Message -->
5789    <div id="feedbackThankYouContent" class="hidden text-center">
5790      <div class="mb-6">
5791        <div
5792          class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-4"
5793        >
5794          <svg
5795            class="h-8 w-8 text-green-600"
5796            fill="none"
5797            stroke="currentColor"
5798            viewBox="0 0 24 24"
5799          >
5800            <path
5801              stroke-linecap="round"
5802              stroke-linejoin="round"
5803              stroke-width="2"
5804              d="M5 13l4 4L19 7"
5805            ></path>
5806          </svg>
5807        </div>
5808        <h3 class="text-xl font-bold text-gray-900 mb-2">Thank You!</h3>
5809        <p class="text-gray-600">
5810          Your feedback has been submitted successfully. We appreciate you
5811          taking the time to help us improve!
5812        </p>
5813      </div>
5814
5815      <button
5816        onclick="closeFeedbackModal()"
5817        class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-3 rounded-lg font-medium transition duration-200"
5818      >
5819        Close
5820      </button>
5821    </div>
5822  </div>
5823</div>
5824
5825<!-- Feedback Button (Floating) -->
5826<div class="fixed hidden lg:block bottom-16 right-6 z-40">
5827  <button
5828    onclick="openFeedbackModal()"
5829    class="bg-dark-100 text-white p-3 rounded-full shadow-lg hover:bg-stone-700 focus:outline-none focus:ring-2 focus:ring-stone-500 focus:ring-offset-2 transition duration-200 group"
5830    title="Send Feedback"
5831  >
5832    <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5833      <path
5834        stroke-linecap="round"
5835        stroke-linejoin="round"
5836        stroke-width="2"
5837        d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
5838      ></path>
5839    </svg>
5840  </button>
5841</div>
5842
5843<script>
5844  // Modal functions
5845  function openFeedbackModal() {
5846    const modal = document.getElementById("feedbackModal");
5847    if (modal) {
5848      modal.classList.remove("hidden");
5849      setTimeout(() => {
5850        modal.classList.remove("opacity-0", "scale-95");
5851        modal.classList.add("opacity-100", "scale-100");
5852      }, 10);
5853      document.getElementById("feedbackMessage").focus();
5854    }
5855  }
5856
5857  function closeFeedbackModal() {
5858    const modal = document.getElementById("feedbackModal");
5859    if (modal) {
5860      modal.classList.add("opacity-0", "scale-95");
5861      modal.classList.remove("opacity-100", "scale-100");
5862      setTimeout(() => {
5863        modal.classList.add("hidden");
5864      }, 200);
5865
5866      // Reset to form view
5867      document.getElementById("feedbackFormContent").classList.remove("hidden");
5868      document
5869        .getElementById("feedbackThankYouContent")
5870        .classList.add("hidden");
5871      document.getElementById("feedbackModalForm").reset();
5872    }
5873  }
5874
5875  function submitFeedbackForm(e) {
5876    e.preventDefault();
5877
5878    const form = document.getElementById("feedbackModalForm");
5879    const submitBtn = form.querySelector('button[type="submit"]');
5880    const messageTextarea = document.getElementById("feedbackMessage");
5881
5882    // Disable form
5883    submitBtn.disabled = true;
5884    submitBtn.textContent = "Sending...";
5885
5886    fetch('/feedback/submit/', {
5887      method: "POST",
5888      headers: {
5889        "Content-Type": "application/json",
5890        "X-CSRFToken": document.querySelector("[name=csrfmiddlewaretoken]")
5891          .value,
5892        "X-Requested-With": "XMLHttpRequest",
5893      },
5894      body: JSON.stringify({
5895        message: messageTextarea.value,
5896      }),
5897    })
5898      .then((response) => {
5899        // Check if response is JSON
5900        const contentType = response.headers.get("content-type");
5901        if (contentType && contentType.includes("application/json")) {
5902          return response.json();
5903        } else {
5904          throw new Error("Server returned non-JSON response");
5905        }
5906      })
5907      .then((data) => {
5908        if (data.success) {
5909          // Hide form and show thank you message
5910          document
5911            .getElementById("feedbackFormContent")
5912            .classList.add("hidden");
5913          document
5914            .getElementById("feedbackThankYouContent")
5915            .classList.remove("hidden");
5916        } else {
5917          throw new Error(data.error || "Failed to submit feedback");
5918        }
5919      })
5920      .catch((error) => {
5921        console.error("Error:", error);
5922        alert("An error occurred while submitting feedback. Please try again.");
5923      })
5924      .finally(() => {
5925        // Re-enable form
5926        submitBtn.disabled = false;
5927        submitBtn.textContent = "Send Feedback";
5928      });
5929  }
5930
5931  // Close modal when clicking outside
5932  document.addEventListener("click", function (e) {
5933    if (e.target.id === "feedbackModal") {
5934      closeFeedbackModal();
5935    }
5936  });
5937
5938  // Close modal with Escape key
5939  document.addEventListener("keydown", function (e) {
5940    if (e.key === "Escape") {
5941      closeFeedbackModal();
5942    }
5943  });
5944</script>
5944
5945
5946    
5947<!-- Go Pro subscription modal: SamCart checkout in iframe -->
5948<div
5949  id="goProModal"
5950  class="fixed inset-0 z-[99998] hidden items-center justify-center bg-black/50 opacity-0 transition-opacity duration-200 ease-out overflow-y-auto py-8"
5951  style="backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);"
5952  aria-hidden="true"
5953  role="dialog"
5954  aria-labelledby="goProModalTitle"
5955  data-samcart-monthly-url="https://nomados.samcart.com/products/nomados-pro-monthly-internal"
5956  data-samcart-annual-url="https://nomados.samcart.com/products/nomados-pro-annual-internal"
5957>
5958  <div
5959    id="goProModalCard"
5960    class="relative bg-white rounded-3xl shadow-xl border border-gray-100 w-full max-w-md mx-4 my-auto transform transition-all duration-200"
5961    onclick="event.stopPropagation()"
5962  >
5963    <button
5964      type="button"
5965      onclick="closeGoProModal()"
5966      class="absolute top-1 right-2 bg-white border border-[#e5e7eb] rounded-full hover:bg-[#f3f4f6] hover:scale-[1.1] z-10 shadow-sm transition-all duration-200 w-8 h-8 flex items-center justify-center"
5967      aria-label="Close"
5968    >
5969      <img
5970        src="/static/icons/close-dark.svg"
5971        alt="Close"
5972        draggable="false"
5973      />
5974    </button>
5975
5976    <!-- Step 1: Pricing choice (optional; when opened from panel we go straight to iframe) -->
5977    <div id="goProStepPricing" class="relative pt-10 pb-8 px-6 md:px-8">
5978      <h2 id="goProModalTitle" class="text-2xl md:text-3xl font-bold text-[#3B3B3C] text-center mb-2">Go Pro</h2>
5979      <p class="text-sm md:text-base text-gray-500 text-center mb-8">Everything you need to live and work anywhere, seamlessly.</p>
5980      <div class="rounded-2xl border border-gray-200 bg-gray-50/50 p-5 md:p-6 mb-6" style="box-shadow: 0 4px 12px rgba(0,0,0,0.06);">
5981        <div class="flex rounded-xl bg-gray-200 p-1 mb-5">
5982          <button type="button" id="goProModalToggleMonthly" onclick="setGoProBilling('monthly'); setGoProModalPlan('monthly');" class="flex-1 py-2.5 px-3 text-sm font-medium rounded-lg transition-colors go-pro-modal-toggle-active bg-white text-[#3B3B3C] shadow-sm">Monthly</button>
5983          <button type="button" id="goProModalToggleAnnual" onclick="setGoProBilling('annual'); setGoProModalPlan('annual');" class="flex-1 py-2.5 px-3 text-sm font-medium rounded-lg transition-colors text-[#3B3B3C]">Annual <span class="bg-[#14B8A6] text-white text-xs font-semibold px-2 py-0.5 rounded-full">Save 45%</span></button>
5984        </div>
5985        <div id="goProModalPriceMonthly" class="go-pro-modal-price text-center mb-7">
5986          <span class="text-4xl font-bold text-[#3B3B3C]">$9</span><span class="text-gray-500 text-base ml-1">/month</span>
5987        </div>
5988        <div id="goProModalPriceAnnual" class="go-pro-modal-price hidden text-center mb-1">
5989          <span class="text-4xl font-bold text-[#3B3B3C]">$59</span><span class="text-gray-500 text-base ml-1">/year</span>
5990          <p class="text-sm text-gray-500 mt-1">Just $4.92/month, billed annually</p>
5991        </div>
5992        <button type="button" onclick="submitGoProCheckout()" class="w-full py-3.5 rounded-xl bg-[#3B3B3C] text-white font-semibold text-base hover:bg-[#2d2d2e] transition-colors">Get Nomados Pro</button>
5993        <p class="text-xs text-gray-400 text-center mt-3">Checkout opens below.</p>
5994      </div>
5995    </div>
5996
5997    <!-- Step 2: Checkout iframe (SamCart) -->
5998    <div id="goProStepIframe" class="relative hidden pt-10 pb-6 px-4">
5999      <div class="rounded-xl border border-gray-200 overflow-hidden bg-gray-100" style="min-height: 520px;">
6000        <iframe
6001          id="goProCheckoutIframe"
6002          title="Nomados Pro checkout"
6003          class="w-full border-0"
6004          style="height: 520px; min-height: 520px;"
6005          sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
6006        ></iframe>
6007      </div>
6008      <p class="text-xs text-gray-400 text-center mt-3">Secure checkout by SamCart. Use the same email as your Nomados account.</p>
6009    </div>
6010  </div>
6011</div>
6012
6013<script>
6014(function() {
6015  var FALLBACK_MONTHLY = 'https://nomados.samcart.com/products/nomados\u002Dpro\u002Dmonthly\u002Dinternal';
6016  var FALLBACK_ANNUAL = 'https://nomados.samcart.com/products/nomados\u002Dpro\u002Dannual\u002Dinternal';
6017
6018  function getCheckoutUrlFromModal() {
6019    var modal = document.getElementById('goProModal');
6020    if (!modal) return '';
6021    var plan = (modal.getAttribute('data-selected-plan') || 'monthly').trim();
6022    return plan === 'annual'
6023      ? (modal.getAttribute('data-samcart-annual-url') || '').trim() || FALLBACK_ANNUAL
6024      : (modal.getAttribute('data-samcart-monthly-url') || '').trim() || FALLBACK_MONTHLY;
6025  }
6026
6027  window.openGoProModal = function() {
6028    var modal = document.getElementById('goProModal');
6029    if (!modal) return;
6030    document.getElementById('goProStepPricing').classList.remove('hidden');
6031    document.getElementById('goProStepIframe').classList.add('hidden');
6032    var card = document.getElementById('goProModalCard');
6033    if (card) { card.classList.remove('max-w-4xl'); card.classList.add('max-w-md'); }
6034    modal.classList.remove('hidden');
6035    modal.classList.add('flex');
6036    requestAnimationFrame(function() { modal.classList.remove('opacity-0'); });
6037    document.body.style.overflow = 'hidden';
6038  };
6039
6040  window.closeGoProModal = function() {
6041    var modal = document.getElementById('goProModal');
6042    if (!modal) return;
6043    var iframe = document.getElementById('goProCheckoutIframe');
6044    if (iframe && iframe.src) iframe.src = 'about:blank';
6045    modal.classList.add('opacity-0');
6046    setTimeout(function() {
6047      modal.classList.add('hidden');
6048      modal.classList.remove('flex');
6049      document.body.style.overflow = '';
6050    }, 200);
6051    // Hide processing loaders — no purchase was completed if user closed the modal
6052    var memberLoader = document.getElementById('membership-processing-loader');
6053    var goProLoader = document.getElementById('go-pro-processing-loader');
6054    if (memberLoader) { memberLoader.classList.add('hidden'); memberLoader.classList.remove('flex'); }
6055    if (goProLoader) { goProLoader.classList.add('hidden'); goProLoader.classList.remove('flex'); }
6056  };
6057
6058  window.setGoProModalPlan = function(plan) {
6059    var modal = document.getElementById('goProModal');
6060    if (modal) modal.setAttribute('data-selected-plan', plan);
6061    var monthlyBlock = document.getElementById('goProModalPriceMonthly');
6062    var annualBlock = document.getElementById('goProModalPriceAnnual');
6063    var btnMonthly = document.getElementById('goProModalToggleMonthly');
6064    var btnAnnual = document.getElementById('goProModalToggleAnnual');
6065    if (!monthlyBlock || !annualBlock || !btnMonthly || !btnAnnual) return;
6066    if (plan === 'monthly') {
6067      monthlyBlock.classList.remove('hidden');
6068      annualBlock.classList.add('hidden');
6069      btnMonthly.classList.add('go-pro-modal-toggle-active', 'bg-white', 'shadow-sm', 'text-[#3B3B3C]');
6070      btnAnnual.classList.remove('go-pro-modal-toggle-active', 'bg-white', 'shadow-sm');
6071      btnAnnual.classList.add('text-[#3B3B3C]');
6072    } else {
6073      annualBlock.classList.remove('hidden');
6074      monthlyBlock.classList.add('hidden');
6075      btnAnnual.classList.add('go-pro-modal-toggle-active', 'bg-white', 'shadow-sm', 'text-[#3B3B3C]');
6076      btnMonthly.classList.remove('go-pro-modal-toggle-active', 'bg-white', 'shadow-sm');
6077      btnMonthly.classList.add('text-[#3B3B3C]');
6078    }
6079  };
6080
6081  window.submitGoProCheckout = function() {
6082    var modal = document.getElementById('goProModal');
6083    if (!modal) return;
6084    var ctaBlock = document.getElementById('go-pro-cta-block');
6085    if (ctaBlock) {
6086      var plan = (ctaBlock.getAttribute('data-selected-plan') || 'monthly').trim();
6087      modal.setAttribute('data-selected-plan', plan);
6088    } else {
6089      modal.setAttribute('data-selected-plan', modal.getAttribute('data-selected-plan') || 'monthly');
6090    }
6091    var url = getCheckoutUrlFromModal();
6092    var stepPricing = document.getElementById('goProStepPricing');
6093    var stepIframe = document.getElementById('goProStepIframe');
6094    var iframe = document.getElementById('goProCheckoutIframe');
6095    var linkNewTab = document.getElementById('goProOpenNewTab');
6096    var card = document.getElementById('goProModalCard');
6097    if (!stepPricing || !stepIframe || !iframe) return;
6098    var modalNotOpen = !modal.classList.contains('flex');
6099    if (modalNotOpen) openGoProModal();
6100    function showIframeStep() {
6101      stepPricing.classList.add('hidden');
6102      stepIframe.classList.remove('hidden');
6103      if (card) { card.classList.remove('max-w-md'); card.classList.add('max-w-4xl'); }
6104      iframe.src = url || 'about:blank';
6105      if (linkNewTab) linkNewTab.href = url || '#';
6106      // Show processing loader on membership card while checkout is happening
6107      var memberLoader = document.getElementById('membership-processing-loader');
6108      var goProLoader = document.getElementById('go-pro-processing-loader');
6109      if (memberLoader) {
6110        var txt = memberLoader.querySelector('#membership-loader-text');
6111        if (txt) txt.textContent = 'Processing your subscription...';
6112        memberLoader.classList.remove('hidden'); memberLoader.classList.add('flex');
6113      }
6114      if (goProLoader) {
6115        var txt2 = goProLoader.querySelector('#go-pro-loader-text');
6116        if (txt2) txt2.textContent = 'Processing your subscription...';
6117        goProLoader.classList.remove('hidden'); goProLoader.classList.add('flex');
6118      }
6119    }
6120    if (modalNotOpen) setTimeout(showIframeStep, 50);
6121    else showIframeStep();
6122  };
6123
6124  document.addEventListener('click', function(e) {
6125    var modal = document.getElementById('goProModal');
6126    if (modal && modal.classList.contains('flex') && e.target === modal) closeGoProModal();
6127  });
6128})();
6129</script>
6129
6130
6131
6132    <!-- Nomados 2.0 Panel Management JavaScript -->
6133    
6133<script>
6134      // Journey limit tracking
6135      window._journeyCount = 0;
6136      window._maxJourneys = null;
6137
6138      /**
6139       * Nomados 2.0 Panel Management System
6140       * Global functions for managing the right panel and sidebar
6141       */
6142      
6143      // Panel state management with navigation stack
6144      const NomadosPanel = {
6145        currentView: null,
6146        isOpen: false,
6147        panelElement: null,
6148        panelBody: null,
6149        panelHeader: null,
6150        panelHeaderTitle: null,
6151        panelCloseBtn: null,
6152        navigationStack: [], // Stack to track navigation history
6153        previousView: null, // Track previous view for back navigation (e.g., destination-detail -> journey-create -> journey)
6154        previousViewOptions: null, // Store options for previous view (e.g., citySlug for destination-detail)
6155        
6156        init: function() {
6157          // Wait for DOM to be fully loaded
6158          if (document.readyState === 'loading') {
6159            document.addEventListener('DOMContentLoaded', () => this.initialize());
6160          } else {
6161            this.initialize();
6162          }
6163        },
6164        
6165        initialize: function() {
6166          this.panelElement = document.getElementById('nomados-panel');
6167          this.panelBody = document.getElementById('nomados-panel-body');
6168          this.panelHeader = document.getElementById('nomados-panel-header');
6169          this.panelHeaderTitle = document.getElementById('nomados-panel-header-title');
6170          this.panelCloseBtn = document.getElementById('nomados-panel-close-btn');
6171          
6172          if (!this.panelElement) {
6173            console.error('Nomados panel element not found');
6174            return;
6175          }
6176          
6177          if (!this.panelBody) {
6178            console.error('Nomados panel body element not found');
6179            return;
6180          }
6181          
6182          // Initialize sidebar buttons - Miles should be active when panel is closed
6183          this.updateSidebarButtons();
6184          
6185          // Verify view elements exist
6186          const viewIds = ['nomados-panel-journey', 'nomados-panel-market', 'nomados-panel-profile'];
6187          viewIds.forEach(id => {
6188            const element = document.getElementById(id);
6189            if (!element) {
6190              console.warn('View element not found:', id);
6191            }
6192          });
6193          
6194          // Close panel on Escape key
6195          document.addEventListener('keydown', (e) => {
6196            if (e.key === 'Escape' && this.isOpen) {
6197              // Check if we're in a sub-view
6198              const subViews = ['journey-detail', 'journey-create', 'journey-edit', 'journey-preview', 'market-visa', 'market-healthcare', 'market-lounge', 'market-esim', 'destination-detail'];
6199              if (subViews.includes(this.currentView)) {
6200                this.goBack();
6201              } else {
6202                this.close();
6203              }
6204            }
6205          });
6206          
6207          // Initialize sidebar button states
6208          this.updateSidebarButtons();
6209        },
6210        
6211        open: function(view, options = {}) {
6212          if (!this.panelElement || !this.panelBody) {
6213            console.error('Panel elements not initialized');
6214            return;
6215          }
6216
6217          // Close Go Pro view if it's open
6218          var goProViewEl = document.getElementById('go-pro-view');
6219          if (goProViewEl && !goProViewEl.classList.contains('hidden') && typeof closeGoProView === 'function') {
6220            closeGoProView();
6221          }
6222          
6223          // Track previous view for back navigation
6224          // Store destination-detail city slug when opening it
6225          if (view === 'destination-detail' && options && options.citySlug) {
6226            // Store city slug for potential back navigation
6227            this.previousViewOptions = { citySlug: options.citySlug };
6228          }
6229          
6230          // If opening journey-create from destination-detail, remember it and its options
6231          if (view === 'journey-create' && this.currentView === 'destination-detail') {
6232            this.previousView = 'destination-detail';
6233            // Store the city slug from current options or from stored destination city slug
6234            if (options && options.citySlug) {
6235              this.previousViewOptions = { citySlug: options.citySlug };
6236            } else if (this.previousViewOptions && this.previousViewOptions.citySlug) {
6237              // Keep the existing city slug from destination-detail
6238            } else {
6239              // Try to extract city slug from the URL used to load destination-detail
6240              // The URL pattern is /chat/panel/destination/<city_slug>/
6241              const destView = document.getElementById('nomados-panel-destination-detail');
6242              if (destView) {
6243                // Try to get from the fetch URL that loaded this view
6244                // We'll extract it from the current URL or stored in a data attribute
6245                const urlMatch = window.location.href.match(/\/panel\/destination\/([^\/]+)\//);
6246                if (urlMatch) {
6247                  this.previousViewOptions = { citySlug: urlMatch[1] };
6248                }
6249              }
6250            }
6251          }
6252          // If opening journey list after creating from destination-detail, keep previous view and options
6253          else if (view === 'journey' && this.previousView === 'destination-detail' && this.currentView === 'journey-create') {
6254            // Keep previousView and previousViewOptions as destination-detail
6255          }
6256          // Clear previous view when opening main views directly
6257          else {
6258            const mainViews = ['journey', 'market', 'profile'];
6259            if (mainViews.includes(view)) {
6260              this.previousView = null;
6261              this.previousViewOptions = null;
6262              this.navigationStack = [];
6263            }
6264          }
6265          
6266          // Track previous view for back navigation
6267          // Store destination-detail city slug when opening it
6268          if (view === 'destination-detail' && options && options.citySlug) {
6269            // Store city slug for potential back navigation
6270            this.previousViewOptions = { citySlug: options.citySlug };
6271          }
6272          
6273          // If opening journey-create from destination-detail, remember it and its options
6274          if (view === 'journey-create' && this.currentView === 'destination-detail') {
6275            this.previousView = 'destination-detail';
6276            // Store the city slug from current options or from stored destination city slug
6277            if (options && options.citySlug) {
6278              this.previousViewOptions = { citySlug: options.citySlug };
6279            } else if (this.previousViewOptions && this.previousViewOptions.citySlug) {
6280              // Keep the existing city slug from destination-detail
6281            } else {
6282              // Try to extract city slug from the URL used to load destination-detail
6283              // The URL pattern is /experiments/panel/destination/<city_slug>/
6284              const destView = document.getElementById('nomados-panel-destination-detail');
6285              if (destView) {
6286                // Try to get from the fetch URL that loaded this view
6287                // We'll extract it from the current URL or stored in a data attribute
6288                const urlMatch = window.location.href.match(/\/panel\/destination\/([^\/]+)\//);
6289                if (urlMatch) {
6290                  this.previousViewOptions = { citySlug: urlMatch[1] };
6291                }
6292              }
6293            }
6294          }
6295          // If opening journey list after creating from destination-detail, keep previous view and options
6296          else if (view === 'journey' && this.previousView === 'destination-detail' && this.currentView === 'journey-create') {
6297            // Keep previousView and previousViewOptions as destination-detail
6298          }
6299          // Clear previous view when opening main views directly
6300          else {
6301            const mainViews = ['journey', 'market', 'profile'];
6302            if (mainViews.includes(view)) {
6303              this.previousView = null;
6304              this.previousViewOptions = null;
6305              this.navigationStack = [];
6306            }
6307          }
6308          
6309          // Simplified navigation: don't track history, just set current view
6310          // Navigation stack is no longer used for back button (requirement #2)
6311          const mainViews = ['journey', 'market', 'profile'];
6312          if (mainViews.includes(view)) {
6313            this.navigationStack = [];
6314          }
6315
6316          // Track journey-preview → market navigation so back returns to preview
6317          const marketSubViews = ['market-visa', 'market-healthcare', 'market-lounge', 'market-esim'];
6318          if (this.currentView === 'journey-preview' && marketSubViews.includes(view)) {
6319            this._returnToPreview = true;
6320          } else if (!marketSubViews.includes(view)) {
6321            // Clear the flag when navigating to anything other than a market sub-view
6322            this._returnToPreview = false;
6323          }
6324
6325          // Track whether market sub-view was opened from the market view
6326          // If opened from chat or elsewhere, back should close the panel instead of going to market
6327          if (marketSubViews.includes(view)) {
6328            this._serviceOpenedFromMarket = (this.currentView === 'market');
6329          }
6330
6331          this.currentView = view;
6332          this.isOpen = true;
6333          
6334          // Update sidebar button states
6335          this.updateSidebarButtons();
6336          
6337          // Load content for the view
6338          this.loadViewContent(view, options);
6339          
6340          // Update header visibility
6341          this.updateHeaderVisibility(view, options);
6342          
6343          // Open panel with animation
6344          this.panelElement.classList.add('open');
6345        },
6346
6347        close: function() {
6348          if (!this.panelElement) {
6349            console.error('Panel element not found');
6350            return;
6351          }
6352
6353          // On mobile, restore the chat header and reset panel padding
6354          if (window.innerWidth < 768) {
6355            var mh = document.querySelector('header.md\\:hidden');
6356            if (mh) mh.style.display = '';
6357            var panelContent = this.panelElement ? this.panelElement.querySelector('.nomados-panel-content') : null;
6358            if (panelContent) panelContent.style.paddingTop = '';
6359            // Hide the journey tab dropdown, view title, and restore logo in header
6360            var headerDropdown = document.getElementById('mobile-header-journey-dropdown');
6361            if (headerDropdown) headerDropdown.classList.add('hidden');
6362            var headerViewTitle = document.getElementById('mobile-header-view-title');
6363            if (headerViewTitle) { headerViewTitle.classList.add('hidden'); headerViewTitle.textContent = ''; }
6364            var headerLogo = document.getElementById('mobile-header-logo');
6365            if (headerLogo) headerLogo.classList.remove('hidden');
6366          }
6367
6368          // Cleanup view-specific state before closing
6369          if (this.currentView === 'market-esim') {
6370            this.cleanupESimView();
6371          } else if (this.currentView === 'market-lounge') {
6372            if (typeof window.resetLoungeView === 'function') {
6373              try { window.resetLoungeView(); } catch (e) {}
6374            }
6375          }
6376          
6377          this.isOpen = false;
6378          this._returnToPreview = false;
6379          // Don't clear navigation stack on close - keep it for reopening
6380          // this.navigationStack = [];
6381          this.panelElement.classList.remove('open');
6382
6383          // Flush pending essentials step now that user closed the panel
6384          if (typeof window._flushPendingEssentialStep === 'function') {
6385            window._flushPendingEssentialStep();
6386          }
6387          
6388          // Hide current view after animation
6389          setTimeout(() => {
6390            if (!this.isOpen && this.currentView) {
6391              const viewId = this.getViewId(this.currentView);
6392              if (viewId) {
6393                const viewElement = document.getElementById(viewId);
6394                if (viewElement) {
6395                  viewElement.style.display = 'none';
6396                  // Clear content for sub-views to force fresh load next time
6397                  // Healthcare view is preserved — its React widget cannot be reloaded
6398                  const subViews = ['journey-create', 'journey-detail', 'journey-preview', 'market-visa', 'market-lounge', 'market-esim', 'destination-detail'];
6399                  if (subViews.includes(this.currentView)) {
6400                    viewElement.innerHTML = '';
6401                  }
6402                }
6403              }
6404            }
6405            this.currentView = null;
6406            this.updateSidebarButtons();
6407            this.hideHeader();
6408          }, 300);
6409        },
6410        
6411        cleanupESimView: function() {
6412          // Reset eSIM view state
6413          if (typeof window.resetESimView === 'function') {
6414            try {
6415              window.resetESimView();
6416            } catch (err) {
6417              console.error('Error resetting eSIM view:', err);
6418            }
6419          }
6420        },
6421        
6422         goBack: function() {
6423          // Cleanup view-specific state before going back
6424          if (this.currentView === 'market-esim') {
6425            this.cleanupESimView();
6426          } else if (this.currentView === 'market-lounge') {
6427            if (typeof window.resetLoungeView === 'function') {
6428              try { window.resetLoungeView(); } catch (e) {}
6429            }
6430          }
6431          
6432          // Clear current view content before going back
6433          // Healthcare view is preserved — its React widget cannot be reloaded
6434          if (this.currentView && this.currentView !== 'market-healthcare') {
6435            const viewId = this.getViewId(this.currentView);
6436            if (viewId) {
6437              const viewElement = document.getElementById(viewId);
6438              if (viewElement) {
6439                viewElement.innerHTML = '';
6440              }
6441            }
6442          }
6443           
6444           // On mobile, navigate back to parent view if on a sub-view, otherwise close
6445           if (window.innerWidth < 768) {
6446             const mobileViewToParent = {
6447               'journey-detail': 'journey',
6448               'journey-create': 'journey',
6449               'journey-edit': 'journey-detail',
6450               'journey-preview': null, // close panel (accessed via "Go to Journey" button)
6451               'market-visa': this._serviceOpenedFromMarket ? 'market' : null,
6452               'market-healthcare': this._serviceOpenedFromMarket ? 'market' : null,
6453               'market-lounge': this._serviceOpenedFromMarket ? 'market' : null,
6454               'market-esim': this._serviceOpenedFromMarket ? 'market' : null,
6455               'destination-detail': null, // close panel
6456             };
6457             const parentView = mobileViewToParent[this.currentView];
6458             if (parentView) {
6459               // Clear current view content
6460               const curViewId = this.getViewId(this.currentView);
6461               if (curViewId) {
6462                 const curEl = document.getElementById(curViewId);
6463                 if (curEl) curEl.innerHTML = '';
6464               }
6465               this.currentView = parentView;
6466               this.loadViewContent(parentView, { skipStack: true });
6467               this.updateHeaderVisibility(parentView);
6468               return;
6469             }
6470             this._returnToPreview = false;
6471             this.close();
6472             return;
6473           }
6474
6475           // Special case: return to journey-preview from market sub-views (desktop only)
6476           const marketViews = ['market-visa', 'market-healthcare', 'market-lounge', 'market-esim'];
6477           if (this._returnToPreview && marketViews.includes(this.currentView)) {
6478             // Cleanup current market view content
6479             const curViewId = this.getViewId(this.currentView);
6480             if (curViewId) {
6481               const curEl = document.getElementById(curViewId);
6482               if (curEl) {
6483                 if (this.currentView === 'market-esim') this.cleanupESimView();
6484                 if (this.currentView === 'market-lounge' && typeof window.resetLoungeView === 'function') {
6485                   try { window.resetLoungeView(); } catch (e) {}
6486                 }
6487                 // Healthcare view is preserved — its React widget cannot be reloaded
6488                 if (this.currentView !== 'market-healthcare') {
6489                   curEl.innerHTML = '';
6490                 }
6491               }
6492             }
6493             this._returnToPreview = false;
6494             this.currentView = 'journey-preview';
6495             this.navigationStack = [];
6496             // Show existing journey-preview content (preserved in DOM)
6497             var allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
6498             allViews.forEach(function(v) { if (v && v.id) v.style.display = 'none'; });
6499             var previewEl = document.getElementById('nomados-panel-journey-preview');
6500             if (previewEl && previewEl.innerHTML.trim()) {
6501               previewEl.style.display = 'block';
6502               this.updateHeaderVisibility('journey-preview');
6503               this.updateSidebarButtons();
6504               if (this.panelBody) this.panelBody.scrollTop = 0;
6505             } else {
6506               // Fallback to journey list if preview content was lost
6507               this.loadViewContent('journey', { skipStack: true });
6508               this.updateHeaderVisibility('journey');
6509             }
6510             // Flush pending essentials step now that user left the service panel
6511             if (typeof window._flushPendingEssentialStep === 'function') {
6512               window._flushPendingEssentialStep();
6513             }
6514             return;
6515           }
6516
6517           // Special case: if we're at journey list and came from destination-detail, go back there
6518           if (this.currentView === 'journey' && this.previousView === 'destination-detail') {
6519             const citySlug = this.previousViewOptions && this.previousViewOptions.citySlug;
6520             
6521             // Go back to destination-detail
6522             const prevView = this.previousView;
6523             const prevOptions = this.previousViewOptions;
6524             this.previousView = null; // Clear previous view
6525             this.previousViewOptions = null;
6526             this.currentView = 'destination-detail';
6527             
6528             if (citySlug) {
6529               this.loadViewContent('destination-detail', { 
6530                 skipStack: true,
6531                 citySlug: citySlug 
6532               });
6533               this.updateHeaderVisibility('destination-detail');
6534             } else {
6535               // Fallback: just close the panel
6536               this.close();
6537             }
6538             return;
6539           }
6540           
6541           // Map sub-views to their main views
6542           // Market sub-views only go back to market if opened from market, otherwise close
6543           const viewToMainView = {
6544             'journey-detail': 'journey',
6545             'journey-preview': 'journey',
6546             'journey-create': 'journey',
6547             'journey-edit': 'journey-detail', // Edit goes back to detail view
6548             'market-visa': this._serviceOpenedFromMarket ? 'market' : null,
6549             'market-healthcare': this._serviceOpenedFromMarket ? 'market' : null,
6550             'market-lounge': this._serviceOpenedFromMarket ? 'market' : null,
6551             'market-esim': this._serviceOpenedFromMarket ? 'market' : null,
6552             // destination-detail should close panel (no back button, shows miles)
6553           };
6554
6555          const mainView = viewToMainView[this.currentView];
6556
6557          if (mainView) {
6558            // Cleanup view-specific state before switching
6559            if (this.currentView === 'market-esim') {
6560              this.cleanupESimView();
6561            } else if (this.currentView === 'market-lounge' && typeof window.resetLoungeView === 'function') {
6562              try { window.resetLoungeView(); } catch (e) {}
6563            }
6564
6565            // Go to main view
6566            this.currentView = mainView;
6567            this.navigationStack = []; // Clear navigation stack
6568            this.loadViewContent(mainView, { skipStack: true });
6569            this.updateHeaderVisibility(mainView);
6570          } else {
6571            // Already at main view (or destination-detail), close panel
6572            this.close();
6573          }
6574         },
6575        
6576         getViewId: function(view) {
6577           const viewMap = {
6578            'destination-detail': 'nomados-panel-destination-detail',
6579            'journey': 'nomados-panel-journey',
6580            'journey-detail': 'nomados-panel-journey-detail',
6581            'journey-preview': 'nomados-panel-journey-preview',
6582            'journey-create': 'nomados-panel-journey-create',
6583            'journey-edit': 'nomados-panel-journey-edit',
6584            'market': 'nomados-panel-market',
6585            'market-visa': 'nomados-panel-market-visa',
6586            'market-healthcare': 'nomados-panel-market-healthcare',
6587            'market-lounge': 'nomados-panel-market-lounge',
6588            'market-esim': 'nomados-panel-market-esim',
6589            'profile': 'nomados-panel-profile',
6590          };
6591          return viewMap[view] || null;
6592        },
6593        
6594        getViewTitle: function(view) {
6595          const titleMap = {
6596            'destination-detail': '', // Hide title for destination-detail
6597            'journey': 'My Journeys',
6598            'journey-detail': '', // Hide title for journey-detail
6599            'journey-preview': '', // Hide title for journey-preview
6600            'journey-create': 'New Journey',
6601            'journey-edit': 'Edit Journey',
6602            'market': 'Marketplace',
6603            'market-visa': 'Visa Services',
6604            'market-healthcare': 'Health Insurance',
6605            'market-lounge': 'Airport Lounge',
6606            'market-esim': 'Mobile Internet (eSIM)',
6607            'profile': 'Profile',
6608          };
6609          return titleMap[view] || '';
6610        },
6611        
6612        updateHeaderVisibility: function(view, options = {}) {
6613          if (!this.panelHeader || !this.panelHeaderTitle || !this.panelCloseBtn) return;
6614          
6615          const mainViews = ['journey', 'market', 'profile', 'destination-detail'];
6616          const isMainView = mainViews.includes(view);
6617          const isMobile = screen.width < 768;
6618          
6619          // Add mobile-specific classes to panel header
6620          if (isMobile) {
6621            this.panelHeader.classList.add('mobile-panel-header');
6622            // Add view-specific mobile classes
6623            this.panelHeader.classList.remove('mobile-journey-detail', 'mobile-destination-detail');
6624            if (view === 'journey-detail' || view === 'journey-preview') {
6625              this.panelHeader.classList.add('mobile-journey-detail');
6626            } else if (view === 'destination-detail') {
6627              this.panelHeader.classList.add('mobile-destination-detail');
6628            }
6629          } else {
6630            this.panelHeader.classList.remove('mobile-panel-header', 'mobile-journey-detail', 'mobile-destination-detail');
6631          }
6632
6633          // Add mobile-specific classes to panel body
6634          if (this.panelBody) {
6635            if (isMobile) {
6636              // Remove all view-specific classes first
6637              this.panelBody.classList.remove('mobile-journey-detail-body', 'mobile-destination-detail-body');
6638              // Add view-specific classes
6639              if (view === 'journey-detail' || view === 'journey-preview') {
6640                this.panelBody.classList.add('mobile-journey-detail-body');
6641              } else if (view === 'destination-detail') {
6642                this.panelBody.classList.add('mobile-destination-detail-body');
6643              }
6644            } else {
6645              this.panelBody.classList.remove('mobile-journey-detail-body', 'mobile-destination-detail-body');
6646            }
6647          }
6648          
6649          // Hide/show mobile header (the top navigation header) based on view
6650          if (isMobile) {
6651            const mobileHeader = document.querySelector('header.md\\:hidden');
6652            const panelContent = this.panelElement ? this.panelElement.querySelector('.nomados-panel-content') : null;
6653            const headerDropdown = document.getElementById('mobile-header-journey-dropdown');
6654            if (mobileHeader) {
6655              const hideHeaderViews = ['journey-detail', 'journey-preview', 'destination-detail'];
6656              if (hideHeaderViews.includes(view)) {
6657                mobileHeader.style.display = 'none';
6658                if (panelContent) panelContent.style.paddingTop = '';
6659              } else {
6660                mobileHeader.style.display = 'block';
6661                // Add top padding so panel content isn't hidden behind the mobile header
6662                if (panelContent) {
6663                  panelContent.style.paddingTop = mobileHeader.offsetHeight + 'px';
6664                }
6665              }
6666            }
6667            // Show/hide the journey tab dropdown, view title, and logo in the mobile header
6668            const headerLogo = document.getElementById('mobile-header-logo');
6669            const headerViewTitle = document.getElementById('mobile-header-view-title');
6670            const mobileHeaderTitles = { 'market': 'Market', 'profile': 'Profile' };
6671
6672            // Reset all dynamic header elements first
6673            if (headerDropdown) headerDropdown.classList.add('hidden');
6674            if (headerViewTitle) { headerViewTitle.classList.add('hidden'); headerViewTitle.textContent = ''; }
6675            if (headerLogo) headerLogo.classList.remove('hidden');
6676
6677            if (view === 'journey') {
6678              // Show journey dropdown, hide logo
6679              if (headerDropdown) headerDropdown.classList.remove('hidden');
6680              if (headerLogo) headerLogo.classList.add('hidden');
6681              // Re-measure header height after dropdown is shown
6682              if (mobileHeader && panelContent) {
6683                panelContent.style.paddingTop = mobileHeader.offsetHeight + 'px';
6684              }
6685            } else if (mobileHeaderTitles[view]) {
6686              // Show view-specific heading, hide logo
6687              if (headerViewTitle) {
6688                headerViewTitle.textContent = mobileHeaderTitles[view];
6689                headerViewTitle.classList.remove('hidden');
6690              }
6691              if (headerLogo) headerLogo.classList.add('hidden');
6692            }
6693          }
6694          
6695          if (isMainView) {
6696            // Hide header, show close button
6697            this.panelHeader.classList.remove('visible');
6698          } else {
6699            // Show header with title
6700            this.panelHeader.classList.add('visible');
6701            const title = options.title || this.getViewTitle(view);
6702            if (this.panelHeaderTitle) {
6703              this.panelHeaderTitle.textContent = title;
6704            }
6705          }
6706          
6707          // Always show close button (requirement #1)
6708          if (this.panelCloseBtn) {
6709            if (isMobile) {
6710              this.panelCloseBtn.style.display = 'none';
6711            } else {
6712              this.panelCloseBtn.style.display = 'flex';
6713            }
6714          }
6715        },
6716        
6717        hideHeader: function() {
6718          if (this.panelHeader) {
6719            this.panelHeader.classList.remove('visible');
6720          }
6721          if (this.panelCloseBtn) {
6722            this.panelCloseBtn.style.display = 'flex';
6723          }
6724          
6725          // Show mobile header again when panel is closed
6726          const isMobile = screen.width < 768;
6727          if (isMobile) {
6728            const mobileHeader = document.querySelector('header.md\\:hidden');
6729            if (mobileHeader) {
6730              mobileHeader.style.display = 'block';
6731            }
6732          }
6733          
6734          // Remove mobile classes from panel body
6735          if (this.panelBody) {
6736            this.panelBody.classList.remove('mobile-journey-detail-body', 'mobile-destination-detail-body');
6737          }
6738        },
6739        
6740        loadViewContent: function(view, options = {}) {
6741          if (!this.panelBody) {
6742            console.error('Panel body not found');
6743            return;
6744          }
6745          
6746          const viewId = this.getViewId(view);
6747          if (!viewId) {
6748            console.error('Invalid view:', view);
6749            return;
6750          }
6751          
6752          // Check if this is a sub-view that needs dynamic loading
6753          const subViews = ['journey-create', 'journey-detail', 'journey-edit', 'journey-preview', 'market-visa', 'market-healthcare', 'market-lounge', 'market-esim', 'destination-detail'];
6754          const isSubView = subViews.includes(view);
6755          
6756          if (isSubView) {
6757            // Load sub-view content dynamically
6758            this.loadSubViewContent(view, viewId, options);
6759            return;
6760          }
6761          
6762          // Special handling for journey view: reload from server if forceRefresh is true
6763          if (view === 'journey' && options.forceRefresh) {
6764            this.reloadJourneyView(viewId);
6765            return;
6766          }
6767          
6768          // Show loader
6769          this.showLoader();
6770          
6771          // Hide all views first
6772          const allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
6773          allViews.forEach(v => {
6774            if (v && v.id) {
6775              v.style.display = 'none';
6776            }
6777          });
6778          
6779          // Find the view element
6780          const findViewElement = () => {
6781            let viewElement = document.getElementById(viewId);
6782            
6783            if (!viewElement) {
6784              console.warn('View element not found immediately, retrying...', viewId);
6785              setTimeout(() => {
6786                viewElement = document.getElementById(viewId);
6787                if (viewElement) {
6788                  showView(viewElement);
6789                } else {
6790                  console.error('View element not found after retry:', viewId);
6791                  this.hideLoader();
6792                }
6793              }, 50);
6794              return;
6795            }
6796            
6797            showView(viewElement);
6798          };
6799          
6800          const showView = (viewElement) => {
6801            if (!viewElement) {
6802              console.error('View element is null');
6803              this.hideLoader();
6804              return;
6805            }
6806            
6807            // Hide all views
6808            const allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
6809            allViews.forEach(v => {
6810              if (v && v.id && v.id !== viewId) {
6811                v.style.display = 'none';
6812              }
6813            });
6814            
6815            // Show the selected view
6816            setTimeout(() => {
6817              viewElement.style.display = 'block';
6818              this.hideLoader();
6819              
6820              // Scroll to top
6821              if (this.panelBody) {
6822                this.panelBody.scrollTop = 0;
6823              }
6824            }, 100);
6825          };
6826          
6827          // Try to find element immediately
6828          findViewElement();
6829        },
6830        
6831        reloadJourneyView: function(viewId) {
6832          // Show loader
6833          this.showLoader();
6834          
6835          // Hide all views
6836          const allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
6837          allViews.forEach(v => {
6838            if (v && v.id) {
6839              v.style.display = 'none';
6840            }
6841          });
6842          
6843          // Get the view element
6844          const viewElement = document.getElementById(viewId);
6845          if (!viewElement) {
6846            console.error('Journey view element not found:', viewId);
6847            this.hideLoader();
6848            return;
6849          }
6850          
6851          // Fetch fresh content from server
6852          const baseUrl = window.location.origin;
6853          const url = baseUrl + '/chat/panel/journey/';
6854          
6855          fetch(url, {
6856            method: 'GET',
6857            headers: {
6858              'X-Requested-With': 'XMLHttpRequest',
6859            },
6860          })
6861          .then(response => {
6862            if (!response.ok) {
6863              throw new Error(`HTTP error! status: ${response.status}`);
6864            }
6865            return response.text();
6866          })
6867          .then(html => {
6868            // Parse the HTML response
6869            try {
6870              const parser = new DOMParser();
6871              const doc = parser.parseFromString(html, 'text/html');
6872              
6873              // Extract body content
6874              const bodyContent = doc.body || doc.querySelector('body');
6875              if (bodyContent && bodyContent.innerHTML) {
6876                viewElement.innerHTML = bodyContent.innerHTML;
6877              } else {
6878                // Fallback: try to extract content between body tags
6879                const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
6880                if (bodyMatch && bodyMatch[1]) {
6881                  viewElement.innerHTML = bodyMatch[1];
6882                } else {
6883                  viewElement.innerHTML = html;
6884                }
6885              }
6886            } catch (error) {
6887              console.error('Error parsing HTML:', error);
6888              // Fallback: try to extract content between body tags
6889              const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
6890              if (bodyMatch && bodyMatch[1]) {
6891                viewElement.innerHTML = bodyMatch[1];
6892              } else {
6893                viewElement.innerHTML = html;
6894              }
6895            }
6896            
6897            // Show the view
6898            viewElement.style.display = 'block';
6899            this.hideLoader();
6900            
6901            // Scroll to top
6902            if (this.panelBody) {
6903              this.panelBody.scrollTop = 0;
6904            }
6905            
6906            // Update current view
6907            this.currentView = 'journey';
6908            this.updateHeaderVisibility('journey');
6909          })
6910          .catch(error => {
6911            console.error('Error reloading journey view:', error);
6912            this.hideLoader();
6913            // Show the existing view as fallback
6914            viewElement.style.display = 'block';
6915          });
6916        },
6917        
6918        reloadJourneyView: function(viewId) {
6919          // Show loader
6920          this.showLoader();
6921          
6922          // Hide all views
6923          const allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
6924          allViews.forEach(v => {
6925            if (v && v.id) {
6926              v.style.display = 'none';
6927            }
6928          });
6929          
6930          // Get the view element
6931          const viewElement = document.getElementById(viewId);
6932          if (!viewElement) {
6933            console.error('Journey view element not found:', viewId);
6934            this.hideLoader();
6935            return;
6936          }
6937          
6938          // Fetch fresh content from server
6939          const baseUrl = window.location.origin;
6940          const url = baseUrl + '/chat/panel/journey/';
6941          
6942          fetch(url, {
6943            method: 'GET',
6944            headers: {
6945              'X-Requested-With': 'XMLHttpRequest',
6946            },
6947          })
6948          .then(response => {
6949            if (!response.ok) {
6950              throw new Error(`HTTP error! status: ${response.status}`);
6951            }
6952            return response.text();
6953          })
6954          .then(html => {
6955            // Parse the HTML response
6956            try {
6957              const parser = new DOMParser();
6958              const doc = parser.parseFromString(html, 'text/html');
6959              
6960              // Extract body content
6961              const bodyContent = doc.body || doc.querySelector('body');
6962              if (bodyContent && bodyContent.innerHTML) {
6963                viewElement.innerHTML = bodyContent.innerHTML;
6964              } else {
6965                // Fallback: try to extract content between body tags
6966                const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
6967                if (bodyMatch && bodyMatch[1]) {
6968                  viewElement.innerHTML = bodyMatch[1];
6969                } else {
6970                  viewElement.innerHTML = html;
6971                }
6972              }
6973            } catch (error) {
6974              console.error('Error parsing HTML:', error);
6975              // Fallback: try to extract content between body tags
6976              const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
6977              if (bodyMatch && bodyMatch[1]) {
6978                viewElement.innerHTML = bodyMatch[1];
6979              } else {
6980                viewElement.innerHTML = html;
6981              }
6982            }
6983            
6984            // Show the view
6985            viewElement.style.display = 'block';
6986            this.hideLoader();
6987            
6988            // Scroll to top
6989            if (this.panelBody) {
6990              this.panelBody.scrollTop = 0;
6991            }
6992            
6993            // Update current view
6994            this.currentView = 'journey';
6995            this.updateHeaderVisibility('journey');
6996          })
6997          .catch(error => {
6998            console.error('Error reloading journey view:', error);
6999            this.hideLoader();
7000            // Show the existing view as fallback
7001            viewElement.style.display = 'block';
7002          });
7003        },
7004        
7005        loadSubViewContent: function(view, viewId, options = {}) {
7006          // Show loader
7007          this.showLoader();
7008          
7009          // Hide all views
7010          const allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
7011          allViews.forEach(v => {
7012            if (v && v.id) {
7013              v.style.display = 'none';
7014            }
7015          });
7016          
7017          // Get the view element
7018          let viewElement = document.getElementById(viewId);
7019          if (!viewElement) {
7020            console.error('Sub-view element not found:', viewId);
7021            this.hideLoader();
7022            return;
7023          }
7024          
7025          // Healthcare view: reuse existing DOM (React widget can't be reloaded)
7026          if (view === 'market-healthcare' && viewElement.innerHTML.trim()) {
7027            this.showSubView(viewElement);
7028            this.hideLoader();
7029            return;
7030          }
7031
7032          // Clear content for other sub-views to force fresh load every time
7033          if (view === 'market-visa') {
7034            delete window.initializeVisaView;
7035            delete window.loadVisaCountries;
7036            delete window.visaCountries;
7037          } else if (view === 'market-esim') {
7038            // Only delete the init function so it gets redefined by the template script.
7039            // Keep ESimService.esimCountries cached to avoid re-fetching countries.json.
7040            delete window.initializeESimView;
7041            if (typeof window.resetESimView === 'function') {
7042              try { window.resetESimView(); } catch (e) {}
7043            }
7044          } else if (view === 'market-lounge') {
7045            delete window.initializeLoungeView;
7046            if (typeof window.resetLoungeView === 'function') {
7047              try { window.resetLoungeView(); } catch (e) {}
7048            }
7049          }
7050          viewElement.innerHTML = '';
7051          
7052          // Map view to URL
7053          const baseUrl = window.location.origin;
7054          // Fall back to stored UUID for journey views when none provided in options
7055          const journeyUuid = options.journeyUuid || window._lastJourneyUuid;
7056          let journeyCreateUrl = options.citySlug ? baseUrl + '/chat/panel/journey/create/' + encodeURIComponent(options.citySlug) + '/' : baseUrl + '/chat/panel/journey/create/';
7057          if (options.suggestedStartDate || options.suggestedEndDate) {
7058            const params = new URLSearchParams();
7059            if (options.suggestedStartDate) params.set('start', options.suggestedStartDate);
7060            if (options.suggestedEndDate) params.set('end', options.suggestedEndDate);
7061            journeyCreateUrl += (journeyCreateUrl.indexOf('?') === -1 ? '?' : '&') + params.toString();
7062          }
7063          const urlMap = {
7064            'journey-create': journeyCreateUrl,
7065            'journey-detail': journeyUuid ? baseUrl + '/chat/panel/journey/' + encodeURIComponent(journeyUuid) + '/' : null,
7066            'journey-preview': journeyUuid ? baseUrl + '/chat/panel/journey/' + encodeURIComponent(journeyUuid) + '/preview/' + (window.innerWidth < 768 ? '?mobile=1' : '') : null,
7067            'journey-edit': journeyUuid ? baseUrl + '/chat/panel/journey/' + encodeURIComponent(journeyUuid) + '/edit/' : null,
7068            'market-visa': baseUrl + '/chat/panel/market/visa/',
7069            'market-healthcare': baseUrl + '/chat/panel/market/healthcare/',
7070            'market-lounge': baseUrl + '/chat/panel/market/lounge/',
7071            'market-esim': baseUrl + '/chat/panel/market/esim/',
7072            'destination-detail': options.citySlug ? baseUrl + '/chat/panel/destination/' + encodeURIComponent(options.citySlug) + '/' : null
7073          };
7074          
7075          const url = urlMap[view];
7076          if (!url) {
7077            console.error('No URL mapped for view:', view, 'Options:', options);
7078            this.hideLoader();
7079  
7079          return;
7080          }
7081          
7082          // Always use fetch for sub-views to avoid HTMX parsing issues with dynamic content
7083          // HTMX can have issues with script tags and complex HTML structures
7084          this.fetchSubViewContent(url, viewElement);
7085        },
7086        
7087        fetchSubViewContent: function(url, viewElement) {
7088          if (!viewElement || !viewElement.id) {
7089            console.error('View element is invalid');
7090            this.hideLoader();
7091            return;
7092          }
7093          
7094          const viewId = viewElement.id; // Get viewId from element
7095          
7096          fetch(url, {
7097            headers: {
7098              'X-Requested-With': 'XMLHttpRequest'
7099            },
7100            credentials: 'same-origin'
7101          })
7102          .then(response => {
7103            if (!response.ok) {
7104              throw new Error(`HTTP error! status: ${response.status}`);
7105            }
7106            // Check content type to ensure we're getting HTML
7107            const contentType = response.headers.get('content-type') || '';
7108            if (!contentType.includes('text/html') && !contentType.includes('text/plain')) {
7109              console.warn('Unexpected content type:', contentType);
7110            }
7111            return response.text();
7112          })
7113          .then(html => {
7114            // Extract content - handle both full HTML documents and partial content
7115            html = html.trim();
7116            
7117            // Check if response is an error page or empty
7118            if (!html || html.length === 0) {
7119              throw new Error('Empty response received');
7120            }
7121            
7122            // Check if response looks like an error page
7123            if (html.includes('<!DOCTYPE html>') && (html.includes('Error') || html.includes('error') || html.includes('404') || html.includes('500'))) {
7124              console.warn('Response might be an error page');
7125            }
7126            
7127            // Verify we're loading into the correct container
7128            if (!viewElement || viewElement.id !== viewId) {
7129              console.error('Container mismatch! Expected:', viewId, 'Got:', viewElement ? viewElement.id : 'null');
7130              this.hideLoader();
7131              return;
7132            }
7133            
7134            // Check if it's a full HTML document (has <html> or <body> tags)
7135            if (html.includes('<body') || html.includes('<html')) {
7136              // Full HTML document - extract body content using DOMParser
7137              try {
7138                const parser = new DOMParser();
7139                const doc = parser.parseFromString(html, 'text/html');
7140                
7141                // Check for parsing errors
7142                const parserError = doc.querySelector('parsererror');
7143                if (parserError) {
7144                  console.warn('DOMParser error, trying fallback method');
7145                  // Fallback: extract content between body tags
7146                  const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
7147                  if (bodyMatch && bodyMatch[1]) {
7148                    viewElement.innerHTML = bodyMatch[1];
7149                  } else {
7150                    // Try to find main content div or just use the HTML
7151                    viewElement.innerHTML = html;
7152                  }
7153                } else {
7154                  const bodyContent = doc.body || doc.querySelector('body');
7155                  if (bodyContent && bodyContent.innerHTML) {
7156                    viewElement.innerHTML = bodyContent.innerHTML;
7157                  } else {
7158                    // Fallback: try to extract content between body tags
7159                    const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
7160                    if (bodyMatch && bodyMatch[1]) {
7161                      viewElement.innerHTML = bodyMatch[1];
7162                    } else {
7163                      // Last resort: use the HTML as-is but log a warning
7164                      console.warn('Could not extract body content, using full HTML');
7165                      viewElement.innerHTML = html;
7166                    }
7167                  }
7168                }
7169              } catch (error) {
7170                console.error('Error parsing HTML:', error);
7171                // Fallback: try to extract content between body tags
7172                const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
7173                if (bodyMatch && bodyMatch[1]) {
7174                  viewElement.innerHTML = bodyMatch[1];
7175                } else {
7176                  // Last resort: use the HTML as-is
7177                  console.warn('Using HTML as fallback after parsing error');
7178                  viewElement.innerHTML = html;
7179                }
7180              }
7181            } else {
7182              // Partial content - use directly
7183              viewElement.innerHTML = html;
7184            }
7185            
7186            // Clean up any malformed script tags that might have HTML content
7187            // This prevents the "Unexpected token '<'" error
7188            const allScripts = viewElement.querySelectorAll('script');
7189            allScripts.forEach(script => {
7190              if (!script.src) {
7191                // For inline scripts, check if content looks like HTML
7192                const content = script.textContent || script.innerHTML || '';
7193                if (content.trim().startsWith('<') && /<[a-z][\s\S]*>/i.test(content)) {
7194                  console.warn('Removing script tag with HTML content:', content.substring(0, 50));
7195                  script.remove();
7196                }
7197              }
7198            });
7199            
7200            // Show the view first, then initialize scripts
7201            // This ensures the DOM is visible and ready before scripts execute
7202            this.showSubView(viewElement);
7203            
7204            // Wait for view to be visible before initializing scripts
7205            // showSubView sets display after 100ms, so wait a bit longer
7206            // For healthcare view, the initialization function will handle script loading
7207            var _currentView = this.currentView;
7208            setTimeout(() => {
7209              this.initializeSubViewScripts(viewElement);
7210              // Re-apply actioned service states when journey preview loads
7211              if (_currentView === 'journey-preview' && typeof window._applyActionedServicesToPreview === 'function') {
7212                window._applyActionedServicesToPreview();
7213              }
7214            }, 200);
7215          })
7216          .catch(error => {
7217            console.error('Error loading sub-view content:', error);
7218            viewElement.innerHTML = '<div class="text-center py-12"><p class="text-red-500">Error loading content. Please try again.</p></div>';
7219            this.hideLoader();
7220          });
7221        },
7222        
7223        showSubView: function(viewElement) {
7224          if (!viewElement) {
7225            console.error('View element is null');
7226            this.hideLoader();
7227            return;
7228          }
7229          
7230          // Hide all views first
7231          const allViews = this.panelBody.querySelectorAll('.nomados-panel-view');
7232          allViews.forEach(v => {
7233            if (v && v.id) {
7234              v.style.display = 'none';
7235            }
7236          });
7237          
7238          // Show the sub-view
7239          setTimeout(() => {
7240            // Double-check we're showing the right view
7241            if (viewElement && viewElement.id) {
7242              viewElement.style.display = 'block';
7243              
7244              // Scroll to top
7245              if (this.panelBody) {
7246                this.panelBody.scrollTop = 0;
7247              }
7248            }
7249            this.hideLoader();
7250          }, 100);
7251        },
7252        
7253        initializeSubViewScripts: function(viewElement) {
7254          if (!viewElement) {
7255            console.warn('initializeSubViewScripts: viewElement is null');
7256            return;
7257          }
7258          
7259          // Ensure view is visible before initializing scripts
7260          if (viewElement.style.display === 'none') {
7261            console.warn('View is hidden, showing it first:', viewElement.id);
7262            viewElement.style.display = 'block';
7263          }
7264          
7265          // List of scripts that are already loaded globally - skip re-loading them
7266          const globalScripts = [
7267            'maps.googleapis.com',
7268            'googleapis.com/maps',
7269            'google.com/maps'
7270          ];
7271          
7272          // Helper function to validate script content
7273          const isValidScriptContent = (content) => {
7274            if (!content || typeof content !== 'string') return false;
7275            const trimmed = content.trim();
7276            // Check if content looks like HTML (starts with < and contains HTML tags)
7277            if (trimmed.startsWith('<') && /<[a-z][\s\S]*>/i.test(trimmed)) {
7278              return false; // This is HTML, not JavaScript
7279            }
7280            // Check if it's empty or just whitespace
7281            if (trimmed.length === 0) return false;
7282            return true;
7283          };
7284          
7285          // Re-run any script tags in the loaded content
7286          const scripts = Array.from(viewElement.querySelectorAll('script'));
7287          const scriptPromises = [];
7288          
7289          scripts.forEach((oldScript, index) => {
7290            try {
7291              // Validate that oldScript is a valid script element
7292              if (!oldScript || !oldScript.tagName || oldScript.tagName.toLowerCase() !== 'script') {
7293                console.warn('Invalid script element found, skipping:', oldScript);
7294                oldScript.remove();
7295                return;
7296              }
7297              
7298              // Skip scripts that are already loaded globally
7299              if (oldScript.src) {
7300                const isGlobalScript = globalScripts.some(global => oldScript.src.includes(global));
7301                if (isGlobalScript) {
7302                  oldScript.remove(); // Remove from DOM but don't re-execute
7303                  return;
7304                }
7305                
7306                // Skip SafetyWing script - it's handled by initializeHealthcareView function
7307                if (oldScript.src.includes('safetywing.com')) {
7308                  oldScript.remove();
7309                  return;
7310                }
7311              }
7312              
7313              // For inline scripts, validate content before processing
7314              if (!oldScript.src) {
7315                const scriptContent = oldScript.textContent || oldScript.innerHTML || '';
7316                if (!isValidScriptContent(scriptContent)) {
7317                  console.warn('Invalid script content detected (contains HTML or is empty), skipping:', {
7318                    content: scriptContent.substring(0, 100),
7319                    length: scriptContent.length
7320                  });
7321                  oldScript.remove();
7322                  return;
7323                }
7324              }
7325              
7326              const newScript = document.createElement('script');
7327              
7328              // Copy all attributes safely
7329              try {
7330                Array.from(oldScript.attributes).forEach(attr => {
7331                  try {
7332                    // Skip src attribute for now, we'll set it separately
7333                    if (attr.name === 'src') return;
7334                    newScript.setAttribute(attr.name, attr.value);
7335                  } catch (attrError) {
7336                    console.warn('Error setting attribute:', attr.name, attrError);
7337                  }
7338                });
7339              } catch (attrError) {
7340                console.warn('Error copying attributes:', attrError);
7341              }
7342              
7343              // Handle module scripts (like SafetyWing widget)
7344              if (oldScript.type === 'module') {
7345                newScript.type = 'module';
7346              }
7347              
7348              // Copy src or text content
7349              if (oldScript.src) {
7350                // For SafetyWing module scripts, add cache-busting to force reload
7351                let scriptSrc = oldScript.src;
7352                if (scriptSrc.includes('safetywing.com') && oldScript.type === 'module') {
7353                  // Add cache-busting parameter to force fresh load
7354                  const separator = scriptSrc.includes('?') ? '&' : '?';
7355                  scriptSrc = scriptSrc + separator + 'v=' + Date.now();
7356                }
7357                newScript.src = scriptSrc;
7358                
7359                const promise = new Promise((resolve, reject) => {
7360                  newScript.onload = () => {
7361                    const delay = oldScript.type === 'module' ? 500 : 100;
7362                    setTimeout(resolve, delay);
7363                  };
7364                  newScript.onerror = (error) => {
7365                    console.warn('Script failed to load:', oldScript.src, error);
7366                    reject(error);
7367                  };
7368                });
7369                scriptPromises.push(promise);
7370                } else {
7371                  // For inline scripts, copy text content safely
7372                  try {
7373                    const scriptContent = oldScript.textContent || oldScript.innerHTML || '';
7374                    // Double-check content is valid before setting
7375                    if (isValidScriptContent(scriptContent)) {
7376                      newScript.textContent = scriptContent;
7377                      // For inline scripts, append immediately and create a promise
7378                      // Inline scripts execute synchronously when appended
7379                      try {
7380                        document.body.appendChild(newScript);
7381                        // Create a promise that resolves after execution
7382                        const inlinePromise = new Promise((resolve) => {
7383                          // Wait longer to ensure all function definitions are complete
7384                          setTimeout(() => {
7385                            resolve();
7386                          }, 300);
7387                        });
7388                        scriptPromises.push(inlinePromise);
7389                        // Mark this script as already appended so we don't append again below
7390                        newScript.dataset.appended = 'true';
7391                      } catch (appendError) {
7392                        console.error('Error appending inline script:', appendError);
7393                        oldScript.remove();
7394                        return;
7395                      }
7396                    } else {
7397                      console.warn('Skipping script with invalid content');
7398                      oldScript.remove();
7399                      return;
7400                    }
7401                  } catch (textError) {
7402                    console.warn('Error copying script text content:', textError);
7403                    oldScript.remove();
7404                    return;
7405                  }
7406                }
7407              
7408              // Validate newScript before appending
7409              if (!newScript) {
7410                console.error('Invalid script element created, skipping');
7411                oldScript.remove();
7412                return;
7413              }
7414              
7415              // Ensure it's a valid Node
7416              if (!(newScript instanceof HTMLScriptElement)) {
7417                console.error('Script element is not a valid HTMLScriptElement:', newScript);
7418                oldScript.remove();
7419                return;
7420              }
7421              
7422              // Validate that document.body exists
7423              if (!document.body) {
7424                console.error('document.body does not exist, cannot append script');
7425                oldScript.remove();
7426                return;
7427              }
7428              
7429              // Append to document body to ensure proper execution context and global scope
7430              // Skip if this is an inline script (already appended above)
7431              if (!newScript.dataset || newScript.dataset.appended !== 'true') {
7432                try {
7433                  // Double-check the element is valid before appending
7434                  if (newScript.nodeType === Node.ELEMENT_NODE && newScript.tagName === 'SCRIPT') {
7435                    document.body.appendChild(newScript);
7436                  } else {
7437                    console.error('Script element is not a valid SCRIPT element:', newScript.nodeType, newScript.tagName);
7438                    oldScript.remove();
7439                    return;
7440                  }
7441                } catch (appendError) {
7442                  console.error('Error appending script:', appendError, {
7443                    script: newScript,
7444                    src: newScript.src,
7445                    type: newScript.type,
7446                    nodeType: newScript.nodeType,
7447                    tagName: newScript.tagName,
7448                    hasContent: !!(newScript.textContent || newScript.src)
7449                  });
7450                  oldScript.remove();
7451                  return;
7452                }
7453              }
7454              
7455              // Remove old script from viewElement
7456              try {
7457                if (oldScript.parentNode) {
7458                  oldScript.remove();
7459                }
7460              } catch (removeError) {
7461                console.warn('Error removing old script:', removeError);
7462              }
7463            } catch (error) {
7464              console.error('Error processing script element:', error, oldScript);
7465              // Remove the problematic script
7466              try {
7467                if (oldScript && oldScript.parentNode) {
7468                  oldScript.remove();
7469                }
7470              } catch (e) {
7471                // Ignore removal errors
7472              }
7473              // Continue with next script
7474            }
7475          });
7476          
7477          // Wait for all scripts to load and execute, then initialize
7478          if (scriptPromises.length > 0) {
7479            Promise.all(scriptPromises).then(() => {
7480              // Give scripts time to define functions - wait longer for dynamic content
7481              setTimeout(() => {
7482                this.initializeViewSpecificFeatures(viewElement);
7483              }, 400);
7484            }).catch((error) => {
7485              console.warn('Base: Some scripts failed, continuing:', error);
7486              setTimeout(() => {
7487                this.initializeViewSpecificFeatures(viewElement);
7488              }, 600);
7489            });
7490          } else {
7491            // For inline scripts, wait longer for execution
7492            setTimeout(() => {
7493              this.initializeViewSpecificFeatures(viewElement);
7494            }, 500);
7495          }
7496        },
7497        
7498        initializeViewSpecificFeatures: function(viewElement) {
7499          if (!viewElement) return;
7500          
7501          const viewId = viewElement.id;
7502          
7503          // Initialize Visa view
7504          if (viewId === 'nomados-panel-market-visa') {
7505            // Fix close button
7506            const closeButtons = viewElement.querySelectorAll('[onclick*="closeVisaModal"]');
7507            closeButtons.forEach(btn => {
7508              btn.setAttribute('onclick', 'nomadosPanelGoBack()');
7509            });
7510            
7511            // Wait for scripts to execute, then initialize
7512            const initVisa = async () => {
7513              // Wait for function to be available with longer timeout
7514              let retries = 0;
7515              const maxRetries = 25;
7516              
7517              while (typeof window.initializeVisaView !== 'function' && retries < maxRetries) {
7518                await new Promise(resolve => setTimeout(resolve, 100));
7519                retries++;
7520              }
7521              
7522              if (typeof window.initializeVisaView === 'function') {
7523                try {
7524                  await window.initializeVisaView();
7525                } catch (err) {
7526                  console.error('Base: Error initializing visa view:', err);
7527                }
7528              } else {
7529                console.error('Base: initializeVisaView not found after', maxRetries, 'retries');
7530              }
7531            };
7532            
7533            // Start initialization after scripts have had time to execute
7534            setTimeout(initVisa, 300);
7535          }
7536          
7537          // Initialize Healthcare view (SafetyWing widget)
7538          if (viewId === 'nomados-panel-market-healthcare') {
7539            // Use the initialization function from market_healthcare.html
7540            setTimeout(() => {
7541              if (typeof window.initializeHealthcareView === 'function') {
7542                try {
7543                  window.initializeHealthcareView();
7544                } catch (err) {
7545                  console.error('Error initializing healthcare view:', err);
7546                }
7547              } else {
7548                console.warn('initializeHealthcareView function not found');
7549              }
7550            }, 200);
7551          }
7552          
7553          // Initialize Lounge view
7554          if (viewId === 'nomados-panel-market-lounge') {
7555            // Simple initialization - use window function if available
7556            setTimeout(() => {
7557              if (typeof window.initializeLoungeView === 'function') {
7558                try {
7559                  window.initializeLoungeView();
7560                } catch (err) {
7561                  console.error('Error initializing lounge view:', err);
7562                }
7563              }
7564              
7565              // Initialize CollectJS if available
7566              setTimeout(() => {
7567                if (typeof window.CollectJS !== 'undefined') {
7568                  const collectScript = viewElement.querySelector('script[data-tokenization-key]');
7569                  if (collectScript) {
7570                    const tokenizationKey = collectScript.getAttribute('data-tokenization-key');
7571                    if (tokenizationKey && CollectJS.configure) {
7572                      CollectJS.configure({ tokenizationKey: tokenizationKey });
7573                    }
7574                  }
7575                }
7576              }, 300);
7577            }, 300);
7578          }
7579
7580          
7581          // Initialize eSIM view
7582          if (viewId === 'nomados-panel-market-esim') {
7583            // Wait for scripts to execute, then initialize
7584            const initESim = async () => {
7585              // Wait for function to be available with longer timeout
7586              let retries = 0;
7587              const maxRetries = 25;
7588              
7589              while (typeof window.initializeESimView !== 'function' && retries < maxRetries) {
7590                await new Promise(resolve => setTimeout(resolve, 100));
7591                retries++;
7592              }
7593              
7594              if (typeof window.initializeESimView === 'function') {
7595                try {
7596                  await window.initializeESimView();
7597                } catch (err) {
7598                  console.error('Base: Error initializing eSIM view:', err);
7599                }
7600              } else {
7601                console.error('Base: initializeESimView not found after', maxRetries, 'retries');
7602              }
7603            };
7604            
7605            // Start initialization after scripts have had time to execute
7606            setTimeout(initESim, 300);
7607          }
7608        },
7609        
7610        showLoader: function(message) {
7611          if (!this.panelBody) return;
7612          if (this.panelElement) {
7613            this.panelElement.classList.add('open');
7614            this.isOpen = true;
7615            if (typeof this.updateSidebarButtons === 'function') this.updateSidebarButtons();
7616          }
7617          var text = message || 'Loading...';
7618          var loader = document.getElementById('nomados-panel-loader');
7619          if (!loader) {
7620            loader = document.createElement('div');
7621            loader.id = 'nomados-panel-loader';
7622            loader.className = 'nomados-panel-loader';
7623            loader.innerHTML = `
7624              <div class="nomados-panel-loader-spinner"></div>
7625              <div class="nomados-panel-loader-text">${text}</div>
7626            `;
7627            this.panelBody.appendChild(loader);
7628          } else {
7629            var textEl = loader.querySelector('.nomados-panel-loader-text');
7630            if (textEl) textEl.textContent = text;
7631          }
7632          loader.style.display = 'flex';
7633        },
7634        
7635        hideLoader: function() {
7636          const loader = document.getElementById('nomados-panel-loader');
7637          if (loader) {
7638            loader.style.display = 'none';
7639          }
7640        },
7641        
7642        updateSidebarButtons: function() {
7643          const buttons = document.querySelectorAll('.sidebar-btn');
7644          
7645          // Map sub-views to their main views (requirement #3)
7646          const subViewToMainView = {
7647            'journey-detail': 'journey',
7648            'journey-preview': 'journey',
7649            'journey-create': 'journey',
7650            'journey-edit': 'journey', // Edit view should keep journey icon active
7651            'market-visa': 'market',
7652            'market-healthcare': 'market',
7653            'market-lounge': 'market',
7654            'market-esim': 'market',
7655            'destination-detail': 'miles' // Destination detail should show miles as active
7656          };
7657          
7658          // Get the effective view (main view if current view is a sub-view)
7659          const effectiveView = subViewToMainView[this.currentView] || this.currentView;
7660          
7661          buttons.forEach(btn => {
7662            const view = btn.getAttribute('data-panel-view');
7663            // Check if button matches effective view (requirement #3)
7664            // Special case: destination-detail should always show miles as active
7665            const isActive = (view === effectiveView && this.isOpen) || (view === 'miles' && (!this.isOpen || this.currentView === 'destination-detail'));
7666            
7667            if (isActive) {
7668              btn.classList.add('active');
7669              
7670              // Switch to filled icon
7671              const filledIcon = btn.querySelector('.miles-icon-filled, .launch-icon-filled, .market-icon-filled, .profile-icon-filled');
7672              const blankIcon = btn.querySelector('.miles-icon-blank, .launch-icon-blank, .market-icon-blank, .profile-icon-blank');
7673              if (filledIcon) filledIcon.classList.remove('hidden');
7674              if (blankIcon) blankIcon.classList.add('hidden');
7675              
7676              // Update text to font-medium
7677              const textSpan = btn.querySelector('.sidebar-label');
7678              if (textSpan) {
7679                textSpan.classList.remove('font-normal');
7680                textSpan.classList.add('font-bold');
7681              }
7682            } else {
7683              btn.classList.remove('active');
7684              
7685              // Switch to blank icon
7686              const filledIcon = btn.querySelector('.miles-icon-filled, .launch-icon-filled, .market-icon-filled, .profile-icon-filled');
7687              const blankIcon = btn.querySelector('.miles-icon-blank, .launch-icon-blank, .market-icon-blank, .profile-icon-blank');
7688              if (filledIcon) filledIcon.classList.add('hidden');
7689              if (blankIcon) blankIcon.classList.remove('hidden');
7690              
7691              // Update text to font-normal
7692              const textSpan = btn.querySelector('.sidebar-label');
7693              if (textSpan) {
7694                textSpan.classList.remove('font-bold');
7695                textSpan.classList.add('font-normal');
7696              }
7697            }
7698          });
7699        },
7700        
7701        switchView: function(view) {
7702          if (this.isOpen && this.currentView === view) {
7703            // Already showing this view, do nothing
7704            return;
7705          }
7706          
7707          if (this.isOpen) {
7708            // Switch content without closing
7709            this.currentView = view;
7710            this.loadViewContent(view);
7711            this.updateHeaderVisibility(view);
7712            this.updateSidebarButtons();
7713          } else {
7714            // Open panel with new view
7715            this.open(view);
7716          }
7717        }
7718      };
7719      
7720      // Global functions for easy access
7721      window.openNomadosPanel = function(view, options = {}) {
7722        NomadosPanel.open(view, options);
7723      };
7724      
7725      window.closeNomadosPanel = function() {
7726        // If a service was opened from journey-preview, go back to preview instead of closing
7727        if (NomadosPanel._returnToPreview) {
7728          NomadosPanel.goBack();
7729          return;
7730        }
7731        NomadosPanel.close();
7732      };
7733      
7734      window.switchNomadosPanelView = function(view) {
7735        NomadosPanel.switchView(view);
7736      };
7737      
7738      // Back button function
7739      window.nomadosPanelGoBack = function() {
7740        NomadosPanel.goBack();
7741      };
7742      
7743      // Sub-view functions
7744      window.openJourneyCreate = function() {
7745        // Block if journey limit reached (non-Pro users)
7746        if (window._maxJourneys !== null && window._journeyCount >= window._maxJourneys) {
7747          if (typeof showNotification === 'function') {
7748            showNotification("You've reached your journey limit. Upgrade to Pro for unlimited journeys!");
7749          }
7750          if (typeof openGoProModal === 'function') {
7751            openGoProModal();
7752          }
7753          return;
7754        }
7755        NomadosPanel.open('journey-create', {
7756          isSubView: true,
7757          previousTitle: 'My Journeys'
7758        });
7759      };
7760      
7761      window.openJourneyDetail = function(journeyUuid) {
7762        NomadosPanel.open('journey-detail', {
7763          isSubView: true,
7764          journeyUuid: journeyUuid,
7765          previousTitle: 'My Journeys'
7766        });
7767      };
7768      
7769      window.openJourneyEdit = function(journeyUuid) {
7770        NomadosPanel.open('journey-edit', {
7771          isSubView: true,
7772          journeyUuid: journeyUuid,
7773          previousTitle: 'Journey Details'
7774        });
7775      };
7776      
7777      window.openMarketVisa = function() {
7778        NomadosPanel.open('market-visa', {
7779          isSubView: true,
7780          previousTitle: 'Marketplace'
7781        });
7782      };
7783      
7784      window.openMarketHealthcare = function() {
7785        NomadosPanel.open('market-healthcare', {
7786          isSubView: true,
7787          previousTitle: 'Marketplace'
7788        });
7789      };
7790      
7791      window.openMarketLounge = function() {
7792        NomadosPanel.open('market-lounge', {
7793          isSubView: true,
7794          previousTitle: 'Marketplace'
7795        });
7796      };
7797
7798      window.openMarketESim = function() {
7799        NomadosPanel.open('market-esim', {
7800          isSubView: true,
7801          previousTitle: 'Marketplace'
7802        });
7803      };
7804      
7805      window.openDestinationDetail = function(citySlug) {
7806        NomadosPanel.open('destination-detail', {
7807          isSubView: true,
7808          citySlug: citySlug,
7809          previousTitle: 'Destinations'
7810        });
7811      };
7812      
7813      // Visual calendar for Plan my trip date selection (start or end)
7814      function renderPlanTripCalendar(widgetEl, onSelectCallback) {
7815        if (!widgetEl) return;
7816        const minDateStr = widgetEl.dataset.minDate || new Date().toISOString().slice(0, 10);
7817        const minDate = new Date(minDateStr);
7818        let viewYear = parseInt(widgetEl.dataset.viewYear || minDate.getFullYear(), 10);
7819        let viewMonth = parseInt(widgetEl.dataset.viewMonth || minDate.getMonth(), 10);
7820        let selectedDateStr = widgetEl.querySelector('.plan-trip-selected-date') ? widgetEl.querySelector('.plan-trip-selected-date').value : '';
7821
7822        const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
7823        const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
7824
7825        function render() {
7826          widgetEl.dataset.viewYear = viewYear;
7827          widgetEl.dataset.viewMonth = viewMonth;
7828          const monthLabel = widgetEl.querySelector('.plan-trip-calendar-month');
7829          if (monthLabel) monthLabel.textContent = monthNames[viewMonth] + ' ' + viewYear;
7830
7831          const first = new Date(viewYear, viewMonth, 1);
7832          const last = new Date(viewYear, viewMonth + 1, 0);
7833          const startPad = first.getDay();
7834          const daysInMonth = last.getDate();
7835
7836          const weekdaysEl = widgetEl.querySelector('.plan-trip-calendar-weekdays');
7837          if (weekdaysEl) {
7838            weekdaysEl.innerHTML = weekdays.map(function(w) { return '<span class="font-medium">' + w + '</span>'; }).join('');
7839          }
7840
7841          const daysEl = widgetEl.querySelector('.plan-trip-calendar-days');
7842          if (!daysEl) return;
7843          var html = '';
7844          for (var i = 0; i < startPad; i++) html += '<span class="py-1.5 text-[#ccc]"> </span>';
7845          for (var d = 1; d <= daysInMonth; d++) {
7846            var dateStr = viewYear + '-' + String(viewMonth + 1).padStart(2, '0') + '-' + String(d).padStart(2, '0');
7847            var cellDate = new Date(viewYear, viewMonth, d);
7848            var isPast = cellDate < minDate;
7849            var isSelected = dateStr === selectedDateStr;
7850            var cls = 'py-1.5 rounded-lg cursor-pointer ';
7851            if (isPast) cls += 'text-[#ccc] cursor-not-allowed';
7852            else if (isSelected) cls += 'bg-[#3B3B3C] text-white font-medium';
7853            else cls += 'text-dark hover:bg-[#F3F3F3]';
7854            html += '<button type="button" class="plan-trip-day ' + cls + '" data-date="' + dateStr + '" ' + (isPast ? 'disabled' : '') + '>' + d + '</button>';
7855          }
7856          daysEl.innerHTML = html;
7857
7858          daysEl.querySelectorAll('.plan-trip-day:not([disabled])').forEach(function(btn) {
7859            btn.addEventListener('click', function() {
7860              selectedDateStr = btn.dataset.date;
7861              var hidden = widgetEl.querySelector('.plan-trip-selected-date');
7862              if (hidden) hidden.value = selectedDateStr;
7863              if (typeof onSelectCallback === 'function') onSelectCallback(selectedDateStr);
7864              render();
7865            });
7866          });
7867
7868          var prevBtn = widgetEl.querySelector('.plan-trip-calendar-prev');
7869          var nextBtn = widgetEl.querySelector('.plan-trip-calendar-next');
7870          if (prevBtn) {
7871            prevBtn.onclick = function() {
7872              viewMonth--;
7873              if (viewMonth < 0) { viewMonth = 11; viewYear--; }
7874              render();
7875            };
7876          }
7877          if (nextBtn) {
7878            nextBtn.onclick = function() {
7879              viewMonth++;
7880              if (viewMonth > 11) { viewMonth = 0; viewYear++; }
7881              render();
7882            };
7883          }
7884        }
7885
7886        render();
7887      }
7888
7889      // Create journey from chat and open journey-preview panel
7890      window.createJourneyFromPlanTrip = function(citySlug, cityName, startDate, endDate, createBtnEl, onSuccessCallback) {
7891        // Block if journey limit reached (non-Pro users)
7892        if (window._maxJourneys !== null && window._journeyCount >= window._maxJourneys) {
7893          if (typeof showNotification === 'function') {
7894            showNotification("You've reached your journey limit. Upgrade to Pro for unlimited journeys!");
7895          }
7896          if (typeof openGoProModal === 'function') {
7897            openGoProModal();
7898          }
7899          return;
7900        }
7901
7902        function getCookie(name) {
7903          var v = null;
7904          if (document.cookie && document.cookie !== '') {
7905            document.cookie.split(';').forEach(function(c) {
7906              c = c.trim();
7907              if (c.substring(0, name.length + 1) === name + '=') v = decodeURIComponent(c.substring(name.length + 1));
7908            });
7909          }
7910          return v;
7911        }
7912        var name = 'My ' + (cityName || 'Trip') + ' Adventure';
7913        var notes = 'Description for my ' + (cityName || 'trip') + ' adventure';
7914        var formData = new FormData();
7915        formData.append('name', name);
7916        formData.append('notes', notes);
7917        formData.append('city_slug', citySlug);
7918        formData.append('start_date', startDate);
7919        formData.append('end_date', endDate);
7920        formData.append('csrfmiddlewaretoken', getCookie('csrftoken'));
7921        if (createBtnEl) { createBtnEl.disabled = true; createBtnEl.style.opacity = '0.6'; }
7922
7923        // Open panel with loader immediately — hide all views so nothing flashes
7924        // On mobile: skip visual open so the chat stays visible for the essentials wizard
7925        var _isMobileCreation = window.innerWidth < 768;
7926        if (typeof NomadosPanel !== 'undefined') {
7927          NomadosPanel.currentView = 'journey-preview';
7928          // Hide every panel view so the journey list doesn't flash
7929          var allPanelViews = NomadosPanel.panelBody ? NomadosPanel.panelBody.querySelectorAll('.nomados-panel-view') : [];
7930          allPanelViews.forEach(function(v) { if (v && v.id) v.style.display = 'none'; });
7931          if (!_isMobileCreation) {
7932            NomadosPanel.isOpen = true;
7933            if (NomadosPanel.panelElement) NomadosPanel.panelElement.classList.add('open');
7934            NomadosPanel.updateSidebarButtons();
7935            NomadosPanel.updateHeaderVisibility('journey-preview');
7936            NomadosPanel.showLoader('Creating your journey...');
7937          }
7938        }
7939
7940        fetch('/journeys/create/', {
7941          method: 'POST',
7942          headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken') },
7943          body: formData
7944        })
7945        .then(function(r) { return r.json(); })
7946        .then(function(data) {
7947          if (data && data.success && data.journey_uuid) {
7948            window._journeyCount++;
7949            // Store journey UUID so "Go to Journey" can reopen after close
7950            window._lastJourneyUuid = data.journey_uuid;
7951            // Directly load preview content into the already-open panel (no full open() call)
7952            var previewUrl = window.location.origin + '/chat/panel/journey/' + encodeURIComponent(data.journey_uuid) + '/preview/' + (_isMobileCreation ? '?mobile=1' : '');
7953            var viewElement = document.getElementById('nomados-panel-journey-preview');
7954            if (viewElement) {
7955              viewElement.innerHTML = '';
7956              NomadosPanel.fetchSubViewContent(previewUrl, viewElement);
7957            }
7958            // Silently refresh journey list HTML in background (don't show it or change currentView)
7959            var journeyViewId = NomadosPanel.getViewId('journey');
7960            var journeyEl = journeyViewId ? document.getElementById(journeyViewId) : null;
7961            if (journeyEl) {
7962              fetch(window.location.origin + '/chat/panel/journey/', {
7963                headers: { 'X-Requested-With': 'XMLHttpRequest' },
7964                credentials: 'same-origin'
7965              })
7966              .then(function(r) { return r.text(); })
7967              .then(function(html) {
7968                try {
7969                  var parser = new DOMParser();
7970                  var doc = parser.parseFromString(html, 'text/html');
7971                  var body = doc.body || doc.querySelector('body');
7972                  if (body && body.innerHTML) journeyEl.innerHTML = body.innerHTML;
7973                } catch(e) { /* silent */ }
7974              })
7975              .catch(function() { /* silent */ });
7976            }
7977            if (typeof onSuccessCallback === 'function') onSuccessCallback(cityName);
7978          } else {
7979            if (typeof NomadosPanel !== 'undefined') NomadosPanel.close();
7980            if (createBtnEl) { createBtnEl.disabled = false; createBtnEl.style.opacity = '1'; }
7981            if (data && data.limit_reached && typeof openGoProModal === 'function') {
7982              openGoProModal();
7983            }
7984            showNotification(data && data.error ? data.error : 'Could not create journey.');
7985          }
7986        })
7987        .catch(function(err) {
7988          console.error(err);
7989          if (typeof NomadosPanel !== 'undefined') NomadosPanel.close();
7990          if (createBtnEl) { createBtnEl.disabled = false; createBtnEl.style.opacity = '1'; }
7991          showNotification('Could not create journey. Please try again.');
7992        });
7993      };
7994
7995      // Append "I've created your [City] journey! 🎉" and "Let's do it!" to chat after journey creation
7996      // Track whether visa is required for this journey
7997      var _visaRequired = false;
7998
7999      // Called by the journey preview template once visa data is fetched
8000      window._onVisaDataReady = function(visaRequired, visaDays) {
8001        _visaRequired = visaRequired;
8002        var visaLineEl = document.getElementById('essentials-visa-line');
8003        if (visaLineEl) {
8004          if (visaRequired) {
8005            visaLineEl.innerHTML = '⚠️ Visa: <strong>Required</strong> — set this up in your essentials';
8006            visaLineEl.className = 'text-dark mt-2';
8007          } else {
8008            var days = visaDays || 90;
8009            visaLineEl.innerHTML = '✅ Visa: <strong>' + days + ' days visa-free</strong> — you\'re all set!';
8010            visaLineEl.className = 'text-dark mt-2';
8011          }
8012        }
8013        // Enable "Let's do it!" now that visa data is ready — but not if already clicked
8014        var btn = document.getElementById('essentials-start-btn');
8015        if (btn && btn.disabled && !btn.getAttribute('data-clicked')) {
8016          btn.disabled = false;
8017          btn.style.opacity = '1';
8018          btn.style.cursor = 'pointer';
8019          btn.classList.add('hover:border-[#3B3B3C]');
8020          var label = btn.querySelector('.btn-label');
8021          if (label) label.textContent = 'Let\'s do it!';
8022          var gi = btn.querySelector('.btn-icon-gif');
8023          var si = btn.querySelector('.btn-icon-svg');
8024          if (gi) gi.style.display = 'block';
8025          if (si) si.style.display = 'none';
8026        }
8027      };
8028
8029      // ── Mobile Services Tracker Bar ──────────────────────────────────────────
8030      var _trackerIconMap = {
8031        'visa': 'tracker-icon-visa',
8032        'health-insurance': 'tracker-icon-health',
8033        'internet': 'tracker-icon-internet',
8034        'airport-lounge': 'tracker-icon-lounge'
8035      };
8036      window._showMobileServicesTracker = function() {
8037        var tracker = document.getElementById('mobile-services-tracker');
8038        if (tracker && window.innerWidth < 768) tracker.style.display = '';
8039      };
8040
8041      window._hideMobileServicesTracker = function() {
8042        var tracker = document.getElementById('mobile-services-tracker');
8043        if (tracker) tracker.style.display = 'none';
8044      };
8045
8046      window._updateMobileTracker = function(step, state) {
8047        var iconId = _trackerIconMap[step];
8048        if (!iconId) return;
8049        var iconEl = document.getElementById(iconId);
8050        if (!iconEl) return;
8051        var wrap = iconEl.closest('.tracker-icon-wrap');
8052        if (!wrap) return;
8053        wrap.classList.remove('tracker-active', 'tracker-done');
8054        if (state === 'active') {
8055          wrap.classList.add('tracker-active');
8056        } else if (state === 'done') {
8057          wrap.classList.add('tracker-done');
8058        }
8059      };
8060
8061      window.appendPlanMyTripSuccessMessage = function(cityName) {
8062        var chatMessages = document.getElementById('chatMessages');
8063        if (!chatMessages) return;
8064        // Store city name and reset counter for essentials flow
8065        _essentialCityName = cityName || '';
8066        _essentialsSetupCount = 0;
8067        _visaRequired = false;
8068        var escapedName = (cityName || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
8069        var html = '<div class="message bot"><div class="message-content w-full">' +
8070          '<span class="text-dark">I\'ve created your ' + escapedName + ' journey! 🎉</span>' +
8071          '<p id="essentials-visa-line" class="text-dark mt-2">⏳ Checking visa requirements...</p>' +
8072          '<p class="text-dark mt-1">Now let\'s get the essentials sorted.</p>' +
8073          '<button type="button" id="essentials-start-btn" disabled class="w-full flex justify-between items-center mt-4 text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] opacity-50 cursor-not-allowed transition-all duration-200" ' +
8074          'onclick="this.setAttribute(\'data-clicked\',\'1\');this.disabled=true;this.style.opacity=\'0.6\';this.style.cursor=\'default\';var gi=this.querySelector(\'.btn-icon-gif\'),si=this.querySelector(\'.btn-icon-svg\');if(gi)gi.style.display=\'none\';if(si)si.style.display=\'block\';startEssentialsChecklist();">' +
8075          '<span class="btn-label">Creating your journey...</span>' +
8076          '<img src="/static/icons/chevron_right.gif" alt="" class="w-5 h-5 btn-icon-gif" style="display:none;" />' +
8077          '<img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5 btn-icon-svg" />' +
8078          '</button></div></div>';
8079        chatMessages.insertAdjacentHTML('beforeend', html);
8080        chatMessages.scrollTop = chatMessages.scrollHeight;
8081        // Safety: enable after 10s regardless (in case visa API is slow) — but not if already clicked
8082        setTimeout(function() { var b = document.getElementById('essentials-start-btn'); if (b && b.disabled && !b.getAttribute('data-clicked')) { b.disabled = false; b.style.opacity = '1'; b.style.cursor = 'pointer'; b.querySelector('.btn-label').textContent = 'Let\'s do it!'; var gi=b.querySelector('.btn-icon-gif'),si=b.querySelector('.btn-icon-svg');if(gi)gi.style.display='block';if(si)si.style.display='none'; } }, 10000);
8083      };
8084
8085      // ── Essentials Checklist Flow ────────────────────────────────────────────
8086      // Guided chat flow: [Visa if required] → Health Insurance → Internet → Airport Lounge
8087      var _essentialSteps = []; // built dynamically based on visa status
8088      var _essentialRowMap = {
8089        'visa': 'preview-checklist-visa',
8090        'health-insurance': 'preview-checklist-health',
8091        'internet': 'preview-checklist-internet',
8092        'airport-lounge': 'preview-checklist-lounge'
8093      };
8094      // Pending next step — held until user closes/leaves the service panel
8095      var _pendingEssentialStep = null;
8096      // Track how many services the user set up (vs skipped)
8097      var _essentialsSetupCount = 0;
8098      // City name for the journey (set when essentials flow starts)
8099      var _essentialCityName = '';
8100
8101      function _echoUserChoice(text) {
8102        var chatMessages = document.getElementById('chatMessages');
8103        if (!chatMessages) return;
8104        var escaped = (text || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
8105        chatMessages.insertAdjacentHTML('beforeend', '<div class="message user"><div class="message-content">' + escaped + '</div></div>');
8106        chatMessages.scrollTop = chatMessages.scrollHeight;
8107      }
8108
8109      // Map essential step keys to their preview icon element IDs
8110      var _essentialIconMap = {
8111        'visa': 'preview-icon-visa',
8112        'health-insurance': 'preview-icon-health',
8113        'internet': 'preview-icon-internet',
8114        'airport-lounge': 'preview-icon-lounge'
8115      };
8116
8117      // Track which services have been actioned (persists across panel open/close)
8118      window._actionedServices = [];
8119
8120      function _highlightPreviewRow(step) {
8121        // No border changes — keep borders as-is per design
8122      }
8123
8124      // Update service row icon and button after user acts (setup or remind-me-later)
8125      function _markServiceActioned(step) {
8126        // Store for re-applying when journey preview reloads
8127        if (window._actionedServices.indexOf(step) === -1) {
8128          window._actionedServices.push(step);
8129        }
8130        var reminderSrc = '/static/icons/service_reminder.png';
8131        var iconId = _essentialIconMap[step];
8132        if (iconId) {
8133          var iconEl = document.getElementById(iconId);
8134          if (iconEl) iconEl.src = reminderSrc;
8135        }
8136        var rowId = _essentialRowMap[step];
8137        if (rowId) {
8138          var row = document.getElementById(rowId);
8139          if (row) {
8140            var btn = row.querySelector('.preview-service-btn');
8141            if (btn) {
8142              btn.classList.remove('bg-[#3B3B3C]', 'hover:bg-[#2a2a2b]', 'text-white');
8143              btn.classList.add('bg-[#FF735D]', 'hover:bg-[#e5654f]', 'text-white');
8144            }
8145          }
8146        }
8147      }
8148
8149      // Re-apply actioned service states when journey preview is (re)loaded
8150      window._applyActionedServicesToPreview = function() {
8151        if (!window._actionedServices || !window._actionedServices.length) return;
8152        var reminderSrc = '/static/icons/service_reminder.png';
8153        window._actionedServices.forEach(function(step) {
8154          var iconId = _essentialIconMap[step];
8155          if (iconId) {
8156            var iconEl = document.getElementById(iconId);
8157            if (iconEl) iconEl.src = reminderSrc;
8158          }
8159          var rowId = _essentialRowMap[step];
8160          if (rowId) {
8161            var row = document.getElementById(rowId);
8162            if (row) {
8163              var btn = row.querySelector('.preview-service-btn');
8164              if (btn) {
8165                btn.classList.remove('bg-[#3B3B3C]', 'hover:bg-[#2a2a2b]');
8166                btn.classList.add('bg-[#FF735D]', 'hover:bg-[#e5654f]');
8167              }
8168            }
8169          }
8170        });
8171      };
8172
8173      function _getNextStep(current) {
8174        var idx = _essentialSteps.indexOf(current);
8175        return (idx >= 0 && idx < _essentialSteps.length - 1) ? _essentialSteps[idx + 1] : null;
8176      }
8177
8178      // Show the journey-ready summary after all essentials are done
8179      function _showEssentialsComplete() {
8180
8181        var chatMessages = document.getElementById('chatMessages');
8182        if (!chatMessages) return;
8183
8184        // Count configured essentials:
8185        // If visa is NOT required, it's auto-configured (+1)
8186        // If visa IS required, it only counts if user clicked "Set up" (included in _essentialsSetupCount)
8187        var configured = _essentialsSetupCount + (_visaRequired ? 0 : 1);
8188        var escapedCity = (_essentialCityName || 'your').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
8189
8190        var _isMobileComplete = window.innerWidth < 768;
8191        var journeyAction = _isMobileComplete ?
8192          '<button type="button" class="w-full flex justify-between items-center mt-3 text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8193          'onclick="if(typeof openNomadosPanel===\'function\') openNomadosPanel(\'journey-preview\');">Go to Journey' +
8194          '<img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5" /></button>' :
8195          '<p class="text-dark mt-1">You can see vital information about your journey on the panel to the right →</p>';
8196
8197        var html = '<div class="message bot"><div class="message-content w-full">' +
8198          '<span class="text-dark">Your ' + escapedCity + ' journey is ready! 🎉</span>' +
8199          '<p class="text-dark mt-1"><strong>' + configured + '/4 essentials configured.</strong></p>' +
8200          journeyAction +
8201          '</div></div>';
8202        chatMessages.insertAdjacentHTML('beforeend', html);
8203        chatMessages.scrollTop = chatMessages.scrollHeight;
8204
8205        // Hide mobile tracker
8206        if (typeof _hideMobileServicesTracker === 'function') _hideMobileServicesTracker();
8207
8208        // Reveal widgets panel with smooth animation (desktop)
8209        if (!_isMobileComplete) {
8210          setTimeout(function() {
8211            if (typeof window._revealWidgetsPanel === 'function') {
8212              window._revealWidgetsPanel();
8213            }
8214          }, 400);
8215        }
8216      }
8217
8218      // Show the pending essential step (called when user leaves a service panel)
8219      window._flushPendingEssentialStep = function() {
8220        if (!_pendingEssentialStep) return;
8221        var step = _pendingEssentialStep;
8222        _pendingEssentialStep = null;
8223        if (step === '__done__') {
8224          // Last service step completed — show summary
8225          setTimeout(_showEssentialsComplete, 600);
8226        } else {
8227          setTimeout(function() { showEssentialStep(step); }, 600);
8228        }
8229      };
8230
8231      window.startEssentialsChecklist = function() {
8232        _echoUserChoice("Let's do it!");
8233        // Build steps dynamically: include visa if required
8234        _essentialSteps = [];
8235        if (_visaRequired) {
8236          _essentialSteps.push('visa');
8237        }
8238        _essentialSteps.push('health-insurance', 'internet', 'airport-lounge');
8239        // Show mobile services tracker and mark visa done if not required
8240        if (typeof _showMobileServicesTracker === 'function') _showMobileServicesTracker();
8241        if (!_visaRequired && typeof _updateMobileTracker === 'function') _updateMobileTracker('visa', 'done');
8242        setTimeout(function() { showEssentialStep(_essentialSteps[0]); }, 500);
8243      };
8244
8245      window.showEssentialStep = function(step) {
8246        var chatMessages = document.getElementById('chatMessages');
8247        if (!chatMessages) return;
8248        // Highlight current step in mobile tracker
8249        if (typeof _updateMobileTracker === 'function') _updateMobileTracker(step, 'active');
8250        var chevronGif = '<img src="/static/icons/chevron_right.gif" alt="" class="w-5 h-5 flex-shrink-0 btn-icon-gif" /><img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5 flex-shrink-0 btn-icon-svg" style="display:none;" />';
8251        var chevronSvg = '<img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5 flex-shrink-0" />';
8252        var html = '';
8253
8254        if (step === 'visa') {
8255          _highlightPreviewRow('visa');
8256          html = '<div class="message bot"><div class="message-content w-full">' +
8257            '<span class="text-dark">A visa is required for this destination. Let\'s get that sorted first!</span>' +
8258            '<div class="flex flex-col md:flex-row gap-2 mt-3">' +
8259            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8260            'onclick="handleEssentialAction(\'visa\',\'setup\',this)">Set up Visa' + chevronGif + '</button>' +
8261            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8262            'onclick="handleEssentialAction(\'visa\',\'later\',this)">Remind me later' + chevronSvg + '</button>' +
8263            '</div></div></div>';
8264        } else if (step === 'health-insurance') {
8265          _highlightPreviewRow('health-insurance');
8266          html = '<div class="message bot"><div class="message-content w-full">' +
8267            '<span class="text-dark">Health insurance is essential for nomads. SafetyWing is $42/month, covers you worldwide, and you can cancel anytime.</span>' +
8268            '<div class="flex flex-col md:flex-row gap-2 mt-3">' +
8269            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8270            'onclick="handleEssentialAction(\'health-insurance\',\'setup\',this)">Set up SafetyWing' + chevronGif + '</button>' +
8271            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8272            'onclick="handleEssentialAction(\'health-insurance\',\'later\',this)">Remind me later' + chevronSvg + '</button>' +
8273            '</div></div></div>';
8274        } else if (step === 'internet') {
8275          _highlightPreviewRow('internet');
8276          html = '<div class="message bot"><div class="message-content w-full">' +
8277            '<span class="text-dark">Stay connected from the moment you land! An eSIM means no hunting for local SIM cards.</span>' +
8278            '<div class="flex flex-col md:flex-row gap-2 mt-3">' +
8279            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8280            'onclick="handleEssentialAction(\'internet\',\'setup\',this)">Get eSIM' + chevronGif + '</button>' +
8281            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8282            'onclick="handleEssentialAction(\'internet\',\'later\',this)">Remind me later' + chevronSvg + '</button>' +
8283            '</div></div></div>';
8284        } else if (step === 'airport-lounge') {
8285          _highlightPreviewRow('airport-lounge');
8286          html = '<div class="message bot"><div class="message-content w-full">' +
8287            '<span class="text-dark">Last thing — your <strong>Priority Pass membership</strong> is included <strong>free</strong>! 🎉</span>' +
8288            '<p class="text-dark mt-1">Access 1,300+ airport lounges worldwide for $39/visit (normally $50-80 walk-up).</p>' +
8289            '<p class="text-dark mt-1">Want to add lounge passes for this trip?</p>' +
8290            '<div class="flex flex-col md:flex-row gap-2 mt-3">' +
8291            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#
8291EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8292            'onclick="handleEssentialAction(\'airport-lounge\',\'setup\',this)">Add lounge pass ($39)' + chevronGif + '</button>' +
8293            '<button type="button" class="essential-btn flex-1 flex justify-between items-center text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200" ' +
8294            'onclick="handleEssentialAction(\'airport-lounge\',\'later\',this)">Remind me later' + chevronSvg + '</button>' +
8295            '</div></div></div>';
8296        }
8297
8298        if (html) {
8299          chatMessages.insertAdjacentHTML('beforeend', html);
8300          chatMessages.scrollTop = chatMessages.scrollHeight;
8301        }
8302      };
8303
8304      window.handleEssentialAction = function(step, action, btnEl) {
8305        // Disable all buttons in this message
8306        var msgEl = btnEl.closest('.message');
8307        if (msgEl) {
8308          msgEl.querySelectorAll('.essential-btn').forEach(function(b) {
8309            b.disabled = true;
8310            b.style.opacity = '0.6';
8311            b.style.cursor = 'default';
8312            // Swap gif to svg on disable
8313            var gif = b.querySelector('.btn-icon-gif');
8314            var svg = b.querySelector('.btn-icon-svg');
8315            if (gif) gif.style.display = 'none';
8316            if (svg) svg.style.display = 'block';
8317          });
8318        }
8319        // Echo the user's choice
8320        var labels = {
8321          'visa': 'Set up Visa',
8322          'health-insurance': 'Set up SafetyWing',
8323          'internet': 'Get eSIM',
8324          'airport-lounge': 'Add lounge pass ($39)'
8325        };
8326        _echoUserChoice(action === 'setup' ? (labels[step] || 'Set up') : 'Remind me later');
8327
8328        // Update service row icon and button in preview panel
8329        _markServiceActioned(step);
8330
8331        // Update mobile tracker
8332        if (typeof _updateMobileTracker === 'function') _updateMobileTracker(step, 'done');
8333
8334        var _isMobileEssential = window.innerWidth < 768;
8335
8336        // Open the marketplace if "setup" — show confirmation message and open panel
8337        if (action === 'setup') {
8338          var chatMessages = document.getElementById('chatMessages');
8339          var confirmMessages = {
8340            'visa': _isMobileEssential ? 'Let\'s get your visa sorted.' : 'Let\'s get your visa sorted. Follow the steps on the right →',
8341            'health-insurance': _isMobileEssential ? 'Great choice! Let\'s get you covered.' : 'Great choice! Follow the steps on the right →',
8342            'internet': _isMobileEssential ? "Let's set up your eSIM now." : "Let's set up your eSIM now. Follow the steps on the right →",
8343            'airport-lounge': _isMobileEssential ? "Let's get you lounge access!" : "Let's get you lounge access! →"
8344          };
8345          if (chatMessages && confirmMessages[step]) {
8346            chatMessages.insertAdjacentHTML('beforeend',
8347              '<div class="message bot"><div class="message-content"><span class="text-dark">' +
8348              confirmMessages[step] + '</span></div></div>'
8349            );
8350            chatMessages.scrollTop = chatMessages.scrollHeight;
8351          }
8352
8353          if (step === 'visa' && typeof openMarketVisa === 'function') openMarketVisa();
8354          else if (step === 'health-insurance' && typeof openMarketHealthcare === 'function') openMarketHealthcare();
8355          else if (step === 'internet' && typeof openMarketESim === 'function') openMarketESim();
8356          else if (step === 'airport-lounge' && typeof openMarketLounge === 'function') openMarketLounge();
8357        }
8358
8359        // Advance to next step
8360        var next = _getNextStep(step);
8361        if (action === 'setup') {
8362          _essentialsSetupCount++;
8363          // Defer next step until user closes/leaves the service panel
8364          _pendingEssentialStep = next;
8365          if (!next) {
8366            _pendingEssentialStep = '__done__';
8367          }
8368        } else {
8369          // "Remind me later" — advance immediately
8370          if (next) {
8371            setTimeout(function() { showEssentialStep(next); }, 600);
8372          } else {
8373            // Final step done — show journey-ready summary
8374            setTimeout(_showEssentialsComplete, 600);
8375          }
8376        }
8377      };
8378
8379
8380      // End date step: inline calendar + "Not sure yet" button (1 month from start)
8381      window.showPlanMyTripEndDateStep = function(citySlug, cityName, startDateStr) {
8382        const chatMessages = document.getElementById('chatMessages');
8383        if (!chatMessages) return;
8384        const uid = Date.now();
8385        const endWidgetId = 'plan-trip-end-' + uid;
8386        const containerId = 'plan-trip-end-container-' + uid;
8387        // Calculate start date + 1 month for "Not sure yet"
8388        const startParts = startDateStr.split('-');
8389        const startDateObj = new Date(parseInt(startParts[0]), parseInt(startParts[1]) - 1, parseInt(startParts[2]));
8390        const oneMonthLater = new Date(startDateObj.getFullYear(), startDateObj.getMonth() + 1, startDateObj.getDate());
8391        const oneMonthStr = oneMonthLater.getFullYear() + '-' + String(oneMonthLater.getMonth() + 1).padStart(2, '0') + '-' + String(oneMonthLater.getDate()).padStart(2, '0');
8392        const block = `
8393          <div class="message bot" id="${containerId}" data-city-slug="${citySlug}" data-city-name="${(cityName || '').replace(/"/g, '&quot;')}" data-start-date="${startDateStr}">
8394            <div class="message-content">
8395              <span class="text-dark">Pick your end date:</span>
8396              <div class="mt-4 w-[320px] rounded-lg shadow-md p-3">
8397                <div class="plan-trip-calendar-widget" id="${endWidgetId}" data-city-slug="${citySlug}" data-min-date="${startDateStr}">
8398                  <div class="flex justify-between items-center mb-3">
8399                    <button type="button" class="plan-trip-calendar-prev w-8 h-8 flex items-center justify-center rounded-lg border border-[#EEE] text-dark text-lg leading-none cursor-pointer" aria-label="Previous month">&#8249;</button>
8400                    <span class="plan-trip-calendar-month text-sm font-semibold text-dark"></span>
8401                    <button type="button" class="plan-trip-calendar-next w-8 h-8 flex items-center justify-center rounded-lg border border-[#EEE] text-dark text-lg leading-none cursor-pointer" aria-label="Next month">&#8250;</button>
8402                  </div>
8403                  <div class="plan-trip-calendar-weekdays grid grid-cols-7 gap-0.5 text-center text-xs text-[#5D5D5D] mb-1"></div>
8404                  <div class="plan-trip-calendar-days grid grid-cols-7 gap-0.5 text-center text-sm"></div>
8405                  <input type="hidden" class="plan-trip-selected-date" value="" />
8406                </div>
8407              </div>
8408              <button type="button" class="plan-trip-not-sure-btn flex items-center gap-1 text-dark ps-3 pe-1.5 py-2 rounded-lg border border-solid border-[#3B3B3C] cursor-pointer transition-all duration-200 mt-4">
8409                Not sure yet
8410                <img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5 flex-shrink-0" />
8411              </button>
8412              <div class="plan-trip-confirm-end-wrap mt-4" style="display:none">
8413                <button type="button" class="plan-trip-confirm-end-date flex items-center gap-1 text-dark ps-3 pe-1.5 py-2 rounded-lg border border-solid border-[#3B3B3C] cursor-pointer transition-all duration-200">
8414                  Confirm end date
8415                  <img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5" />
8416                </button>
8417              </div>
8418            </div>
8419          </div>
8420        `;
8421        chatMessages.insertAdjacentHTML('beforeend', block);
8422        chatMessages.scrollTop = chatMessages.scrollHeight;
8423        const container = document.getElementById(containerId);
8424        const endWidget = document.getElementById(endWidgetId);
8425        const confirmWrap = container.querySelector('.plan-trip-confirm-end-wrap');
8426        const confirmBtn = container.querySelector('.plan-trip-confirm-end-date');
8427        const notSureBtn = container.querySelector('.plan-trip-not-sure-btn');
8428
8429        renderPlanTripCalendar(endWidget, function() {
8430          if (confirmWrap) confirmWrap.style.display = 'block';
8431          // Hide "Not sure yet" once a date is manually selected
8432          if (notSureBtn) notSureBtn.style.display = 'none';
8433          setTimeout(function() { chatMessages.scrollTop = chatMessages.scrollHeight; }, 50);
8434        });
8435        // Scroll after calendar grid renders
8436        setTimeout(function() { chatMessages.scrollTop = chatMessages.scrollHeight; }, 100);
8437
8438        // "Not sure yet" → set end date to start + 1 month and create journey
8439        if (notSureBtn) {
8440          notSureBtn.addEventListener('click', function() {
8441            notSureBtn.disabled = true;
8442            notSureBtn.style.opacity = '0.5';
8443            endWidget.querySelectorAll('button').forEach(function(b) { b.disabled = true; });
8444            endWidget.style.opacity = '0.6';
8445            if (confirmWrap) confirmWrap.style.display = 'none';
8446            const userEcho = '<div class="message user"><div class="message-content">Not sure yet – 1 month from start</div></div>';
8447            chatMessages.insertAdjacentHTML('beforeend', userEcho);
8448            chatMessages.scrollTop = chatMessages.scrollHeight;
8449            const name = (container.dataset.cityName || '').replace(/&quot;/g, '"');
8450            const startVal = container.dataset.startDate || '';
8451            createJourneyFromPlanTrip(citySlug, name, startVal, oneMonthStr, null, function(cn) { appendPlanMyTripSuccessMessage(cn); });
8452          });
8453        }
8454
8455        // Confirm end date from calendar
8456        if (confirmBtn) {
8457          confirmBtn.addEventListener('click', function() {
8458            const hidden = endWidget.querySelector('.plan-trip-selected-date');
8459            const endDateVal = hidden ? hidden.value : '';
8460            if (!endDateVal) return;
8461            confirmBtn.disabled = true;
8462            confirmBtn.style.opacity = '0.5';
8463            endWidget.querySelectorAll('button').forEach(function(b) { b.disabled = true; });
8464            endWidget.style.opacity = '0.6';
8465            const formatted = new Date(endDateVal + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
8466            const userEcho = '<div class="message user"><div class="message-content">' + formatted + '</div></div>';
8467            chatMessages.insertAdjacentHTML('beforeend', userEcho);
8468            chatMessages.scrollTop = chatMessages.scrollHeight;
8469            const name = (container.dataset.cityName || '').replace(/&quot;/g, '"');
8470            const startVal = container.dataset.startDate || '';
8471            createJourneyFromPlanTrip(citySlug, name, startVal, endDateVal, null, function(cn) { appendPlanMyTripSuccessMessage(cn); });
8472          });
8473        }
8474      };
8475
8476
8477      // Plan my trip chat workflow: show welcome text + "Plan my Trip" button in chat when destination opens
8478      window.appendPlanMyTripPrompt = function(citySlug, cityName, countryName) {
8479        const chatMessages = document.getElementById('chatMessages');
8480        if (!chatMessages) return;
8481
8482        // Same city already has a prompt — nothing to do
8483        if (chatMessages.querySelector('[data-plan-prompt-city="' + citySlug + '"]')) return;
8484
8485        const escapedName = (cityName || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
8486        const escapedSlug = (citySlug || '').replace(/&/g, '&amp;').replace(/'/g, "\\'").replace(/"/g, '&quot;');
8487        const escapedCountry = (countryName || '').replace(/&/g, '&amp;').replace(/'/g, "\\'").replace(/"/g, '&quot;');
8488
8489        // Find the most recent existing prompt (for a different city)
8490        const allPrompts = chatMessages.querySelectorAll('[data-plan-prompt-city]');
8491        const lastPrompt = allPrompts.length > 0 ? allPrompts[allPrompts.length - 1] : null;
8492
8493        if (lastPrompt) {
8494          const planBtn = lastPrompt.querySelector('.plan-trip-btn');
8495          const tripStarted = planBtn && planBtn.disabled;
8496
8497          if (!tripStarted) {
8498            // "Plan my Trip" hasn't been clicked yet — just update the existing prompt in-place
8499            lastPrompt.dataset.planPromptCity = citySlug;
8500            const textSpan = lastPrompt.querySelector('.text-dark');
8501            if (textSpan) {
8502              textSpan.innerHTML = escapedName + ' \u2014 great choice! Check out the details and hit \u201cPlan my trip\u201d when you\u2019re ready. \uD83D\uDC47';
8503            }
8504            if (planBtn) {
8505              planBtn.disabled = false;
8506              planBtn.style.opacity = '';
8507              planBtn.onclick = function() {
8508                this.disabled = true;
8509                this.style.opacity = '0.6';
8510                var si = this.querySelector('.pmt-icon-static'), hi = this.querySelector('.pmt-icon-hover');
8511                if (si) si.style.display = 'block';
8512                if (hi) hi.style.display = 'none';
8513                handleJourneyClick(citySlug, cityName, countryName);
8514              };
8515            }
8516            chatMessages.scrollTop = chatMessages.scrollHeight;
8517            return;
8518          }
8519          // Trip was already started — fall through to add a new prompt below
8520        }
8521
8522        const html = `
8523          <div class="message bot" data-plan-prompt-city="${citySlug}">
8524            <div class="message-content w-full">
8525              <span class="text-dark">${escapedName} — great choice! Check out the details and hit "Plan my trip" when you're ready. 👇</span>
8526              <button type="button"
8527                class="plan-trip-btn w-full flex justify-between items-center mt-4 text-dark px-2.5 py-1.5 rounded-lg border border-solid border-[#EEE] hover:border-[#3B3B3C] cursor-pointer transition-all duration-200"
8528                onclick="this.disabled=true;this.style.opacity='0.6';var si=this.querySelector('.pmt-icon-static'),hi=this.querySelector('.pmt-icon-hover');if(si)si.style.display='block';if(hi)hi.style.display='none';handleJourneyClick('${escapedSlug}','${escapedName}','${escapedCountry}');">
8529                Plan my Trip
8530                <img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5 pmt-icon-static" style="display:none;" /><img src="/static/icons/chevron_right.gif" alt="" class="w-5 h-5 pmt-icon-hover" />
8531              </button>
8532            </div>
8533          </div>
8534        `;
8535        chatMessages.insertAdjacentHTML('beforeend', html);
8536        chatMessages.scrollTop = chatMessages.scrollHeight;
8537      };
8538      
8539      window.showPlanMyTripTimeframeStep = function(citySlug, cityName, countryName) {
8540        const chatMessages = document.getElementById('chatMessages');
8541        if (!chatMessages) return;
8542        const escapedName = (cityName || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
8543        const today = new Date().toISOString().slice(0, 10);
8544        const uid = Date.now();
8545        const startWidgetId = 'plan-trip-start-' + uid;
8546        const containerId = 'plan-trip-start-container-' + uid;
8547        const userBubble = `
8548          <div class="message user">
8549            <div class="message-content">Yes! Plan my ${escapedName} trip</div>
8550          </div>
8551        `;
8552        const botBlock = `
8553          <div class="message bot" id="${containerId}" data-city-slug="${citySlug}" data-city-name="${(cityName || '').replace(/"/g, '&quot;')}" data-country-name="${(countryName || '').replace(/"/g, '&quot;')}">
8554            <div class="message-content">
8555              <span class="text-dark">Pick your start date:</span>
8556              <div class="mt-4 w-[320px] rounded-lg shadow-md p-3">
8557                <div class="plan-trip-calendar-widget" id="${startWidgetId}" data-city-slug="${citySlug}" data-min-date="${today}">
8558                  <div class="flex justify-between items-center mb-3">
8559                    <button type="button" class="plan-trip-calendar-prev w-8 h-8 flex items-center justify-center rounded-lg border border-[#EEE] text-dark text-lg leading-none cursor-pointer" aria-label="Previous month">&#8249;</button>
8560                    <span class="plan-trip-calendar-month text-sm font-semibold text-dark"></span>
8561                    <button type="button" class="plan-trip-calendar-next w-8 h-8 flex items-center justify-center rounded-lg border border-[#EEE] text-dark text-lg leading-none cursor-pointer" aria-label="Next month">&#8250;</button>
8562                  </div>
8563                  <div class="plan-trip-calendar-weekdays grid grid-cols-7 gap-0.5 text-center text-xs text-[#5D5D5D] mb-1"></div>
8564                  <div class="plan-trip-calendar-days grid grid-cols-7 gap-0.5 text-center text-sm"></div>
8565                  <input type="hidden" class="plan-trip-selected-date" value="" />
8566                </div>
8567              </div>
8568              <div class="plan-trip-confirm-start-wrap mt-4" style="display:none">
8569                <button type="button" class="plan-trip-confirm-start-date flex items-center gap-1 text-dark ps-3 pe-1.5 py-2 rounded-lg border border-solid border-[#3B3B3C] cursor-pointer transition-all duration-200">
8570                  Confirm start date
8571                  <img src="/static/icons/chevron_right_dark.svg" alt="" class="w-5 h-5" />
8572                </button>
8573              </div>
8574            </div>
8575          </div>
8576        `;
8577        chatMessages.insertAdjacentHTML('beforeend', userBubble + botBlock);
8578        chatMessages.scrollTop = chatMessages.scrollHeight;
8579        const container = document.getElementById(containerId);
8580        const startWidget = document.getElementById(startWidgetId);
8581        const confirmWrap = container.querySelector('.plan-trip-confirm-start-wrap');
8582        const confirmBtn = container.querySelector('.plan-trip-confirm-start-date');
8583        renderPlanTripCalendar(startWidget, function(selectedStart) {
8584          if (confirmWrap) confirmWrap.style.display = 'block';
8585          setTimeout(function() { chatMessages.scrollTop = chatMessages.scrollHeight; }, 50);
8586        });
8587        // Scroll after calendar grid renders
8588        setTimeout(function() { chatMessages.scrollTop = chatMessages.scrollHeight; }, 100);
8589        if (confirmBtn) {
8590          confirmBtn.addEventListener('click', function() {
8591            const hidden = startWidget.querySelector('.plan-trip-selected-date');
8592            const startDateVal = hidden ? hidden.value : '';
8593            if (!startDateVal) return;
8594            // Disable calendar and confirm button
8595            confirmBtn.disabled = true;
8596            confirmBtn.style.opacity = '0.5';
8597            startWidget.querySelectorAll('button').forEach(function(b) { b.disabled = true; });
8598            startWidget.style.opacity = '0.6';
8599            // Echo user selection
8600            const formatted = new Date(startDateVal + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
8601            const userEcho = '<div class="message user"><div class="message-content">' + formatted + '</div></div>';
8602            chatMessages.insertAdjacentHTML('beforeend', userEcho);
8603            chatMessages.scrollTop = chatMessages.scrollHeight;
8604            // Show end date step
8605            const name = (container.dataset.cityName || '').replace(/&quot;/g, '"');
8606            showPlanMyTripEndDateStep(citySlug, name, startDateVal);
8607          });
8608        }
8609      };
8610      
8611      // Toggle Favorite Function (if not already defined)
8612      if (typeof window.toggleFavorite === 'undefined') {
8613        window.toggleFavorite = function(event, citySlug, button) {
8614          if (event) {
8615            event.preventDefault();
8616            event.stopPropagation();
8617          }
8618          // Get CSRF token from cookies
8619          function getCookie(name) {
8620            let cookieValue = null;
8621            if (document.cookie && document.cookie !== "") {
8622              const cookies = document.cookie.split(";");
8623              for (let i = 0; i < cookies.length; i++) {
8624                const cookie = cookies[i].trim();
8625                if (cookie.substring(0, name.length + 1) === name + "=") {
8626                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
8627                  break;
8628                }
8629              }
8630            }
8631            return cookieValue;
8632          }
8633
8634          const csrftoken = getCookie("csrftoken");
8635
8636          // Disable button during request
8637          button.disabled = true;
8638
8639          fetch(`/favorites/${citySlug}/toggle/`, {
8640            method: 'POST',
8641            headers: {
8642              "Content-Type": "application/json",
8643              "X-CSRFToken": csrftoken,
8644              "X-Requested-With": "XMLHttpRequest",
8645            },
8646          })
8647            .then((response) => response.json())
8648            .then((data) => {
8649              if (data.success) {
8650                // Update button appearance
8651                const icon = button.querySelector(".favorite-icon");
8652                const img = button.querySelector("img");
8653                
8654                if (data.is_favorited) {
8655                  // Update SVG if it exists
8656                  if (icon) {
8657                    icon.innerHTML = `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" fill="currentColor"></path>`;
8658                  }
8659                  
8660                  // Update img if it exists
8661                  if (img) {
8662                    img.src = "/static/icons/star_active.svg";
8663                  }
8664                } else {
8665                  // Update SVG if it exists
8666                  if (icon) {
8667                    icon.innerHTML = `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path>`;
8668                  }
8669                  
8670                  // Update img if it exists
8671                  if (img) {
8672                    img.src = "/static/icons/star_default.svg";
8673                  }
8674                }
8675
8676                // Update data attribute
8677                button.setAttribute("data-favorited", data.is_favorited);
8678              }
8679            })
8680            .catch((error) => {
8681              console.error("Error toggling favorite:", error);
8682              showNotification("Failed to update favorite status. Please try again.");
8683            })
8684            .finally(() => {
8685              // Re-enable button
8686              button.disabled = false;
8687            });
8688        };
8689      }
8690      
8691      // Initialize on DOM ready
8692      if (document.readyState === 'loading') {
8693        document.addEventListener('DOMContentLoaded', () => {
8694          NomadosPanel.init();
8695        });
8696      } else {
8697        NomadosPanel.init();
8698      }
8699    </script>
8699
8700    <!-- Real-time membership status (profile, sidebar, Go Pro) -->
8701    
8701<script>
8702    (function() {
8703      var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
8704      var wsUrl = protocol + '//' + window.location.host + '/ws/status/';
8705
8706      // ── Loader helpers ──
8707      function showLoader(id, text) {
8708        var el = document.getElementById(id);
8709        if (!el) return;
8710        var txt = el.querySelector('[id$="-loader-text"]') || el.querySelector('span');
8711        if (txt) txt.textContent = text || 'Processing...';
8712        el.classList.remove('hidden');
8713        el.classList.add('flex');
8714      }
8715      function hideLoader(id) {
8716        var el = document.getElementById(id);
8717        if (!el) return;
8718        el.classList.add('hidden');
8719        el.classList.remove('flex');
8720      }
8721      function flashLoader(id, text, delayMs) {
8722        showLoader(id, text);
8723        setTimeout(function() { hideLoader(id); }, delayMs || 2000);
8724      }
8725
8726      // ── Status badge helper ──
8727      function updateStatusBadge(el, status, isPro) {
8728        if (!el) return;
8729        var isCanceled = (status === 'canceled');
8730        var isPastDue = (status === 'past_due');
8731        var isExpired = (status === 'expired');
8732        // Reset classes
8733        el.className = 'inline-flex items-center px-2 py-1 rounded-full text-xs font-medium';
8734        if (isCanceled && isPro) {
8735          el.textContent = 'Canceled';
8736          el.classList.add('bg-yellow-100', 'text-yellow-800');
8737        } else if (isPastDue) {
8738          el.textContent = 'Past Due';
8739          el.classList.add('bg-red-100', 'text-red-800');
8740        } else if (isExpired || (!isPro && status !== 'active')) {
8741          el.textContent = 'Expired';
8742          el.classList.add('bg-gray-100', 'text-gray-700');
8743        } else {
8744          el.classList.add('hidden');
8745          el.textContent = '';
8746        }
8747      }
8748
8749      // ── Go Pro card color helper ──
8750      function updateGoProCardColors(el, titleEl, status) {
8751        if (!el) return;
8752        // Remove old border classes
8753        el.className = el.className
8754          .replace(/border-\[#3B3B3C\]/g, '')
8755          .replace(/border-\[#FF735D\]/g, '')
8756          .replace(/border-\[#D9D9D9\]/g, '');
8757        // Add new border based on status (no bg changes — title color stays text-[#3B3B3C])
8758        if (status === 'past_due') {
8759          el.classList.add('border-[#FF735D]');
8760          if (titleEl) titleEl.innerHTML = '<span class="text-[#FF735D]">Pro</span> \u2014 Payment Failed !';
8761        } else if (status === 'canceled') {
8762          el.classList.add('border-[#D9D9D9]');
8763          if (titleEl) titleEl.innerHTML = '<span class="text-[#FF735D]">Pro</span> \u2014 Canceled';
8764        } else {
8765          el.classList.add('border-[#3B3B3C]');
8766          if (titleEl) titleEl.innerHTML = 'You\'re a <span class="text-[#FF735D]">Pro</span> member';
8767        }
8768      }
8769
8770      try {
8771        var ws = new WebSocket(wsUrl);
8772        ws.onmessage = function(event) {
8773          var data = JSON.parse(event.data);
8774          if (data.type !== 'status_update' || !data.payload) return;
8775          var p = data.payload;
8776
8777          // ── Loader events (cancel / switch / checkout processing) ──
8778          if (p.event === 'cancel_processing') {
8779            showLoader('membership-processing-loader', 'Canceling your subscription...');
8780            showLoader('go-pro-processing-loader', 'Canceling your subscription...');
8781            return;
8782          }
8783          if (p.event === 'cancel_failed') {
8784            flashLoader('membership-processing-loader', 'Cancel failed. Try again.', 2500);
8785            flashLoader('go-pro-processing-loader', 'Cancel failed. Try again.', 2500);
8786            return;
8787          }
8788          if (p.event === 'switch_processing') {
8789            showLoader('membership-processing-loader', 'Switching your plan...');
8790            showLoader('go-pro-processing-loader', 'Switching your plan...');
8791            return;
8792          }
8793          if (p.event === 'switch_failed') {
8794            flashLoader('membership-processing-loader', 'Switch failed. Try again.', 2500);
8795            flashLoader('go-pro-processing-loader', 'Switch failed. Try again.', 2500);
8796            return;
8797          }
8798
8799          // ── Derive state ──
8800          var isRevoked = (p.event === 'revoked' || p.tier === 'registered_free');
8801          var isPro = (p.tier === 'pro') || (p.event === 'granted' || p.event === 'renewed' || p.event === 'canceled' || p.event === 'payment_failed' || p.event === 'delinquent' || p.event === 'recovered');
8802          if (isRevoked) isPro = false;
8803          var subStatus = p.subscription_status || 'active';
8804          var isCanceledStatus = (p.event === 'canceled' || subStatus === 'canceled');
8805          var isPastDue = (subStatus === 'past_due' || p.event === 'payment_failed' || p.event === 'delinquent');
8806          var planText = (p.plan_type === 'monthly') ? 'Monthly plan' : (p.plan_type === 'annual') ? 'Annual plan' : (isPro ? 'Pro' : 'Free account');
8807          var renewalText = '';
8808          var dateStr = '';
8809          if (p.event === 'revoked' || !isPro) {
8810            renewalText = 'Upgrade to Pro for unlimited journeys and more.';
8811          } else if (isPastDue) {
8812            renewalText = 'Payment failed — update your payment method to avoid losing access.';
8813          } else if (p.current_period_end) {
8814            try {
8815              var d = new Date(p.current_period_end);
8816              dateStr = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
8817              renewalText = isCanceledStatus ? ('Access until ' + dateStr) : ('Renews ' + dateStr);
8818            } catch (e) {
8819              renewalText = 'Active subscription';
8820            }
8821          } else {
8822            renewalText = 'Active subscription';
8823          }
8824          var alreadyProRenewalText = isPastDue ? renewalText : ((isCanceledStatus && dateStr) ? ('Access until ' + dateStr) : renewalText);
8825
8826          // ── Loader transitions for completed events ──
8827          if (p.event === 'canceled') {
8828            flashLoader('membership-processing-loader', 'Subscription canceled.', 2000);
8829            flashLoader('go-pro-processing-loader', 'Subscription canceled.', 2000);
8830          } else if (p.event === 'granted' || p.event === 'renewed' || p.event === 'recovered') {
8831            flashLoader('membership-processing-loader', 'Welcome to Pro!', 2000);
8832            flashLoader('go-pro-processing-loader', 'Welcome to Pro!', 2000);
8833          } else {
8834            hideLoader('membership-processing-loader');
8835            hideLoader('go-pro-processing-loader');
8836          }
8837
8838          // ── Profile page ──
8839          var tierEl = document.getElementById('membership-tier-badge');
8840          var planEl = document.getElementById('membership-plan-text');
8841          var renewalEl = document.getElementById('membership-renewal-text');
8842          if (tierEl) {
8843            tierEl.textContent = isPro ? 'Pro' : 'Registered';
8844            tierEl.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ' + (isPro ? 'bg-[#14B8A6]/20 text-[#0D9488]' : 'bg-gray-100 text-gray-700');
8845          }
8846          if (planEl) planEl.textContent = planText;
8847          if (renewalEl) renewalEl.textContent = renewalText;
8848          var profileManageLink = document.getElementById('profile-manage-subscription-link');
8849          if (profileManageLink) profileManageLink.style.display = isPro ? '' : 'none';
8850
8851          // Profile status badge
8852          var statusBadge = document.getElementById('membership-status-badge');
8853          updateStatusBadge(statusBadge, subStatus, isPro);
8854
8855          // Profile cancel button
8856          var cancelBtn = document.getElementById('profile-cancel-subscription-btn');
8857          if (cancelBtn) cancelBtn.style.display = (isPro && (subStatus === 'active' || subStatus === 'past_due')) ? '' : 'none';
8858
8859          // Profile status message block
8860          var statusMsg = document.getElementById('membership-status-msg');
8861          if (statusMsg) {
8862            if (isCanceledStatus && isPro) {
8863              statusMsg.className = 'bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-2';
8864              statusMsg.innerHTML = '<p class="text-sm text-yellow-800 mb-1"><strong>Subscription Canceled</strong></p><p class="text-sm text-yellow-700">' + (dateStr ? "You'll retain Pro access until " + dateStr + '.' : 'Your Pro access has ended.') + '</p>';
8865            } else if (isPastDue) {
8866              statusMsg.className = 'bg-red-50 border border-red-200 rounded-lg p-3 mb-2';
8867              statusMsg.innerHTML = '<p class="text-sm text-red-800 mb
8867-1"><strong>Payment Failed</strong></p><p class="text-sm text-red-700">Your recent payment couldn\'t be processed. Please update your payment method.</p>';
8868            } else if (!isPro && p.event !== 'revoked') {
8869              statusMsg.className = 'bg-gray-50 border border-gray-200 rounded-lg p-3 mb-2';
8870              statusMsg.innerHTML = '<p class="text-sm text-gray-800 mb-1"><strong>Subscription Expired</strong></p><p class="text-sm text-gray-700">Your Pro subscription has expired. Upgrade to regain Pro features!</p>';
8871            } else {
8872              statusMsg.className = '';
8873              statusMsg.innerHTML = '<p class="text-sm text-gray-500">' + renewalText + '</p>';
8874            }
8875          }
8876
8877          // ── Sidebar (desktop + mobile) ──
8878          var sidebarBadge = document.getElementById('sidebar-membership-badge');
8879          if (sidebarBadge) {
8880            sidebarBadge.textContent = isPro ? 'Pro' : 'Registered';
8881            sidebarBadge.className = 'text-sm font-medium ' + (isPro ? 'text-[#14B8A6]' : 'text-gray-600');
8882          }
8883          var mobileSidebar = document.getElementById('mobile-sidebar-membership-text');
8884          if (mobileSidebar) mobileSidebar.textContent = isPro ? 'Pro member' : 'Registered';
8885
8886          // ── Go Pro content ──
8887          var ctaBlock = document.getElementById('go-pro-cta-block');
8888          var alreadyProBlock = document.getElementById('go-pro-already-pro');
8889          var alreadyProRenewal = document.getElementById('go-pro-already-pro-renewal');
8890          var alreadyProTitle = document.getElementById('go-pro-already-pro-title');
8891          if (ctaBlock) ctaBlock.classList.toggle('hidden', isPro);
8892          if (alreadyProBlock) {
8893            alreadyProBlock.classList.toggle('hidden', !isPro);
8894            updateGoProCardColors(alreadyProBlock, alreadyProTitle, subStatus);
8895          }
8896          if (alreadyProRenewal) alreadyProRenewal.textContent = isPro ? alreadyProRenewalText : renewalText;
8897
8898          // Pro price display (static $9/month, shown only for Pro users)
8899          var priceProBlock = document.getElementById('go-pro-price-pro');
8900          if (priceProBlock) priceProBlock.classList.toggle('hidden', !isPro);
8901
8902          // Manage membership block (shown for Pro users)
8903          var manageBlock = document.getElementById('go-pro-manage-block');
8904          if (manageBlock) manageBlock.classList.toggle('hidden', !isPro);
8905
8906          // Manage membership subtext (changes for canceled status)
8907          var manageSubtext = document.getElementById('go-pro-manage-subtext');
8908          if (manageSubtext) {
8909            manageSubtext.textContent = (isPro && isCanceledStatus)
8910              ? 'Reactivate your Pro membership on our secure portal'
8911              : 'Continue to our secure portal';
8912          }
8913        };
8914        ws.onclose = function() {};
8915      } catch (e) {}
8916
8917      // ── Cancel subscription (user-initiated via SamCart API) ──
8918      function getCsrfToken() {
8919        try { return document.cookie.match(/csrftoken=([^;]+)/)[1]; } catch(e) { return ''; }
8920      }
8921
8922      // ── Cancel confirmation modal helpers ──
8923      window.cancelSubscription = function() {
8924        var modal = document.getElementById('cancelSubModal');
8925        if (!modal) return;
8926        modal.classList.remove('hidden');
8927        modal.classList.add('flex');
8928        requestAnimationFrame(function() { modal.classList.remove('opacity-0'); });
8929      };
8930
8931      window.closeCancelSubModal = function() {
8932        var modal = document.getElementById('cancelSubModal');
8933        if (!modal) return;
8934        modal.classList.add('opacity-0');
8935        setTimeout(function() { modal.classList.add('hidden'); modal.classList.remove('flex'); }, 200);
8936      };
8937
8938      document.addEventListener('click', function(e) {
8939        var modal = document.getElementById('cancelSubModal');
8940        if (modal && modal.classList.contains('flex') && e.target === modal) closeCancelSubModal();
8941      });
8942
8943      window.confirmCancelSubscription = function() {
8944        closeCancelSubModal();
8945        console.log('[Cancel Sub] Starting cancel request...');
8946        var csrfToken = getCsrfToken();
8947        showLoader('membership-processing-loader', 'Canceling your subscription...');
8948        showLoader('go-pro-processing-loader', 'Canceling your subscription...');
8949        fetch('/services/samcart/cancel-subscription/', {
8950          method: 'POST',
8951          headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
8952          credentials: 'same-origin'
8953        })
8954        .then(function(resp) {
8955          console.log('[Cancel Sub] Response status:', resp.status, resp.statusText);
8956          return resp.json().then(function(d) { return { ok: resp.ok, status: resp.status, data: d }; });
8957        })
8958        .then(function(result) {
8959          console.log('[Cancel Sub] Response data:', result.data);
8960          if (!result.ok) {
8961            hideLoader('membership-processing-loader');
8962            hideLoader('go-pro-processing-loader');
8963            var msg = result.data.error || 'Unable to cancel. Please try again.';
8964            console.error('[Cancel Sub] Error:', msg, 'Status:', result.status);
8965            if (result.data.customer_hub_url) {
8966              msg += '\n\nYou can also cancel via the SamCart Customer Hub.';
8967            }
8968            showNotification(msg);
8969          } else {
8970            console.log('[Cancel Sub] Canceled. refund_issued=', result.data.refund_issued, 'refund_amount=', result.data.refund_amount);
8971            // WebSocket 'revoked' event handles the UI downgrade to free
8972          }
8973        })
8974        .catch(function(err) {
8975          console.error('[Cancel Sub] Network error:', err);
8976          hideLoader('membership-processing-loader');
8977          hideLoader('go-pro-processing-loader');
8978          showNotification('Network error. Please try again or c
8978ontact support.');
8979        });
8980      };
8981
8982      // ── Switch plan (active users: cancel + refund + open checkout for new plan) ──
8983      window.switchPlan = function(targetPlan) {
8984        var planLabel = targetPlan === 'annual' ? 'Annual ($59/yr)' : 'Monthly ($9/mo)';
8985        if (!confirm('Switch to ' + planLabel + '?\n\nYour current subscription will be canceled and a prorated refund issued. You will then check out on the new plan.')) return;
8986        console.log('[Switch Plan] Starting switch to', targetPlan);
8987        showLoader('membership-processing-loader', 'Switching your plan...');
8988        showLoader('go-pro-processing-loader', 'Switching your plan...');
8989        fetch('/services/samcart/switch-plan/', {
8990          method: 'POST',
8991          headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() },
8992          credentials: 'same-origin',
8993          body: JSON.stringify({ target_plan: targetPlan })
8994        })
8995        .then(function(resp) {
8996          console.log('[Switch Plan] Response status:', resp.status, resp.statusText);
8997          return resp.json().then(function(d) { return { ok: resp.ok, status: resp.status, data: d }; });
8998        })
8999        .then(function(result) {
9000          console.log('[Switch Plan] Response data:', result.data);
9001          hideLoader('membership-processing-loader');
9002          hideLoader('go-pro-processing-loader');
9003          if (!result.ok) {
9004            var msg = result.data.error || 'Unable to switch plan. Please try again.';
9005            console.error('[Switch Plan] Error:', msg, 'Status:', result.status);
9006            if (result.data.customer_hub_url) {
9007              msg += '\n\nYou can also manage your plan via the SamCart Customer Hub.';
9008            }
9009            showNotification(msg);
9010            return;
9011          }
9012          console.log('[Switch Plan] Success! Opening checkout for', targetPlan);
9013          // Open the SamCart checkout modal for the new plan
9014          var modal = document.getElementById('goProModal');
9015          if (modal) {
9016            modal.setAttribute('data-selected-plan', targetPlan);
9017            if (typeof window.submitGoProCheckout === 'function') {
9018              window.submitGoProCheckout();
9019            }
9020          }
9021        })
9022        .catch(function(err) {
9023          console.error('[Switch Plan] Network error:', err);
9024          hideLoader('membership-processing-loader');
9025          hideLoader('go-pro-processing-loader');
9026          showNotification('Network error. Please try again or contact support.');
9027        });
9028      };
9029    })();
9030    </script>
9030
9031  
9031<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"00bcde813c7e4560bc694b3e7f874119","r":1,"spa":2}' crossorigin="anonymous"></script>
9031
9032</body>
9033</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.