0% found this document useful (0 votes)
13 views51 pages

new one

The document is an HTML template for a Museum Chatbot, featuring various styles and scripts for functionality. It includes elements for user interaction, such as chat messages, buttons, and forms, with support for dark mode. The design utilizes Tailwind CSS for styling and incorporates features like QR code generation and email integration.

Uploaded by

punithyellanti
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views51 pages

new one

The document is an HTML template for a Museum Chatbot, featuring various styles and scripts for functionality. It includes elements for user interaction, such as chat messages, buttons, and forms, with support for dark mode. The design utilizes Tailwind CSS for styling and incorporates features like QR code generation and email integration.

Uploaded by

punithyellanti
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 51

<!

DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Museum Chatbot</title>
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<link href="https://fonts.googleapis.com/css2?
family=Inter:wght@400;600&family=Noto+Sans+Devanagari&family=Noto+Sans+Tamil&displa
y=swap" rel="stylesheet">
<script
src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.j
s"></script>
<!-- EmailJS SDK -->
<script type="text/javascript"
src="https://cdn.jsdelivr.net/npm/@emailjs/browser@3/dist/email.min.js">
</script>
<script src="https://cdn.jsdelivr.net/npm/qrcode/build/qrcode.min.js"></script>
<style>
body {
font-family: 'Inter', 'Noto Sans Tamil', 'Noto Sans Devanagari', sans-
serif;
}

/* Dark Mode Styles */


body.dark-mode {
background-color: #1a202c;
color: #e2e8f0;
}
body.dark-mode .bg-gray-100 {
background-color: #2d3748;
}
body.dark-mode .bg-white {
background-color: #2d3748;
color: #e2e8f0;
}
body.dark-mode .text-gray-700 {
color: #e2e8f0;
}
body.dark-mode label.text-gray-700 { /* Specific fix for labels in dark
mode */
color: #e2e8f0;
}
body.dark-mode .text-blue-600 {
color: #63b3ed;
}
body.dark-mode .chat-message.receiver .message-content {
background-color: #4a5568;
color: #e2e8f0;
}
body.dark-mode #chat-container {
background-color: #2d3748;
border-color: #4a5568;
}
body.dark-mode .shadow-lg {
box-shadow: 0 10px 15px -3px rgba(255, 255, 255, 0.1), 0 4px 6px -2px
rgba(255, 255, 255, 0.05);
}
body.dark-mode .input-area input,
body.dark-mode .input-area select, /* Added select */
body.dark-mode #user-details-form input, /* Added user details form inputs
*/
body.dark-mode #user-details-form select, /* Added user details form
selects */
body.dark-mode #persons-selection select, /* Added persons select */
body.dark-mode #date-picker, /* Added date picker */
body.dark-mode .person-details-form input, /* Added person details inputs
*/
body.dark-mode .person-details-form select { /* Added person details
selects */
background-color: #4a5568;
color: #e2e8f0;
border-color: #718096;
}
body.dark-mode #previous-tickets { /* Added previous tickets styling */
background-color: #4a5568;
border-color: #718096;
}
body.dark-mode .person-details-form { /* Added person details form styling
*/
background-color: #4a5568;
border-color: #718096;
}
body.dark-mode .person-details { /* Added person details div styling */
background-color: #4a5568;
border-color: #718096;
}
body.dark-mode #reference-number { /* Added reference number styling */
background-color: #4a5568;
border-color: #718096;
}
body.dark-mode #payment-status { /* Added payment status styling */
color: #e2e8f0;
}

#ticket-pdf-content {
display: none;
}

#chat-container {
display: flex;
flex-direction: column;
height: 400px;
overflow-y: auto;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
background-color: #f7fafc;
margin-bottom: 1rem;
}

.chat-message {
display: flex;
margin-bottom: 0.75rem;
}
.sender {
justify-content: flex-end;
}

.receiver {
justify-content: flex-start;
}

.message-content {
max-width: 80%;
padding: 0.75rem 1rem;
border-radius: 1.25rem;
white-space: pre-wrap; /* Ensure line breaks are rendered */
word-wrap: break-word; /* Break long words */
}

.sender .message-content {
background-color: #3b82f6;
color: white;
border-top-right-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
border-top-left-radius: 1.25rem;
border-bottom-left-radius: 1.25rem;
}

.receiver .message-content {
background-color: #e2e8f0;
color: #1a202c;
border-top-left-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
border-top-right-radius: 1.25rem;
}

.input-area {
display: flex;
align-items: center;
gap: 0.5rem;
}

#user-input {
flex: 1;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 1.25rem; /* Make input rounder */
font-size: 1rem;
outline: none;
}

#user-input:focus {
ring: 2 ring-blue-500 border-transparent;
}

#send-button {
padding: 0.75rem 1.5rem;
background-color: #3b82f6;
color: white;
border-radius: 1.25rem; /* Make button rounder */
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
display: flex;
align-items: center;
gap: 0.5rem;
}

#send-button:hover {
background-color: #2563eb;
}

#send-button:focus {
outline: none;
ring: 2 ring-blue-500 ring-opacity-50;
}

.action-button {
background-color: #3b82f6;
color: white;
font-weight: 600;
border-radius: 1.25rem; /* Make button rounder */
padding: 0.75rem 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
margin: 0.5rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}

.action-button:hover {
background-color: #2563eb;
}

.action-button:focus {
outline: none;
ring: 2 ring-blue-500 ring-opacity-50;
}

.action-button:disabled {
background-color: #a0aec0;
cursor: not-allowed;
}

.cancel-button {
background-color: #ef4444;
color: white;
font-weight: 600;
border-radius: 1.25rem; /* Make button rounder */
padding: 0.75rem 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
margin: 0.5rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.cancel-button:hover {
background-color: #dc2626;
}

.logout-button {
background-color: #ef4444;
color: white;
font-weight: 600;
border-radius: 1.25rem; /* Make button rounder */
padding: 0.75rem 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}

.logout-button:hover {
background-color: #dc2626;
}

.history-button {
background-color: #10b981;
color: white;
font-weight: 600;
border-radius: 1.25rem; /* Make button rounder */
padding: 0.75rem 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}

.history-button:hover {
background-color: #059669;
}

.hidden {
display: none;
}

.typing-indicator {
display: flex;
align-items: center;
margin-left: 1rem;
margin-top: 0.5rem; /* Added margin top */
}

.typing-dot {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background-color: #d1d5db; /* Lighter dot in light mode */
margin: 0 0.25rem;
animation: typing 1s infinite;
}
body.dark-mode .typing-dot {
background-color: #9ca3af; /* Darker dot in dark mode */
}

.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}

.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}

@keyframes typing {
0% { opacity: 0.4; transform: translateY(0); }
50% { opacity: 1; transform: translateY(-0.25rem); }
100% { opacity: 0.4; transform: translateY(0); }
}

.museum-button,
.service-button {
background-color: #3b82f6;
color: white;
font-weight: 600;
border-radius: 1.25rem;
padding: 0.8rem 1.6rem;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
border: none;
margin: 0.5rem;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
width: 100%;
text-align: center;
}
.service-button:disabled { /* Style for disabled time slot buttons */
background-color: #a0aec0;
cursor: not-allowed;
transform: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
body.dark-mode .service-button:disabled {
background-color: #4a5568;
color: #718096;
}

.museum-button:hover,
.service-button:hover:not(:disabled) { /* Apply hover only if not disabled
*/
background-color: #2563eb;
transform: translateY(-0.125rem);
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
}

.museum-button:active,
.service-button:active:not(:disabled) {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.museum-button:focus,
.service-button:focus {
outline: none;
ring: 2 ring-blue-500 ring-opacity-50;
border-radius: 1.5rem;
}

.button-icon {
width: 1.25rem;
height: 1.25rem;
}

#owner-qr-code {
margin-top: 1rem;
text-align: center;
}

#ticket-pdf-content img {
display: block;
margin: 10px auto;
}

#payment-status {
margin-top: 1rem;
text-align: center;
color: #1a202c;
font-weight: 600; /* Made bold */
}

#persons-selection select {
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
font-size: 1rem;
outline: none;
width: 100%;
margin-top: 0.5rem;
}

#previous-tickets {
margin-top: 1rem;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background-color: #f7fafc;
}

#previous-tickets h3 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}

#previous-tickets-list {
max-height: 150px;
overflow-y: auto;
}

.ticket-item {
padding: 0.5rem;
border-bottom: 1px solid #e2e8f0;
white-space: pre-line; /* Render line breaks in history */
}
body.dark-mode .ticket-item {
border-bottom-color: #718096;
}

.ticket-item:last-child {
border-bottom: none;
}

/* Date picker styles */


#date-selection {
margin-top: 1rem;
}

#date-picker {
width: 100%;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
font-size: 1rem;
margin-top: 0.5rem;
}

/* Person details form styles */


.person-details-form {
margin-top: 1rem;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background-color: #f7fafc;
}

.person-details-form h4 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}

.person-details-form input,
.person-details-form select {
width: 100%;
padding: 0.5rem;
margin-bottom: 0.5rem;
border: 1px solid #e2e8f0;
border-radius: 0.25rem;
}

.person-details-container {
margin-top: 1rem;
}
.person-details {
margin-bottom: 1rem;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background-color: #f7fafc;
}

/* Ticket details styles */


.ticket-details-container {
white-space: pre-line;
margin-bottom: 1rem;
}

.attendee-list {
margin-top: 0.5rem;
padding-left: 1rem;
}

.attendee-item {
margin-bottom: 0.25rem;
}

/* Reference number styles */


#reference-number {
margin-top: 1rem;
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background-color: #f7fafc;
font-weight: bold;
text-align: center;
}

/* Lookup button styles */


.lookup-button {
background-color: #8b5cf6;
color: white;
font-weight: 600;
border-radius: 1.25rem; /* Make button rounder */
padding: 0.75rem 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
margin: 0.5rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}

.lookup-button:hover {
background-color: #7c3aed;
}

/* Validation QR code styles */


#validation-qr-code {
margin: 20px auto;
text-align: center;
}
#validation-qr-code p {
margin-bottom: 10px;
font-weight: bold;
}
</style>
</head>
<body class="bg-gray-100 flex justify-center items-center min-h-screen">
<div class="bg-white shadow-lg rounded-lg p-6 w-full max-w-md">
<h1 class="text-2xl font-semibold text-blue-600 mb-4 text-center">Welcome
to the Museum Chatbot</h1>
<div class="flex justify-between mb-4">
<select id="language-select" class="border rounded p-2">
<option value="en">English</option>
<option value="hi">Hindi</option>
<option value="ta">Tamil</option>
</select>
<button id="dark-mode-toggle" class="action-button text-sm"> <!-- Made
button smaller -->
<svg id="dark-mode-icon" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <!-- Icon size adjusted --
>
<!-- Sun Icon (Default Light Mode) -->
<path fill-rule="evenodd" d="M10 2a.75.75 0 0
1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2Zm0 12a.75.75 0 0
1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 14Zm-5.657-2.843a.75.75 0 0 1
0 1.06l-1.061 1.061a.75.75 0 1 1-1.06-1.06l1.06-1.061a.75.75 0 0 1 1.061 0Zm12.728
0a.75.75 0 0 1 1.06 0l1.061 1.061a.75.75 0 1 1-1.06 1.06l-1.061-1.06a.75.75 0 0 1
0-1.061ZM10 5a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z" clip-rule="evenodd" />
</svg>
<span id="dark-mode-text">Dark Mode</span> <!-- Added span for text
-->
</button>
</div>
<div class="flex justify-between mb-4">
<button id="view-history-button" class="history-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M1.5 3.141A1.5 1.5 0 0 1 3
1.641h14a1.5 1.5 0 0 1 1.5 1.5v1.05a.75.75 0 0 1-1.5 0V3.14a.5.5 0 0 0-.5-.5H3a.5.5
0 0 0-.5.5v13.718a.5.5 0 0 0 .5.5h5.75a.75.75 0 0 1 0 1.5H3A1.5 1.5 0 0 1 1.5
17V3.141ZM10 9a.75.75 0 0 1 .75.75v1.562l1.007.503a.75.75 0 0 1-.514 1.379l-
1.5-.75A.75.75 0 0 1 9.25 12v-2.25A.75.75 0 0 1 10 9Z" clip-rule="evenodd" />
<path d="M12.25 10.5a.75.75 0 0 0-1.5 0v5.31l-1.77-
1.062a.75.75 0 1 0-.94 1.624l2.5 1.5a.75.75 0 0 0 .94 0l6.5-3.9a.75.75 0 0 0-.47-
1.385l-3.5-.011v-1.716a.75.75 0 0 0-1.5 0v2.016a.75.75 0 0 0 .47
1.385l2.658.009L12.25 15.81v-5.31Z" />
</svg>
<span id="history-button-text">View History</span> <!-- Added span
for text -->
</button>
<button id="logout-button" class="logout-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M3 4.25A2.25 2.25 0 0 1 5.25
2h5.5A2.25 2.25 0 0 1 13 4.25v2a.75.75 0 0 1-1.5 0v-2a.75.75 0 0 0-.75-.75h-
5.5a.75.75 0 0 0-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 0 0 .75-.75v-2a.75.75
0 0 1 1.5 0v2A2.25 2.25 0 0 1 10.75 18h-5.5A2.25 2.25 0 0 1 3 15.75V4.25Zm9.22
3.97a.75.75 0 0 1 1.06 0l3 3a.75.75 0 0 1 0 1.06l-3 3a.75.75 0 1 1-1.06-1.06l1.72-
1.72H5.25a.75.75 0 0 1 0-1.5h8.69l-1.72-1.72a.75.75 0 0 1 0-1.06Z" clip-
rule="evenodd" />
</svg>
<span id="logout-button-text">Logout</span> <!-- Added span for
text -->
</button>
</div>
<div class="flex justify-center mb-4">
<button id="lookup-button" class="lookup-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 1 0 0 11 5.5 5.5
0 0 0 0-11ZM2 9a7 7 0 1 1 12.452 4.391l3.328 3.329a.75.75 0 1 1-1.06 1.06l-3.329-
3.328A7 7 0 0 1 2 9Z" clip-rule="evenodd" />
</svg>
<span id="lookup-button-text">Lookup Booking</span> <!-- Added span
for text -->
</button>
</div>
<div id="previous-tickets" class="hidden">
<h3 id="previous-tickets-header">Your Previously Booked Tickets</h3>
<div id="previous-tickets-list"></div>
</div>
<div id="chat-container">
<!-- Initial message will be added by JS based on language -->
</div>
<div class="typing-indicator hidden" id="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
<form id="input-form" class="input-area mt-2"> <!-- Added margin top -->
<input type="text" id="user-input" placeholder="Enter your message..."
class="rounded-full">
<button id="send-button" type="submit" class="rounded-full">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" class="w-6 h-6">
<path d="M3.105 2.391a.75.75 0 0 0-.272.77V8h10.5a.75.75 0 0 1
0 1.5H3.105v4.839a.75.75 0 0 0 .272.77l9.162 4.142A.75.75 0 0 0 13 18V6a.75.75 0 0
0-1.563-.692l-9.162 4.141Z" />
</svg>
<span id="send-button-text">Send</span> <!-- Added span for text --
>
</button>
</form>
<div id="user-details-form" class="space-y-4 hidden mt-4">
<div>
<label for="phone" class="block text-gray-700 text-sm font-bold mb-
2" id="phone-label">Phone Number:</label>
<input type="tel" id="phone" placeholder="Enter your phone number"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700
leading-tight focus:outline-none focus:shadow-outline rounded-full">
</div>
<div>
<label for="email" class="block text-gray-700 text-sm font-bold mb-
2" id="email-label">Email:</label>
<input type="email" id="email" placeholder="Enter your email"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700
leading-tight focus:outline-none focus:shadow-outline rounded-full">
</div>
<button onclick="getUserDetails()" class="action-button" id="submit-
details-button">Submit</button>
</div>
<div id="user-details-response" class="hidden mt-4">
<p id="user-details-response-text" class="font-bold"></p>
</div>
<div id="museum-selection" class="space-y-2 hidden mt-4">
<p class="text-gray-700" id="museum-select-text">First, please select a
museum:</p>
<div class="flex flex-col">
<button onclick="selectMuseum('natural-history')" class="museum-
button">
<img
src="https://unpkg.com/lucide-static@latest/icons/landmark.svg" alt="Natural
History Museum" class="button-icon" />
<span class="museum-name">Museum of Natural History</span>
</button>
<button onclick="selectMuseum('art')" class="museum-button">
<img
src="https://unpkg.com/lucide-static@latest/icons/image.svg" alt="Art Museum"
class="button-icon" />
<span class="museum-name">Art Museum</span>
</button>
<button onclick="selectMuseum('science')" class="museum-button">
<img
src="https://unpkg.com/lucide-static@latest/icons/atom.svg" alt="Science Museum"
class="button-icon" />
<span class="museum-name">Science Museum</span>
</button>
</div>
</div>
<div id="service-selection" class="space-y-2 hidden mt-4">
<p class="text-gray-700" id="service-select-text">Great! Now, what type
of service would you like today?</p>
<div class="flex flex-col">
<button onclick="selectService('guided-tour')" class="service-
button">
<img src="https://unpkg.com/lucide-static@latest/icons/map-
pin.svg" alt="Guided Tour" class="button-icon" />
<span class="service-name">Guided Tour</span>
</button>
<button onclick="selectService('event-booking')" class="service-
button">
<img
src="https://unpkg.com/lucide-static@latest/icons/calendar.svg" alt="Event Booking"
class="button-icon" />
<span class="service-name">Event Booking</span>
</button>
<button onclick="selectService('general-inquiry')" class="service-
button">
<img
src="https://unpkg.com/lucide-static@latest/icons/info.svg" alt="General Inquiry"
class="button-icon" />
<span class="service-name">General Inquiry</span>
</button>
</div>
</div>
<div id="date-selection" class="space-y-2 hidden mt-4">
<p class="text-gray-700" id="date-select-text">Please select a date for
your visit:</p>
<input type="date" id="date-picker" min="">
</div>
<div id="time-selection" class="space-y-2 hidden mt-4">
<p id="time-selection-text" class="text-gray-700">Available time
slots:</p>
<div id="time-slots" class="flex flex-col">
</div>
<!-- Removed Confirm button here, selection triggers next step -->
</div>
<div id="persons-selection" class="space-y-2 hidden mt-4">
<p class="text-gray-700" id="persons-select-text">How many persons will
be attending?</p>
<select id="persons-count" onchange="updatePersons()">
<!-- Options will be dynamically populated -->
</select>
<button onclick="showPersonDetailsForm()" class="action-button"
id="continue-button">Continue</button>
</div>
<div id="person-details-container" class="hidden mt-4">
<div id="person-details-forms" class="person-details-container"></div>
<button onclick="confirmPersonDetails()" class="action-button"
id="confirm-details-button">Confirm Details</button>
</div>
<div id="time-and-cost" class="hidden mt-4">
<p id="time-and-cost-text" class="font-bold"></p>
</div>
<div id="error-message" class="hidden mt-4 bg-red-100 border border-red-400
text-red-700 px-4 py-3 rounded relative" role="alert">
<strong class="font-bold">Error:</strong>
<span class="block sm:inline" id="error-message-text"></span>
</div>
<div id="ticket-section" class="space-y-4 hidden mt-4">
<div id="reference-number" class="hidden">
<p><span id="reference-label">Reference Number:</span> <span
id="reference-number-value"></span></p>
</div>
<div id="ticket-details" class="ticket-details-container font-bold
border p-3 rounded"></div> <!-- Added border/padding -->
<div id="owner-qr-code" class="hidden">
<p id="scan-pay-text">Scan to Pay via UPI (Amount: ₹<span id="upi-
amount"></span>):</p>
<!-- QR Code Canvas will be added here -->
</div>
<div id="validation-qr-code" class="hidden">
<p id="validation-qr-text">Ticket Validation QR:</p>
<!-- QR Code Canvas will be added here -->
</div>
<div class="flex flex-wrap justify-center"> <!-- Use flex-wrap -->
<button onclick="printTicket('alert')" id="print-alert-button"
class="action-button">Print (Alert)</button> <!-- Shortened Text -->
<button onclick="printTicket('pdf')" id="print-pdf-button"
class="action-button">Print (PDF)</button> <!-- Shortened Text -->
</div>
<div class="flex flex-wrap justify-center"> <!-- Use flex-wrap -->
<button onclick="showUpiQrCode()" class="action-button" id="show-
qr-button">Show Payment QR</button> <!-- Shortened Text -->
<button onclick="checkPayment()" class="action-button" id="check-
payment-button">Check Payment</button>
<button id="confirm-ticket-button" onclick="confirmTicket()"
class="action-button hidden" disabled>Confirm & Email</button> <!-- Shortened Text
-->
</div>
<div id="payment-status" class="hidden"></div>
</div>
<div id="confirmation-message" class="hidden mt-4 bg-green-100 border
border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
<strong class="font-bold">Success!</strong>
<span class="block sm:inline" id="confirmation-message-text"></span>
<button id="cancel-ticket-button" onclick="cancelTicket()"
class="cancel-button mt-2"> <!-- Added margin top -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" class="w-6 h-6">
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0
16Zm3.707-10.707a1 1 0 0 0-1.414-1.414L10 8.586 7.707 6.293a1 1 0 0 0-1.414
1.414L8.586 10l-2.293 2.293a1 1 0 1 0 1.414 1.414L10 11.414l2.293 2.293a1 1 0 0 0
1.414-1.414L11.414 10l2.293-2.293Z" clip-rule="evenodd" /> <!-- Changed icon
slightly -->
</svg>
<span id="cancel-ticket-text">Cancel Ticket</span> <!-- Added span
for text -->
</button>
</div>
</div>

<!-- Ticket PDF Content (Hidden) -->


<div id="ticket-pdf-content" class="hidden">
<div id="ticket-content" style="padding: 20px; border: 1px solid #ccc;
font-family: Arial, sans-serif;">
<h2 style="text-align: center; font-size: 24px; margin-bottom: 15px;
color: #333;">Museum Ticket</h2>
<div id="reference-number-pdf" style="text-align: center; font-weight:
bold; margin-bottom: 15px; font-size: 14px; color: #555;"></div>
<div id="ticket-details-pdf" class="ticket-details-container"
style="font-size: 14px; line-height: 1.6; margin-bottom: 20px; color: #333;">
<!-- Content will be dynamically inserted -->
</div>
<div id="validation-qr-code-pdf" style="text-align: center; margin-top:
25px;">
<p style="font-weight: bold; margin-bottom: 10px; font-size: 16px;
color: #333;">Ticket Validation QR Code</p>
<!-- QR code canvas will be inserted here -->
</div>
<p style="text-align: center; margin-top: 20px; font-size: 12px;
color: #888;">Thank you for visiting!</p>
</div>
</div>

<script>
// Initialize EmailJS with your public key
(function() {
try {
// Use the provided Public Key
emailjs.init("9BHIclDoDXmRMDi4t");
console.log("EmailJS Initialized Successfully");
} catch (error) {
console.error("EmailJS initialization failed:", error);
// Optionally display an error to the user or disable email
functionality
alert("Could not initialize email service. Email confirmation might
not be available.");
}
})();

// Language Translations (No changes needed here for this request)


const translations = {
en: {
welcome: "Welcome to the Museum Chatbot",
helloMessage: "Hello! I'm your guide. How can I help you
today?", // Updated initial message
userDetailsPrompt: "Great! To proceed with booking, please provide
your phone number and email.", // Slightly rephrased
selectMuseumPrompt: "Okay, please select a museum you'd like to
visit.",
selectServicePrompt: "You've selected the {museum}. Now, what type
of service would you like?",
selectDatePrompt: "Please select a date for your visit (April 5th -
7th available):", // Updated prompt slightly
selectTimePrompt: "You've chosen {service}. Available slots for
{date}:", // Included date
invalidPhone: "Invalid phone number. Please enter a 10-digit
number.",
invalidEmail: "Invalid email address.",
selectMuseumError: "Please select a museum from the options.",
selectServiceError: "Please select a service: Guided Tour, Event
Booking, or General Inquiry.",
alreadySelected: "It looks like you've already selected a museum
and service. Let's choose a date and time, or you can ask something else.", //
Rephrased
detailsSaved: "Thanks! Your details have been saved.",
noTickets: "Sorry, only {tickets} ticket(s) are available for this
slot. Please select {tickets} or fewer persons.", // Improved clarity
noTicketsError: "Only {tickets} ticket(s) available for the
selected time slot.", // More specific error
ticketDetails: "Museum: {museum}\nService: {service}\nDate: {date}\
nTime: {time}\nPersons: {persons}\nTotal Cost: ₹{cost}\nPhone: {phone}\nEmail:
{email}", // Date before time
noPayment: "No payment required for this service.",
noPaymentReady: "No payment required. Ticket ready to confirm!", //
Updated
noPaymentMessage: "This service is free. You can print or confirm
your ticket now.", // Updated
scanUpi: "Scan the UPI QR code below to pay ₹{cost}. After payment,
click 'Check Payment' to verify.", // Added cost
paymentVerified: "Payment verified successfully!",
paymentNotConfirmed: "Payment not confirmed. Please complete the
payment and try again.",
paymentNotConfirmedMessage: "Payment not confirmed. Please scan the
QR code, complete the payment, then click 'Check Payment' again.", // Rephrased
paymentVerifyPrompt: "Please verify the payment before confirming
the ticket.",
ticketConfirmed: "Your ticket is confirmed! We've sent the details
to {email}. Enjoy your visit!", // Mentioned email
ticketConfirmedMessage: "Ticket confirmed! Details sent to your
email.", // Shorter chat message
emailFailed: "Ticket confirmed, but the confirmation email failed
to send. Please check your email address or contact support. Your booking reference
is {referenceNumber}.", // Included reference number
emailFailedMessage: "Ticket confirmed locally, but email sending
failed. Ref: {referenceNumber}", // Shorter chat message
timeAndCost: "Selected: {time} on {date}\nCost: ₹{cost} per
person", // Combined info
personsPrompt: "How many persons? (Max 5, or {ticketsLeft} if fewer
available)", // Show limit
timeSlotsPrompt: "Available time slots for {date}:", // Show date
previousTicketsHeader: "Your Previously Booked Tickets",
noPreviousTickets: "No previous tickets found.",
ticketCancelled: "Your ticket (Ref: {referenceNumber}) has been
cancelled successfully.", // Included ref#
ticketCancelledMessage: "Ticket Ref: {referenceNumber} cancelled!
Feel free to book a new one.", // Shorter chat message
viewTicketHistory: "View History", // Shortened
hideTicketHistory: "Hide History", // Shortened
personDetailsTitle: "Person {number} Details",
enterName: "Enter name",
enterAge: "Enter age",
selectGender: "Select gender",
male: "Male",
female: "Female",
other: "Other",
attendees: "Attendees:",
lookupBooking: "Lookup Booking",
lookupPromptType: "Lookup by: 1) Reference Number or 2) Phone
Number?",
lookupPromptRef: "Enter your reference number:",
lookupPromptPhone: "Enter your phone number (10 digits):",
bookingNotFound: "Booking not found. Please check your details.",
bookingsFound: "Your bookings:\n{list}\nEnter the number of the
booking to view:",
bookingDetailsTitle: "Booking Details",
referenceNumber: "Reference Number: {number}",
validationQR: "Ticket Validation QR Code",
submit: "Submit",
continue: "Continue",
confirmDetails: "Confirm Details",
printAlert: "Print (Alert)",
printPDF: "Print (PDF)",
showPaymentQR: "Show Payment QR",
checkPayment: "Check Payment",
confirmAndEmail: "Confirm & Email",
cancelTicket: "Cancel Ticket",
darkMode: "Dark Mode",
lightMode: "Light Mode",
logout: "Logout",
send: "Send",
enterMessage: "Enter your message...",
phoneLabel: "Phone Number:",
emailLabel: "Email:",
museumSelectText: "First, please select a museum:",
serviceSelectText: "Great! Now, what type of service would you like
today?",
dateSelectText: "Please select a date for your visit (April 5th -
7th available):", // Matched prompt above
personsSelectText: "How many persons will be attending?",
scanPayText: "Scan to Pay via UPI (Amount: ₹{amount}):",
validationQrText: "Ticket Validation QR:",
referenceLabel: "Reference Number:",
noTimeSlots: "No available time slots for {date}. Please try
another date." // Added message
},
hi: { // NOTE: Hindi translations might need updating for date ranges
if specific dates were mentioned
welcome: "म्यूजियम चैटबॉट में आपका स्वागत है",
helloMessage: "नमस्ते! मैं आपका गाइड हूँ। आज मैं आपकी कैसे मदद कर
सकता हूँ?",
userDetailsPrompt: "बढ़िया! बुकिंग के लिए, कृपया अपना फोन नंबर और
ईमेल प्रदान करें।",
selectMuseumPrompt: "ठीक है, कृपया एक म्यूजियम चुनें जिसे आप देखना
चाहते हैं।",
selectServicePrompt: "आपने {museum} को चुना है। अब, आप किस प्रकार
की सेवा चाहते हैं?",
selectDatePrompt: "कृपया अपनी यात्रा के लिए एक तिथि चुनें (5 अप्रैल
- 7 अप्रैल उपलब्ध):",
selectTimePrompt: "आपने {service} को चुना है। {date} के लिए उपलब्ध
स्लॉट:",
invalidPhone: "अमान्य फोन नंबर। कृपया 10 अंकों का नंबर दर्ज करें।",
invalidEmail: "अमान्य ईमेल पता।",
selectMuseumError: "कृपया विकल्पों में से एक म्यूजियम चुनें।",
selectServiceError: "कृपया एक सेवा चुनें: गाइडेड टूर, इवेंट बुकिंग,
या सामान्य पूछताछ।",
alreadySelected: "लगता है आपने म्यूजियम और सेवा चुन ली है। चलिए
तारीख और समय चुनें, या आप कुछ और पूछ सकते हैं।",
detailsSaved: "धन्यवाद! आपके विवरण सहेज लिए गए हैं।",
noTickets: "क्षमा करें, इस स्लॉट के लिए केवल {tickets} टिकट उपलब्ध
हैं। कृपया {tickets} या उससे कम व्यक्ति चुनें।",
noTicketsError: "चुने गए समय स्लॉट के लिए केवल {tickets} टिकट
उपलब्ध हैं।",
ticketDetails: "म्यूजियम: {museum}\n सेवा: {service}\n दिनांक:
{date}\n समय: {time}\n व्यक्ति: {persons}\n कुल लागत: ₹{cost}\n फोन: {phone}\n ईमेल:
{email}",
noPayment: "इस सेवा के लिए कोई भुगतान आवश्यक नहीं है।",
noPaymentReady: "कोई भुगतान आवश्यक नहीं। टिकट कन्फर्म करने के लिए
तैयार है!",
noPaymentMessage: "यह सेवा निःशुल्क है। आप अब अपना टिकट प्रिंट या
कन्फर्म कर सकते हैं।",
scanUpi: "₹{cost} का भुगतान करने के लिए नीचे दिए गए UPI QR कोड को
स्कैन करें। भुगतान के बाद, पुष्टि करने के लिए 'भुगतान जांचें' पर क्लिक करें।",
paymentVerified: "भुगतान सफलतापूर्वक सत्यापित हो गया!",
paymentNotConfirmed: "भुगतान की पुष्टि नहीं हुई। कृपया भुगतान पूरा
करें और फिर से कोशिश करें।",
paymentNotConfirmedMessage: "भुगतान की पुष्टि नहीं हुई। कृपया QR
कोड स्कैन करें, भुगतान पूरा करें, फिर 'भुगतान जांचें' पर दोबारा क्लिक करें।",
paymentVerifyPrompt: "कृपया टिकट कन्फर्म करने से पहले भुगतान
सत्यापित करें।",
ticketConfirmed: "आपका टिकट कन्फर्म हो गया है! हमने विवरण {email}
पर भेज दिए हैं। अपनी यात्रा का आनंद लें!",
ticketConfirmedMessage: "टिकट कन्फर्म! विवरण आपके ईमेल पर भेजे
गए।",
emailFailed: "टिकट कन्फर्म हो गया, लेकिन पुष्टि ईमेल भेजने में विफल
रहा। कृपया अपना ईमेल पता जांचें या सहायता से संपर्क करें। आपका बुकिंग संदर्भ
{referenceNumber} है।",
emailFailedMessage: "टिकट स्थानीय रूप से कन्फर्म, लेकिन ईमेल भेजना
विफल। संदर्भ: {referenceNumber}",
timeAndCost: "चयनित: {time} को {date}\n लागत: ₹{cost} प्रति
व्यक्ति",
personsPrompt: "कितने व्यक्ति? (अधिकतम 5, या {ticketsLeft} यदि कम
उपलब्ध हों)",
timeSlotsPrompt: "{date} के लिए उपलब्ध समय स्लॉट:",
previousTicketsHeader: "आपके पहले से बुक किए गए टिकट",
noPreviousTickets: "कोई पिछले टिकट नहीं मिले।",
ticketCancelled: "आपका टिकट (संदर्भ: {referenceNumber}) सफलतापूर्वक
रद्द कर दिया गया है।",
ticketCancelledMessage: "टिकट संदर्भ: {referenceNumber} रद्द! चाहें
तो नया बुक करें।",
viewTicketHistory: "इतिहास देखें",
hideTicketHistory: "इतिहास छिपाएं",
personDetailsTitle: "व्यक्ति {number} विवरण",
enterName: "नाम दर्ज करें",
enterAge: "आयु दर्ज करें",
selectGender: "लिंग चुनें",
male: "पुरुष",
female: "महिला",
other: "अन्य",
attendees: "उपस्थितगण:",
lookupBooking: "बुकिंग देखें",
lookupPromptType: "किससे देखें: 1) संदर्भ संख्या या 2) फ़ोन नंबर?",
lookupPromptRef: "अपना संदर्भ संख्या दर्ज करें:",
lookupPromptPhone: "अपना फ़ोन नंबर दर्ज करें (10 अंक):",
bookingNotFound: "बुकिंग नहीं मिली। कृपया अपने विवरण जांचें।",
bookingsFound: "आपकी बुकिंग:\n{list}\देखने के लिए बुकिंग का नंबर
दर्ज करें:",
bookingDetailsTitle: "बुकिंग विवरण",
referenceNumber: "संदर्भ संख्या: {number}",
validationQR: "टिकट सत्यापन QR कोड",
submit: "प्रस्तुत करें",
continue: "जारी रखें",
confirmDetails: "विवरण पुष्टि करें",
printAlert: "प्रिंट (अलर्ट)",
printPDF: "प्रिंट (PDF)",
showPaymentQR: "पेमेंट QR दिखाएं",
checkPayment: "भुगतान जांचें",
confirmAndEmail: "पुष्टि करें और ईमेल करें",
cancelTicket: "टिकट रद्द करें",
darkMode: "डार्क मोड",
lightMode: "लाइट मोड",
logout: "लॉग आउट",
send: "भेजें",
enterMessage: "अपना संदेश दर्ज करें...",
phoneLabel: "फोन नंबर:",
emailLabel: "ईमेल:",
museumSelectText: "पहले, कृपया एक म्यूजियम चुनें:",
serviceSelectText: "बढ़िया! अब, आप किस प्रकार की सेवा चाहेंगे?",
dateSelectText: "कृपया अपनी यात्रा के लिए एक तिथि चुनें (5 अप्रैल -
7 अप्रैल उपलब्ध):",
personsSelectText: "कितने व्यक्ति शामिल होंगे?",
scanPayText: "UPI द्वारा भुगतान करने के लिए स्कैन करें (राशि: ₹
{amount}):",
validationQrText: "टिकट सत्यापन QR:",
referenceLabel: "संदर्भ संख्या:",
noTimeSlots: "{date} के लिए कोई उपलब्ध समय स्लॉट नहीं है। कृपया कोई
और तारीख चुनें।" // Added message
},
ta: { // NOTE: Tamil translations might need updating for date ranges
if specific dates were mentioned
welcome: "அருங்காட்சியக சாட்பாட்டிற்கு வரவேற்கிறோம்",
helloMessage: "வணக்கம்! நான் உங்கள் வழிகாட்டி. இன்று உங்களுக்கு
எப்படி உதவ முடியும்?",
userDetailsPrompt: "அருமை! முன்பதிவு செய்ய, உங்கள் தொலைபேசி எண்
மற்றும் மின்னஞ்சலைக் கொடுங்கள்.",
selectMuseumPrompt: "சரி, நீங்கள் பார்வையிட விரும்பும் ஒரு
அருங்காட்சியகத்தைத் தேர்ந்தெடுக்கவும்.",
selectServicePrompt: "நீங்கள் {museum} ஐத் தேர்ந்தெடுத்துள்ளீர்கள்.
இப்போது, எந்த வகையான சேவையை விரும்புகிறீர்கள்?",
selectDatePrompt: "உங்கள் பயணத்திற்கான தேதியைத் தேர்ந்தெடுக்கவும்
(ஏப்ரல் 5 - 7 வரை உள்ளது):",
selectTimePrompt: "நீங்கள் {service} ஐத் தேர்ந்தெடுத்துள்ளீர்கள்.
{date} க்கான கிடைக்கும் நேரங்கள்:",
invalidPhone: "தவறான தொலைபேசி எண். 10 இலக்க எண்ணை உள்ளிடவும்.",
invalidEmail: "தவறான மின்னஞ்சல் முகவரி.",
selectMuseumError: "விருப்பங்களிலிருந்து ஒரு அருங்காட்சியகத்தைத்
தேர்ந்தெடுக்கவும்.",
selectServiceError: "ஒரு சேவையைத் தேர்ந்தெடுக்கவும்: வழிகாட்டப்பட்ட
சுற்றுலா, நிகழ்வு முன்பதிவு, அல்லது பொது விசாரணை.",
alreadySelected: "நீங்கள் அருங்காட்சியகம் மற்றும் சேவையைத்
தேர்ந்தெடுத்து விட்டீர்கள் போல. தேதி மற்றும் நேரத்தைத் தேர்வு செய்வோம், அல்லது வேறு
ஏதாவது கேட்கலாம்.",
detailsSaved: "நன்றி! உங்கள் விவரங்கள் சேமிக்கப்பட்டுள்ளன.",
noTickets: "மன்னிக்கவும், இந்த நேரத்திற்கு {tickets} டிக்கெட்(கள்)
மட்டுமே உள்ளன. தயவுசெய்து {tickets} அல்லது அதற்கும் குறைவான நபர்களைத்
தேர்ந்தெடுக்கவும்.",
noTicketsError: "தேர்ந்தெடுக்கப்பட்ட நேரத்திற்கு {tickets}
டிக்கெட்(கள்) மட்டுமே உள்ளன.",
ticketDetails: "அருங்காட்சியகம்: {museum}\n சேவை: {service}\n தேதி:
{date}\n நேரம்: {time}\n நபர்கள்: {persons}\n மொத்த செலவு: ₹{cost}\n தொலைபேசி:
{phone}\n மின்னஞ்சல்: {email}",
noPayment: "இந்த சேவைக்கு கட்டணம் இல்லை.",
noPaymentReady: "கட்டணம் தேவையில்லை. டிக்கெட் உறுதிப்படுத்த தயாராக
உள்ளது!",
noPaymentMessage: "இந்த சேவை இலவசம். இப்போது உங்கள் டிக்கெட்டை
அச்சிடலாம் அல்லது உறுதிப்படுத்தலாம்.",
scanUpi: "₹{cost} செலுத்த கீழே உள்ள UPI QR குறியீட்டை ஸ்கேன்
செய்யவும். பணம் செலுத்திய பிறகு, சரிபார்க்க 'பணம் செலுத்தியதை சரிபார்க்கவும்'
என்பதைக் கிளிக் செய்யவும்.",
paymentVerified: "பணம் செலுத்துதல் வெற்றிகரமாக
சரிபார்க்கப்பட்டது!",
paymentNotConfirmed: "பணம் செலுத்துதல் உறுதிப்படுத்தப்படவில்லை.
தயவுசெய்து பணம் செலுத்திவிட்டு மீண்டும் முயற்சிக்கவும்.",
paymentNotConfirmedMessage: "பணம் செலுத்துதல்
உறுதிப்படுத்தப்படவில்லை. தயவுசெய்து QR குறியீட்டை ஸ்கேன் செய்து, பணம் செலுத்திய
பிறகு, 'பணம் செலுத்தியதை சரிபார்க்கவும்' என்பதை மீண்டும் கிளிக் செய்யவும்.",
paymentVerifyPrompt: "டிக்கெட்டை உறுதிப்படுத்துவதற்கு முன் பணம்
செலுத்துதலை சரிபார்க்கவும்.",
ticketConfirmed: "உங்கள் டிக்கெட் உறுதி செய்யப்பட்டது! விவரங்களை
{email} க்கு அனுப்பியுள்ளோம். உங்கள் பயணத்தை அனுபவிக்கவும்!",
ticketConfirmedMessage: "டிக்கெட் உறுதி செய்யப்பட்டது! விவரங்கள்
உங்கள் மின்னஞ்சலுக்கு அனுப்பப்பட்டன.",
emailFailed: "டிக்கெட் உறுதி செய்யப்பட்டது, ஆனால் உறுதிப்படுத்தல்
மின்னஞ்சல் அனுப்ப முடியவில்லை. உங்கள் மின்னஞ்சல் முகவரியைச் சரிபார்க்கவும் அல்லது
ஆதரவைத் தொடர்பு கொள்ளவும். உங்கள் முன்பதிவு குறிப்பு எண் {referenceNumber}.",
emailFailedMessage: "டிக்கெட் உள்ளூரில் உறுதிசெய்யப்பட்டது, ஆனால்
மின்னஞ்சல் அனுப்புதல் தோல்வியடைந்தது. குறிப்பு: {referenceNumber}",
timeAndCost: "தேர்ந்தெடுக்கப்பட்டது: {time} அன்று {date}\n செலவு: ₹
{cost} ஒரு நபருக்கு",
personsPrompt: "எத்தனை நபர்கள்? (அதிகபட்சம் 5, அல்லது {ticketsLeft}
குறைவாக இருந்தால்)",
timeSlotsPrompt: "{date} க்கான கிடைக்கும் நேரங்கள்:",
previousTicketsHeader: "உங்கள் முந்தைய முன்பதிவு டிக்கெட்டுகள்",
noPreviousTickets: "முந்தைய டிக்கெட்டுகள் எதுவும் இல்லை.",
ticketCancelled: "உங்கள் டிக்கெட் (குறிப்பு: {referenceNumber})
வெற்றிகரமாக ரத்து செய்யப்பட்டது.",
ticketCancelledMessage: "டிக்கெட் குறிப்பு: {referenceNumber} ரத்து
செய்யப்பட்டது! தேவைப்பட்டால் புதியதை முன்பதிவு செய்யுங்கள்.",
viewTicketHistory: "வரலாறு பார்",
hideTicketHistory: "வரலாறு மறை",
personDetailsTitle: "நபர் {number} விவரங்கள்",
enterName: "பெயரை உள்ளிடவும்",
enterAge: "வயதை உள்ளிடவும்",
selectGender: "பாலினத்தைத் தேர்ந்தெடுக்கவும்",
male: "ஆண்",
female: "பெண்",
other: "மற்றவை",
attendees: "பங்கேற்பாளர்கள்:",
lookupBooking: "முன்பதிவைப் பார்",
lookupPromptType: "எதன் மூலம் பார்ப்பது: 1) குறிப்பு எண் அல்லது 2)
தொலைபேசி எண்?",
lookupPromptRef: "உங்கள் குறிப்பு எண்ணை உள்ளிடவும்:",
lookupPromptPhone: "உங்கள் தொலைபேசி எண்ணை உள்ளிடவும் (10
இலக்கங்கள்):",
bookingNotFound: "முன்பதிவு கண்டுபிடிக்கப்படவில்லை. உங்கள்
விவரங்களைச் சரிபார்க்கவும்.",
bookingsFound: "உங்கள் முன்பதிவுகள்:\n{list}\பார்க்க விரும்பும்
முன்பதிவின் எண்ணை உள்ளிடவும்:",
bookingDetailsTitle: "முன்பதிவு விவரங்கள்",
referenceNumber: "குறிப்பு எண்: {number}",
validationQR: "டிக்கெட் சரிபார்ப்பு QR குறியீடு",
submit: "சமர்ப்பி",
continue: "தொடர்க",
confirmDetails: "விவரங்களை உறுதிப்படுத்து",
printAlert: "அச்சிடு (Alert)",
printPDF: "அச்சிடு (PDF)",
showPaymentQR: "பணம் செலுத்தும் QR காட்டு",
checkPayment: "பணம் செலுத்தியதை சரிபார்",
confirmAndEmail: "உறுதி செய்து மின்னஞ்சல் அனுப்பு",
cancelTicket: "டிக்கெட்டை ரத்துசெய்",
darkMode: "இருண்ட பயன்முறை",
lightMode: "ஒளி பயன்முறை",
logout: "வெளியேறு",
send: "அனுப்பு",
enterMessage: "உங்கள் செய்தியை உள்ளிடவும்...",
phoneLabel: "தொலைபேசி எண்:",
emailLabel: "மின்னஞ்சல்:",
museumSelectText: "முதலில், ஒரு அருங்காட்சியகத்தைத்
தேர்ந்தெடுக்கவும்:",
serviceSelectText: "அருமை! இப்போது, எந்த வகையான சேவையை
விரும்புகிறீர்கள்?",
dateSelectText: "உங்கள் வருகைக்கான தேதியைத் தேர்ந்தெடுக்கவும்
(ஏப்ரல் 5 - 7 வரை உள்ளது):",
personsSelectText: "எத்தனை பேர் கலந்து கொள்வார்கள்?",
scanPayText: "UPI மூலம் செலுத்த ஸ்கேன் செய்யவும் (தொகை: ₹
{amount}):",
validationQrText: "டிக்கெட் சரிபார்ப்பு QR:",
referenceLabel: "குறிப்பு எண்:",
noTimeSlots: "{date} அன்று கிடைக்கும் நேரங்கள் இல்லை. வேறு தேதியை
முயற்சிக்கவும்." // Added message
}
};

let selectedService = "";


let selectedTime = null; // Store time object { time, price, ticketsLeft,
date }
let selectedMuseum = "";
let userPhone = "";
let userEmail = "";
let paymentVerifiedGlobally = false; // Use a more specific name to avoid
conflict
let upiQrData = "";
let numberOfPersons = 1;
let totalCost = 0;
let chatHistory = [];
let currentLanguage = 'en';
let bookedTickets = [];
let currentTicket = null;
let isHistoryVisible = false;
let personDetails = [];
let referenceNumber = "";
let validationQrData = "";
let selectedDate = ""; // Keep track of the selected date separately

// Provided EmailJS IDs


const EMAILJS_SERVICE_ID = "service_o0yykpc";
const EMAILJS_TEMPLATE_ID = "template_2wfcd7m";

// UPI Base URL (Replace with your actual UPI details if needed)
const upiBaseUrl = "upi://pay?pa=punithchowdary11@oksbi&pn=punith
%20chowdary&aid=uGICAgMDQlZymZQ";

// Service Data (Example - structure is important)


const serviceData = {
"natural-history": {
name: "Museum of Natural History",
name_hi: "प्राकृतिक इतिहास संग्रहालय",
name_ta: "இயற்கை வரலாற்று அருங்காட்சியகம்",
services: {
"guided-tour": {
name: "Guided Tour",
name_hi: "गाइडेड टूर",
name_ta: "வழிகாட்டப்பட்ட சுற்றுலா",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "10:00 AM", price: 25,
ticketsLeft: 8 }, { time: "1:00 PM", price: 25, ticketsLeft: 5 } ],
"2025-04-06": [ { time: "11:00 AM", price: 25,
ticketsLeft: 10 }, { time: "3:00 PM", price: 25, ticketsLeft: 7 } ],
"2025-04-07": [ { time: "9:00 AM", price: 20,
ticketsLeft: 6 }, { time: "11:00 AM", price: 20, ticketsLeft: 4 } ],
// Removed 2025-04-08 (as per request only keep 5-7)
}
},
"event-booking": {
name: "Event Booking",
name_hi: "इवेंट बुकिंग",
name_ta: "நிகழ்வு முன்பதிவு",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "6:00 PM", price: 60,
ticketsLeft: 8 }, { time: "7:30 PM", price: 85, ticketsLeft: 3 } ],
"2025-04-06": [ { time: "5:30 PM", price: 55,
ticketsLeft: 7 }, { time: "7:00 PM", price: 80, ticketsLeft: 4 } ],
"2025-04-07": [ { time: "6:30 PM", price: 60,
ticketsLeft: 6 }, { time: "8:00 PM", price: 85, ticketsLeft: 2 } ],
// Removed 2025-04-08
}
},
"general-inquiry": {
name: "General Inquiry",
name_hi: "सामान्य पूछताछ",
name_ta: "பொது விசாரணை",
times: { // Even free services need a structure for
date/time selection consistency
// Removed 2025-04-04
"2025-04-05": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
"2025-04-06": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
"2025-04-07": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
// Removed 2025-04-08
}
}
}
},
"art": {
name: "Art Museum",
name_hi: "कला संग्रहालय",
name_ta: "கலை அருங்காட்சியகம்",
services: {
"guided-tour": {
name: "Guided Tour",
name_hi: "गाइडेड टूर",
name_ta: "வழிகாட்டப்பட்ட சுற்றுலா",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "11:00 AM", price: 15,
ticketsLeft: 5 }, { time: "2:00 PM", price: 15, ticketsLeft: 3 } ],
"2025-04-06": [ { time: "10:00 AM", price: 15,
ticketsLeft: 6 }, { time: "12:00 PM", price: 15, ticketsLeft: 4 } ],
"2025-04-07": [ { time: "11:00 AM", price: 15,
ticketsLeft: 5 }, { time: "1:00 PM", price: 15, ticketsLeft: 3 } ],
// Removed 2025-04-08
}
},
"event-booking": {
name: "Event Booking",
name_hi: "इवेंट बुकिंग",
name_ta: "நிகழ்வு முன்பதிவு",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "7:00 PM", price: 70,
ticketsLeft: 6 }, { time: "8:30 PM", price: 95, ticketsLeft: 2 } ],
"2025-04-06": [ { time: "6:30 PM", price: 65,
ticketsLeft: 7 }, { time: "8:00 PM", price: 90, ticketsLeft: 4 } ],
"2025-04-07": [ { time: "7:00 PM", price: 70,
ticketsLeft: 5 }, { time: "8:30 PM", price: 95, ticketsLeft: 2 } ],
// Removed 2025-04-08
}
},
"general-inquiry": {
name: "General Inquiry",
name_hi: "सामान्य पूछताछ",
name_ta: "பொது விசாரணை",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
"2025-04-06": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
"2025-04-07": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
// Removed 2025-04-08
}
}
}
},
"science": {
name: "Science Museum",
name_hi: "विज्ञान संग्रहालय",
name_ta: "அறிவியல் அருங்காட்சியகம்",
services: {
"guided-tour": {
name: "Guided Tour",
name_hi: "गाइडेड टूर",
name_ta: "வழிகாட்டப்பட்ட சுற்றுலா",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "09:00 AM", price: 18,
ticketsLeft: 4 }, { time: "11:00 AM", price: 18, ticketsLeft: 2 } ],
"2025-04-06": [ { time: "10:00 AM", price: 18,
ticketsLeft: 7 }, { time: "12:00 PM", price: 18, ticketsLeft: 5 } ],
"2025-04-07": [ { time: "09:00 AM", price: 18,
ticketsLeft: 6 }, { time: "11:00 AM", price: 18, ticketsLeft: 4 } ],
// Removed 2025-04-08
}
},
"event-booking": {
name: "Event Booking",
name_hi: "इवेंट बुकिंग",
name_ta: "நிகழ்வு முன்பதிவு",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "5:00 PM", price: 55,
ticketsLeft: 10 }, { time: "6:30 PM", price: 80, ticketsLeft: 5 } ],
"2025-04-06": [ { time: "4:30 PM", price: 50,
ticketsLeft: 8 }, { time: "6:00 PM", price: 75, ticketsLeft: 4 } ],
"2025-04-07": [ { time: "5:00 PM", price: 55,
ticketsLeft: 7 }, { time: "6:30 PM", price: 80, ticketsLeft: 3 } ],
// Removed 2025-04-08
}
},
"general-inquiry": {
name: "General Inquiry",
name_hi: "सामान्य पूछताछ",
name_ta: "பொது விசாரணை",
times: {
// Removed 2025-04-04
"2025-04-05": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
"2025-04-06": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
"2025-04-07": [ { time: "Anytime", price: 0,
ticketsLeft: 100 } ],
// Removed 2025-04-08
}
}
}
}
};

// DOM Elements (Get references - no changes needed here)


const chatContainer = document.getElementById('chat-container');
const userInput = document.getElementById('user-input');
const sendButton = document.getElementById('send-button');
const inputForm = document.getElementById('input-form');
const userDetailsForm = document.getElementById('user-details-form');
const userDetailsResponse = document.getElementById('user-details-
response');
const userDetailsResponseText = document.getElementById('user-details-
response-text');
const errorMessage = document.getElementById('error-message');
const errorMessageText = document.getElementById('error-message-text');
const museumSelection = document.getElementById('museum-selection');
const serviceSelection = document.getElementById('service-selection');
const dateSelection = document.getElementById('date-selection');
const datePicker = document.getElementById('date-picker');
const timeSelection = document.getElementById('time-selection');
const timeSlots = document.getElementById('time-slots');
const timeSelectionText = document.getElementById('time-selection-text');
const personsSelection = document.getElementById('persons-selection');
const personsCount = document.getElementById('persons-count');
const personDetailsContainer = document.getElementById('person-details-
container');
const personDetailsForms = document.getElementById('person-details-forms');
const timeAndCost = document.getElementById('time-and-cost');
const timeAndCostText = document.getElementById('time-and-cost-text');
const ticketSection = document.getElementById('ticket-section');
const ticketDetails = document.getElementById('ticket-details');
const ticketDetailsPdf = document.getElementById('ticket-details-pdf');
const ownerQrCodeDiv = document.getElementById('owner-qr-code');
const validationQrCodeDiv = document.getElementById('validation-qr-code');
const validationQrCodePdf = document.getElementById('validation-qr-code-
pdf');
const upiAmountSpan = document.getElementById('upi-amount');
const paymentStatus = document.getElementById('payment-status');
const confirmTicketButton = document.getElementById('confirm-ticket-
button');
const printAlertButton = document.getElementById('print-alert-button');
const printPdfButton = document.getElementById('print-pdf-button');
const confirmationMessage = document.getElementById('confirmation-
message');
const confirmationMessageText = document.getElementById('confirmation-
message-text');
const cancelTicketButton = document.getElementById('cancel-ticket-button');
const typingIndicator = document.getElementById('typing-indicator');
const logoutButton = document.getElementById('logout-button');
const darkModeToggle = document.getElementById('dark-mode-toggle');
const darkModeIcon = document.getElementById('dark-mode-icon');
const languageSelect = document.getElementById('language-select');
const viewHistoryButton = document.getElementById('view-history-button');
const previousTickets = document.getElementById('previous-tickets');
const previousTicketsList = document.getElementById('previous-tickets-
list');
const lookupButton = document.getElementById('lookup-button');
const referenceNumberDiv = document.getElementById('reference-number');
const referenceNumberValue = document.getElementById('reference-number-
value');
const referenceNumberPdf = document.getElementById('reference-number-pdf');
const darkModeText = document.getElementById('dark-mode-text');
const historyButtonText = document.getElementById('history-button-text');
const logoutButtonText = document.getElementById('logout-button-text');
const lookupButtonText = document.getElementById('lookup-button-text');
const sendButtonText = document.getElementById('send-button-text');
const phoneLabel = document.getElementById('phone-label');
const emailLabel = document.getElementById('email-label');
const submitDetailsButton = document.getElementById('submit-details-
button');
const museumSelectText = document.getElementById('museum-select-text');
const serviceSelectText = document.getElementById('service-select-text');
const dateSelectText = document.getElementById('date-select-text');
const personsSelectText = document.getElementById('persons-select-text');
const continueButton = document.getElementById('continue-button');
const confirmDetailsButton = document.getElementById('confirm-details-
button');
const referenceLabel = document.getElementById('reference-label');
const scanPayText = document.getElementById('scan-pay-text');
const validationQrText = document.getElementById('validation-qr-text');
const printAlertButtonText = document.getElementById('print-alert-button');
// Button element itself
const printPdfButtonText = document.getElementById('print-pdf-
button'); // Button element itself
const showQrButtonText = document.getElementById('show-qr-button'); //
Button element itself
const checkPaymentButtonText = document.getElementById('check-payment-
button'); // Button element itself
const confirmTicketButtonText = document.getElementById('confirm-ticket-
button'); // Button element itself
const cancelTicketText = document.getElementById('cancel-ticket-text');
const previousTicketsHeader = document.getElementById('previous-tickets-
header');

// --- Initialization ---

// Set minimum date for date picker


function setMinDate() {
const today = new Date(); // Get current date
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const todayStr = `${yyyy}-${mm}-${dd}`;

// Set default to April 5, 2025, for demo purposes, but min should be
today
const defaultDateStr = "2025-04-05";

// Determine the minimum selectable date


// If today is *after* the default start date, use today as min.
// Otherwise, use the default start date as min.
// For this demo, we'll force the min date to be the default start
date
const minDate = defaultDateStr;

datePicker.min = minDate; // Set minimum selectable date


datePicker.value = defaultDateStr; // Set default value
}

// Load data from localStorage on page load


document.addEventListener('DOMContentLoaded', () => {
setMinDate(); // Set date picker min/default

// Load Chat History


try {
const savedHistory = localStorage.getItem('chatHistory');
if (savedHistory) {
chatHistory = JSON.parse(savedHistory);
// Don't render history here, updateLanguage will do it
}
} catch (error) {
console.error("Failed to load chat history:", error);
chatHistory = [];
localStorage.removeItem('chatHistory'); // Clear corrupted data
}

// Load Booked Tickets


try {
const savedTickets = localStorage.getItem('bookedTickets');
if (savedTickets) {
bookedTickets = JSON.parse(savedTickets);
}
} catch (error) {
console.error("Failed to load booked tickets:", error);
bookedTickets = [];
localStorage.removeItem('bookedTickets'); // Clear corrupted data
}

// Load Theme Preference


const currentTheme = localStorage.getItem('theme') || 'light';
setTheme(currentTheme); // Apply theme

// Load Language Preference


const savedLanguage = localStorage.getItem('language') || 'en';
languageSelect.value = savedLanguage;
currentLanguage = savedLanguage;
updateLanguage(); // Apply language and render initial state
// Initial welcome message
appendMessage('receiver', translations[currentLanguage].helloMessage);
// Prompt for museum selection
setTimeout(() => {
appendMessage('receiver',
translations[currentLanguage].selectMuseumPrompt);
museumSelection.classList.remove('hidden');
}, 1000);
});

// --- UI Updates & Language ---

function setTheme(theme) {
if (theme === 'dark') {
document.body.classList.add('dark-mode');
darkModeIcon.innerHTML = `<path d="M12 16a4 4 0 0 0 0-8 4 4 0 0 0-
3.465 2H7a5 5 0 0 1 5 5v1.535A3.978 3.978 0 0 0 12 16ZM2 10a8 8 0 1 1 8 8 8 8 0 0
1-8-8Zm8-6a.75.75 0 0 0 0 1.5A1.5 1.5 0 0 1 11.5 7 .75.75 0 0 0 10 7.75 3 3 0 0 0 7
4.75.75.75 0 0 0 6.25 4H5a.75.75 0 0 0 0 1.5h.325A4.5 4.5 0 0 1 10 9.5a4.5 4.5 0 0
1 4.5 4.326V14a.75.75 0 0 0 1.5 0v-.674A6 6 0 0 0 10 4Z"/>`; // Moon icon
darkModeText.textContent = translations[currentLanguage]?.lightMode
|| "Light Mode"; // Show option to switch to Light
} else {
document.body.classList.remove('dark-mode');
darkModeIcon.innerHTML = `<path fill-rule="evenodd" d="M10 2a.75.75
0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2Zm0 12a.75.75 0 0
1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 14Zm-5.657-2.843a.75.75 0 0 1
0 1.06l-1.061 1.061a.75.75 0 1 1-1.06-1.06l1.06-1.061a.75.75 0 0 1 1.061 0Zm12.728
0a.75.75 0 0 1 1.06 0l1.061 1.061a.75.75 0 1 1-1.06 1.06l-1.061-1.06a.75.75 0 0 1
0-1.061ZM10 5a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z" clip-rule="evenodd" />`; // Sun icon
darkModeText.textContent = translations[currentLanguage]?.darkMode
|| "Dark Mode"; // Show option to switch to Dark
}
}

function updateLanguage() {
const lang = currentLanguage;
const tr = translations[lang];

if (!tr) {
console.error(`Translations not found for language: ${lang}`);
return; // Exit if translations are missing
}

// Update static text elements


document.querySelector('h1').textContent = tr.welcome;
userInput.placeholder = tr.enterMessage;
phoneLabel.textContent = tr.phoneLabel;
emailLabel.textContent = tr.emailLabel;
submitDetailsButton.textContent = tr.submit;
museumSelectText.textContent = tr.museumSelectText;
serviceSelectText.textContent = tr.serviceSelectText;
dateSelectText.textContent = tr.dateSelectText;
personsSelectText.textContent = tr.personsPrompt.split('(')[0]; //
Update label text dynamically later
continueButton.textContent = tr.continue;
confirmDetailsButton.textContent = tr.confirmDetails;
referenceLabel.textContent = tr.referenceLabel;
scanPayText.textContent = tr.scanPayText.replace('{amount}',
totalCost.toFixed(2)); // Update amount if visible
validationQrText.textContent = tr.validationQrText;
printAlertButtonText.childNodes[printAlertButtonText.childNodes.length
- 1].nodeValue = ` ${tr.printAlert}`; // Update button text node
printPdfButtonText.childNodes[printPdfButtonText.childNodes.length -
1].nodeValue = ` ${tr.printPDF}`;
showQrButtonText.childNodes[showQrButtonText.childNodes.length -
1].nodeValue = ` ${tr.showPaymentQR}`;

checkPaymentButtonText.childNodes[checkPaymentButtonText.childNodes.length -
1].nodeValue = ` ${tr.checkPayment}`;

confirmTicketButtonText.childNodes[confirmTicketButtonText.childNodes.length -
1].nodeValue = ` ${tr.confirmAndEmail}`;
cancelTicketText.textContent = tr.cancelTicket;
previousTicketsHeader.textContent = tr.previousTicketsHeader;
historyButtonText.textContent = isHistoryVisible ? tr.hideTicketHistory
: tr.viewTicketHistory;
logoutButtonText.textContent = tr.logout;
lookupButtonText.textContent = tr.lookupBooking;
sendButtonText.textContent = tr.send;

// Update theme toggle button text based on current theme


const isDarkMode = document.body.classList.contains('dark-mode');
darkModeText.textContent = isDarkMode ? tr.lightMode : tr.darkMode;

// Update museum/service names in buttons


document.querySelectorAll('.museum-button .museum-name').forEach((span,
index) => {
const museumKey = ['natural-history', 'art', 'science'][index];
span.textContent = serviceData[museumKey]?.[`name_${lang}`] ||
serviceData[museumKey]?.name;
});
document.querySelectorAll('.service-button .service-
name').forEach((span, index) => {
const serviceKey = ['guided-tour', 'event-booking', 'general-
inquiry'][index];
// Need selectedMuseum to get the correct service name translation
if (selectedMuseum &&
serviceData[selectedMuseum]?.services[serviceKey]) {
span.textContent =
serviceData[selectedMuseum].services[serviceKey]?.[`name_${lang}`] ||
serviceData[selectedMuseum].services[serviceKey]?.name;
} else {
// Fallback if museum not selected yet (might happen on
initial load)
// Attempt a basic translation lookup
let serviceNameFallback = serviceKey;
if(serviceKey === 'guided-tour') serviceNameFallback =
tr.guidedTour || serviceKey;
if(serviceKey === 'event-booking') serviceNameFallback =
tr.eventBooking || serviceKey;
if(serviceKey === 'general-inquiry') serviceNameFallback =
tr.generalInquiry || serviceKey;
span.textContent = serviceNameFallback;
}
});
// Re-render chat history with new language
chatContainer.innerHTML = ''; // Clear existing chat
chatHistory.forEach(msg => {
let messageText = msg.message;
// Basic translation attempt (won't handle placeholders complexly)
Object.keys(translations.en).forEach(key => {
if (typeof translations.en[key] === 'string' &&
translations.en[key] === msg.message && tr[key] && !messageText.includes('{')) {
messageText = tr[key];
}
});
appendMessageToDOM(msg.sender, messageText);
});

// Update dynamic elements like prompts if they are visible


if (!personsSelection.classList.contains('hidden') && selectedTime) {
personsSelectText.textContent = tr.personsPrompt
.replace('{ticketsLeft}', selectedTime.ticketsLeft);
}
if (!timeSelection.classList.contains('hidden') && selectedDate) {
timeSelectionText.textContent =
tr.timeSlotsPrompt.replace('{date}', formatDate(selectedDate));
}
if (!serviceSelection.classList.contains('hidden') && selectedMuseum)
{
const museumName = serviceData[selectedMuseum]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.name;
serviceSelectText.textContent =
tr.selectServicePrompt.replace('{museum}', museumName);
}

// Update ticket history if visible


if (isHistoryVisible) {
displayPreviousTickets(); // Re-render with new language
}
}

// --- Event Listeners ---

// Dark Mode Toggle


darkModeToggle.addEventListener('click', () => {
const isDarkMode = document.body.classList.toggle('dark-mode');
const newTheme = isDarkMode ? 'dark' : 'light';
localStorage.setItem('theme', newTheme);
setTheme(newTheme); // Update UI appearance
updateLanguage(); // Update text elements related to theme
});

// Language Selection
languageSelect.addEventListener('change', (e) => {
currentLanguage = e.target.value;
localStorage.setItem('language', currentLanguage);
updateLanguage(); // Update all UI text and re-render chat
});

// Logout Button
logoutButton.addEventListener('click', () => {
console.log("Logout initiated");
localStorage.removeItem('chatHistory');
localStorage.removeItem('bookedTickets');
alert("You have been logged out (simulation).");
resetChatState(true); // Full reset including user details
chatHistory = [];
bookedTickets = [];
chatContainer.innerHTML = '';
appendMessage('receiver', translations[currentLanguage].helloMessage);
setTimeout(() => {
appendMessage('receiver',
translations[currentLanguage].selectMuseumPrompt);
museumSelection.classList.remove('hidden');
}, 1000);
});

// Lookup Booking Button


lookupButton.addEventListener('click', lookupBooking);

// View History Button


viewHistoryButton.addEventListener('click', () => {
isHistoryVisible = !isHistoryVisible;
const lang = currentLanguage;
const tr = translations[lang];
if (isHistoryVisible) {
previousTickets.classList.remove('hidden');
historyButtonText.textContent = tr.hideTicketHistory;
displayPreviousTickets();
} else {
previousTickets.classList.add('hidden');
historyButtonText.textContent = tr.viewTicketHistory;
}
chatContainer.scrollTop = chatContainer.scrollHeight; // Scroll down
});

// Date Picker Change


datePicker.addEventListener('change', () => {
selectedDate = datePicker.value; // Update selected date
updateAvailableTimes();
});

// Input Form Submission


inputForm.addEventListener('submit', (event) => {
event.preventDefault();
sendMessage();
});

// --- Core Chat Logic ---

function resetChatState(fullReset = false) {


selectedService = "";
selectedTime = null;
selectedMuseum = "";
paymentVerifiedGlobally = false;
upiQrData = "";
numberOfPersons = 1;
totalCost = 0;
currentTicket = null;
personDetails = [];
referenceNumber = "";
validationQrData = "";
selectedDate = ""; // Reset selected date

if(fullReset){ // Clear user details only on full logout reset


userPhone = "";
userEmail = "";
}

// Hide all interactive sections except the initial ones potentially


userDetailsForm.classList.add('hidden');
userDetailsResponse.classList.add('hidden');
museumSelection.classList.add('hidden');
serviceSelection.classList.add('hidden');
dateSelection.classList.add('hidden');
timeSelection.classList.add('hidden');
personsSelection.classList.add('hidden');
personDetailsContainer.classList.add('hidden');
timeAndCost.classList.add('hidden');
ticketSection.classList.add('hidden');
confirmationMessage.classList.add('hidden');
errorMessage.classList.add('hidden');
ownerQrCodeDiv.classList.add('hidden');
validationQrCodeDiv.classList.add('hidden');
referenceNumberDiv.classList.add('hidden');

// Reset date picker to default


setMinDate();
}

function sendMessage() {
const message = userInput.value.trim();
if (message === '') return;

appendMessage('sender', message); // Add user message to chat and


history
userInput.value = ''; // Clear input
userInput.disabled = true;
sendButton.disabled = true;
showTypingIndicator();

// Simulate bot thinking and processing


setTimeout(() => {
hideTypingIndicator();
processMessage(message); // Process the user's message
userInput.disabled = false;
sendButton.disabled = false;
userInput.focus(); // Set focus back to input
}, 800); // Shorter delay
}

// Appends message to DOM only


function appendMessageToDOM(sender, message) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${sender}`;
const messageContentDiv = document.createElement('div');
messageContentDiv.className = 'message-content';
// Basic check for structured data to prevent [object Object]
if (typeof message === 'object' && message !== null) {
messageContentDiv.textContent = JSON.stringify(message, null,
2); // Display object nicely
} else {
messageContentDiv.textContent = message;
}
messageDiv.appendChild(messageContentDiv);
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight; // Auto-scroll
}

// Appends message to DOM and saves to history/localStorage


function appendMessage(sender, message) {
appendMessageToDOM(sender, message); // Add to UI

// Save to history array


chatHistory.push({ sender, message });

// Save history to localStorage


try {
localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
} catch (error) {
console.error("Failed to save chat history:", error);
// Maybe prune history if it's too large?
if (error.name === 'QuotaExceededError') {
console.warn("LocalStorage quota exceeded. Pruning chat
history.");
chatHistory.splice(0, 50); // Remove oldest 50 messages
try {
localStorage.setItem('chatHistory',
JSON.stringify(chatHistory));
} catch (e) {
console.error("Still failed to save history after
pruning:", e);
}
}
}
}

function showTypingIndicator() {
typingIndicator.classList.remove('hidden');
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function hideTypingIndicator() {
typingIndicator.classList.add('hidden');
}

// Main message processing logic


function processMessage(message) {
const lowerCaseMessage = message.toLowerCase();
const lang = currentLanguage;
const tr = translations[lang];
hideErrorMessage(); // Hide any previous errors

// Handle "cancel" or "reset" intent anytime


if (lowerCaseMessage === 'cancel' || lowerCaseMessage === 'reset' ||
lowerCaseMessage === 'start over') {
appendMessage('receiver', "Okay, let's start over. Please select a
museum.");
resetChatState(); // Partial reset (keep user details if logged in)
museumSelection.classList.remove('hidden');
return;
}

// --- State-based processing ---

// 1. Expecting User Details?


if (!userDetailsForm.classList.contains('hidden')) {
appendMessage('receiver', "Please submit your details using the
form above.");
return; // Don't process text input while form is active
}

// 2. Expecting Museum Selection?


if (selectedMuseum === "" &&
museumSelection.classList.contains('hidden') === false) {
if (lowerCaseMessage.includes('natural history') ||
lowerCaseMessage.includes('history')) { selectMuseum('natural-history'); }
else if (lowerCaseMessage.includes('art'))
{ selectMuseum('art'); }
else if (lowerCaseMessage.includes('science'))
{ selectMuseum('science');}
else { appendMessage('receiver', tr.selectMuseumError); }
return;
}

// 3. Expecting Service Selection?


if (selectedMuseum !== "" && selectedService === "" &&
serviceSelection.classList.contains('hidden') === false) {
if (lowerCaseMessage.includes('guided tour'))
{ selectService('guided-tour'); }
else if (lowerCaseMessage.includes('event booking') ||
lowerCaseMessage.includes('event')) { selectService('event-booking'); }
else if (lowerCaseMessage.includes('general inquiry') ||
lowerCaseMessage.includes('inquiry')) { selectService('general-inquiry'); }
else { appendMessage('receiver', tr.selectServiceError); }
return;
}

// 4. Expecting Date/Time/Persons/Details? Guide user to buttons/forms.


if (!dateSelection.classList.contains('hidden'))
{ appendMessage('receiver', "Please select a date using the calendar above.");
return; }
if (!timeSelection.classList.contains('hidden'))
{ appendMessage('receiver', "Please choose a time slot using the buttons above.");
return; }
if (!personsSelection.classList.contains('hidden'))
{ appendMessage('receiver', "Please select the number of persons and click
'Continue'."); return; }
if (!personDetailsContainer.classList.contains('hidden'))
{ appendMessage('receiver', "Please fill in the details for all attendees and click
'Confirm Details'."); return; }
if (!ticketSection.classList.contains('hidden'))
{ appendMessage('receiver', "Please use the buttons above to manage your ticket
(Pay, Check Payment, Confirm, Print)."); return; }
if (!confirmationMessage.classList.contains('hidden'))
{ appendMessage('receiver', "Your ticket is confirmed. You can cancel it using the
button above or start a new booking by saying 'reset'."); return; }

// --- General Queries (Fallback) ---


if (lowerCaseMessage.includes('hours') ||
lowerCaseMessage.includes('time')) {
appendMessage('receiver', "Our museums are generally open from 9 AM
to 5 PM (times may vary). Specific event times are listed during booking. Which
museum are you interested in?");
} else if (lowerCaseMessage.includes('location') ||
lowerCaseMessage.includes('address')) {
appendMessage('receiver', "We have several locations. Could you
please specify which museum (Natural History, Art, Science)?");
} else if (lowerCaseMessage.includes('cost') ||
lowerCaseMessage.includes('price') || lowerCaseMessage.includes('fee')) {
appendMessage('receiver', "Ticket prices vary. Guided Tours and
Events have specific costs shown during booking. General Inquiry is free. Which
service are you interested in?");
} else if (lowerCaseMessage.includes('help')) {
appendMessage('receiver', "I can help book tickets (Guided Tour,
Event) or answer questions (General Inquiry). To book, please select a museum
first. Type 'cancel' anytime to restart.");
} else if (lowerCaseMessage.includes('available dates')) {
appendMessage('receiver', "Bookings are currently available for
April 5th, 6th, and 7th, 2025.");
}
else {
appendMessage('receiver', "I'm not sure how to help with that. You
can ask about museum hours, locations, prices, or start booking by selecting a
museum. Available dates are April 5-7. Type 'cancel' to restart.");
}
}

// --- User Interaction Handlers (Button Clicks etc.) ---

function getUserDetails() {
const phoneInput = document.getElementById('phone');
const emailInput = document.getElementById('email');
const phone = phoneInput.value.trim();
const email = emailInput.value.trim();
let isValid = true;
const lang = currentLanguage;
const tr = translations[lang];

hideErrorMessage(); // Clear previous errors

if (!/^\d{10}$/.test(phone)) { showError(tr.invalidPhone);
phoneInput.classList.add('border-red-500'); isValid = false; }
else { phoneInput.classList.remove('border-red-500'); }

if (!/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email))
{ showError(tr.invalidEmail); emailInput.classList.add('border-red-500'); isValid =
false; }
else { emailInput.classList.remove('border-red-500'); }

if (!isValid) return; // Stop if validation fails


userPhone = phone;
userEmail = email;
userDetailsForm.classList.add('hidden');
appendMessage('receiver', tr.detailsSaved);

// Decide next step based on current state


if (selectedMuseum === "") {
setTimeout(() => { appendMessage('receiver',
tr.selectMuseumPrompt); museumSelection.classList.remove('hidden'); }, 500);
} else if (selectedService === "") {
const museumName = serviceData[selectedMuseum]?.[`name_${lang}`]
|| serviceData[selectedMuseum]?.name;
setTimeout(() => { appendMessage('receiver',
tr.selectServicePrompt.replace('{museum}', museumName));
serviceSelection.classList.remove('hidden'); }, 500);
} else { // Museum and Service already selected
setTimeout(() => {
appendMessage('receiver', tr.selectDatePrompt);
dateSelection.classList.remove('hidden');
selectedDate = datePicker.value; // Use current picker value
updateAvailableTimes(); // Update times for the selected date
}, 500);
}
}

function selectMuseum(museumKey) {
selectedMuseum = museumKey;
const lang = currentLanguage;
const museumName = serviceData[museumKey]?.[`name_${lang}`] ||
serviceData[museumKey]?.name; // Get translated name
appendMessage('receiver', `Okay, you selected: ${museumName}.`);

appendMessage('receiver',
translations[lang].selectServicePrompt.replace('{museum}', museumName));
museumSelection.classList.add('hidden');
updateLanguage(); // Ensure service names are updated
serviceSelection.classList.remove('hidden');
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function selectService(serviceKey) {
selectedService = serviceKey;
const lang = currentLanguage;
const tr = translations[lang];
const serviceName = serviceData[selectedMuseum].services[serviceKey]?.
[`name_${lang}`] || serviceData[selectedMuseum].services[serviceKey]?.name;
appendMessage('receiver', `Service selected: ${serviceName}.`);

if (!userPhone || !userEmail) { // Check if user details needed


appendMessage('receiver', tr.userDetailsPrompt);
serviceSelection.classList.add('hidden');
userDetailsForm.classList.remove('hidden');
} else { // Proceed to date selection
appendMessage('receiver', tr.selectDatePrompt);
serviceSelection.classList.add('hidden');
dateSelection.classList.remove('hidden');
selectedDate = datePicker.value; // Set current date
updateAvailableTimes(); // Update times for the default/selected
date
}
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function updateAvailableTimes() {
if (!selectedMuseum || !selectedService) return;

const currentSelectedDate = datePicker.value;


selectedDate = currentSelectedDate; // Store globally

const timesForDate =
serviceData[selectedMuseum]?.services[selectedService]?.times?.
[currentSelectedDate] || [];
const lang = currentLanguage;
const tr = translations[lang];

timeSlots.innerHTML = ''; // Clear previous slots

if (timesForDate.length === 0 || !
Object.keys(serviceData[selectedMuseum].services[selectedService].times).includes(c
urrentSelectedDate)) {
// Check if the date itself is invalid or just has no slots
timeSelectionText.textContent = tr.noTimeSlots.replace('{date}',
formatDate(currentSelectedDate));
}
else {
timeSelectionText.textContent =
tr.timeSlotsPrompt.replace('{date}', formatDate(currentSelectedDate));

timesForDate.forEach((slot) => {
const button = document.createElement('button');
button.className = 'service-button';
const isSoldOut = slot.ticketsLeft === 0;
button.disabled = isSoldOut; // Disable if no tickets

let buttonText = `${slot.time}`;


if (slot.price > 0) {
buttonText += ` (₹${slot.price}, ${slot.ticketsLeft} $
{slot.ticketsLeft === 1 ? 'ticket' : 'tickets'} left)`;
} else {
buttonText += ` (Free, ${slot.ticketsLeft} $
{slot.ticketsLeft === 1 ? 'ticket' : 'tickets'} left)`;
}
if(isSoldOut) {
buttonText += " - Sold Out";
}

button.textContent = buttonText;
button.onclick = () => selectTime(slot); // Pass the whole slot
object
timeSlots.appendChild(button);
});
}

dateSelection.classList.add('hidden');
timeSelection.classList.remove('hidden');
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function selectTime(timeSlot) {
selectedTime = { ...timeSlot, date: selectedDate }; // Store selected
time info including date

const lang = currentLanguage;


const tr = translations[lang];

appendMessage('receiver', `Time selected: ${selectedTime.time} on $


{formatDate(selectedTime.date)}.`);

timeAndCostText.textContent = tr.timeAndCost
.replace('{time}', selectedTime.time)
.replace('{date}', formatDate(selectedTime.date))
.replace('{cost}', selectedTime.price);
timeAndCost.classList.remove('hidden');

timeSelection.classList.add('hidden');
personsSelection.classList.remove('hidden');
updatePersonsDropdown();
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function updatePersonsDropdown() {
if (!selectedTime) return;

const maxAllowed = 5;
const ticketsAvailable = selectedTime.ticketsLeft;
const maxPersons = Math.max(1, Math.min(maxAllowed,
ticketsAvailable));

personsCount.innerHTML = '';
for (let i = 1; i <= maxPersons; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = `${i} ${i > 1 ? 'Persons' : 'Person'}`;
personsCount.appendChild(option);
}

const lang = currentLanguage;


const tr = translations[lang];
personsSelectText.textContent = tr.personsPrompt
.replace('(Max 5', `(Max ${maxAllowed}`)
.replace('{ticketsLeft}', ticketsAvailable);

personsCount.value = '1';
numberOfPersons = 1;
}

function updatePersons() {
numberOfPersons = parseInt(personsCount.value);
}

function showPersonDetailsForm() {
if (!selectedTime || numberOfPersons <= 0) return;

const lang = currentLanguage;


const tr = translations[lang];
if (numberOfPersons > selectedTime.ticketsLeft) {
showError(tr.noTicketsError.replace('{tickets}',
selectedTime.ticketsLeft));
personsCount.value = selectedTime.ticketsLeft > 0 ?
selectedTime.ticketsLeft : 1; // Adjust dropdown
updatePersons();
return;
}

personsSelection.classList.add('hidden');
personDetailsContainer.classList.remove('hidden');
personDetailsForms.innerHTML = '';
personDetails = [];

appendMessage('receiver', `Okay, please provide details for $


{numberOfPersons} person(s).`);

for (let i = 1; i <= numberOfPersons; i++) {


const formDiv = document.createElement('div');
formDiv.className = 'person-details mb-4 p-3 border rounded
dark:border-gray-600';

const title = document.createElement('h4');


title.textContent = tr.personDetailsTitle.replace('{number}', i);
title.className = 'font-semibold mb-2';
formDiv.appendChild(title);

// --- Name ---


const nameLabel = document.createElement('label');
nameLabel.htmlFor = `name-${i}`; nameLabel.textContent = tr.enterName + ":";
nameLabel.className = 'block text-sm font-medium mb-1';
formDiv.appendChild(nameLabel);
const nameInput = document.createElement('input'); nameInput.type =
'text'; nameInput.id = `name-${i}`; nameInput.placeholder = tr.enterName;
nameInput.className = 'shadow-sm appearance-none border rounded w-full py-2 px-3
text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 leading-tight
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent mb-2';
nameInput.required = true; nameInput.dataset.personIndex = i - 1;
nameInput.dataset.field = 'name'; formDiv.appendChild(nameInput);

// --- Age ---


const ageLabel = document.createElement('label'); ageLabel.htmlFor
= `age-${i}`; ageLabel.textContent = tr.enterAge + ":"; ageLabel.className = 'block
text-sm font-medium mb-1'; formDiv.appendChild(ageLabel);
const ageInput = document.createElement('input'); ageInput.type =
'number'; ageInput.id = `age-${i}`; ageInput.min = '1'; ageInput.max = '120';
ageInput.placeholder = tr.enterAge; ageInput.className = 'shadow-sm appearance-none
border rounded w-full py-2 px-3 text-gray-700 dark:text-gray-200 bg-white dark:bg-
gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500
focus:border-transparent mb-2'; ageInput.required = true;
ageInput.dataset.personIndex = i - 1; ageInput.dataset.field = 'age';
formDiv.appendChild(ageInput);

// --- Gender ---


const genderLabel = document.createElement('label');
genderLabel.htmlFor = `gender-${i}`; genderLabel.textContent = tr.selectGender +
":"; genderLabel.className = 'block text-sm font-medium mb-1';
formDiv.appendChild(genderLabel);
const genderSelect = document.createElement('select');
genderSelect.id = `gender-${i}`; genderSelect.className = 'shadow-sm appearance-
none border rounded w-full py-2 px-3 text-gray-700 dark:text-gray-200 bg-white
dark:bg-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500
focus:border-transparent mb-2'; genderSelect.required = true;
genderSelect.dataset.personIndex = i - 1; genderSelect.dataset.field = 'gender';
const genderOptions = { "": `--- ${tr.selectGender} ---`, Male:
tr.male, Female: tr.female, Other: tr.other }; // Add placeholder
for (const value in genderOptions) {
const option = document.createElement('option');
option.value = value; option.textContent =
genderOptions[value];
if(value === "") option.disabled = true; // Make placeholder
unselectable
genderSelect.appendChild(option);
}
genderSelect.value = ""; // Set initial value to placeholder
formDiv.appendChild(genderSelect);

personDetailsForms.appendChild(formDiv);
}
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function confirmPersonDetails() {
hideErrorMessage();
const inputs = personDetailsForms.querySelectorAll('input[required],
select[required]');
let allValid = true;
personDetails = [];

inputs.forEach(input => {
input.classList.remove('border-red-500');
let isInvalid = false;
if (!input.value) { isInvalid = true; }
if (input.tagName === 'SELECT' && input.value === "") { isInvalid
= true; } // Check for placeholder
if (input.type === 'number' && (parseInt(input.value) <
parseInt(input.min) || parseInt(input.value) > parseInt(input.max))) { isInvalid =
true; }

if (isInvalid) {
input.classList.add('border-red-500');
allValid = false;
}
});

if (!allValid) {
showError('Please fill in all fields correctly for all persons.');
return;
}

for (let i = 0; i < numberOfPersons; i++) {


const nameInput = personDetailsForms.querySelector(`input[data-
person-index="${i}"][data-field="name"]`);
const ageInput = personDetailsForms.querySelector(`input[data-
person-index="${i}"][data-field="age"]`);
const genderSelect = personDetailsForms.querySelector(`select[data-
person-index="${i}"][data-field="gender"]`);
if (nameInput && ageInput && genderSelect) {
personDetails.push({ name: nameInput.value.trim(), age:
ageInput.value, gender: genderSelect.value });
} else {
console.error(`Could not find input fields for person index $
{i}`);
showError("Error collecting attendee details. Please retry.");
return;
}
}

totalCost = (selectedTime?.price || 0) * numberOfPersons;


personDetailsContainer.classList.add('hidden');
ticketSection.classList.remove('hidden');
displayTicketSummary();
resetPaymentAndConfirmation();
appendMessage('receiver', "Details confirmed. Here is your ticket
summary. Please proceed with payment if required.");
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function displayTicketSummary() {
if (!selectedMuseum || !selectedService || !selectedTime || !userPhone
|| !userEmail) {
console.error("Cannot display ticket summary: Missing required
data.");
showError("Error displaying ticket summary. Please try again.");
return;
}

const lang = currentLanguage;


const tr = translations[lang];
const museumName = serviceData[selectedMuseum]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.name;
const serviceName =
serviceData[selectedMuseum]?.services[selectedService]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.services[selectedService]?.name;

let ticketDetailsText = tr.ticketDetails


.replace('{museum}', museumName)
.replace('{service}', serviceName)
.replace('{date}', formatDate(selectedTime.date))
.replace('{time}', selectedTime.time)
.replace('{persons}', numberOfPersons)
.replace('{cost}', totalCost.toFixed(2)) // Use fixed decimal
places
.replace('{phone}', userPhone)
.replace('{email}', userEmail);

if (referenceNumber) { ticketDetailsText += `\n$


{tr.referenceNumber.replace('{number}', referenceNumber)}`; }

if (personDetails.length > 0) {
ticketDetailsText += `\n\n${tr.attendees}`;
personDetails.forEach(person => {
const genderDisplay = tr[person.gender.toLowerCase()] ||
person.gender;
ticketDetailsText += `\n- ${person.name} (${person.age}, $
{genderDisplay})`;
});
}

ticketDetails.textContent = ticketDetailsText; // For main display


// Prepare HTML for PDF preview
let pdfHtmlContent = ticketDetailsText
.replace(tr.attendees,
`<br><strong>${tr.attendees}</strong>`) // Make attendees bold
.replace(/\n- /g, '<br>- ') // Ensure list items have breaks
.replace(/\n/g, '<br>'); // Convert other newlines
ticketDetailsPdf.innerHTML = pdfHtmlContent;

// Show/hide payment elements


if (totalCost > 0) {
showQrButtonText.disabled = false;
checkPaymentButtonText.disabled = false;
printAlertButton.disabled = true; // Disable printing until
confirmed/paid
printPdfButton.disabled = true;
// Prompt user or automatically show QR
showUpiQrCode();
} else { // Free service
paymentStatus.textContent = tr.noPaymentReady;
paymentStatus.classList.remove('hidden');
ownerQrCodeDiv.classList.add('hidden');
showQrButtonText.disabled = true;
checkPaymentButtonText.disabled = true;
confirmTicketButton.disabled = false;
confirmTicketButton.classList.remove('hidden');
paymentVerifiedGlobally = true;
printAlertButton.disabled = false; // Enable printing for free
tickets
printPdfButton.disabled = false;
}
}

function resetPaymentAndConfirmation() {
paymentVerifiedGlobally = false;
paymentStatus.classList.add('hidden'); paymentStatus.textContent = '';
ownerQrCodeDiv.classList.add('hidden');
validationQrCodeDiv.classList.add('hidden'); // Hide validation QR
referenceNumberDiv.classList.add('hidden'); // Hide reference number
confirmTicketButton.classList.add('hidden');
confirmTicketButton.disabled = true;
confirmationMessage.classList.add('hidden');
// Reset print buttons based on whether it's free or paid (initial
state)
printAlertButton.disabled = totalCost > 0;
printPdfButton.disabled = totalCost > 0;
checkPaymentButtonText.disabled = totalCost <= 0;
showQrButtonText.disabled = totalCost <= 0;
}

function generateQRCode(data, targetDiv) {


targetDiv.innerHTML = ''; // Clear previous QR
const qrCanvas = document.createElement('canvas');
targetDiv.appendChild(qrCanvas);
QRCode.toCanvas(qrCanvas, data, { width: 150, margin: 1 }, (error) => {
if (error) {
console.error("QR Code generation failed:", error);
targetDiv.innerHTML = '<p class="text-red-500">Error generating
QR code.</p>';
}
});
}

function showUpiQrCode() {
hideErrorMessage();
const lang = currentLanguage;
const tr = translations[lang];

if (totalCost <= 0) { // Handle free case (should ideally be caught


earlier)
paymentVerifiedGlobally = true;
paymentStatus.textContent = tr.noPaymentReady;
paymentStatus.classList.remove('hidden');
ownerQrCodeDiv.classList.add('hidden');
confirmTicketButton.classList.remove('hidden');
confirmTicketButton.disabled = false;
checkPaymentButtonText.disabled = true; showQrButtonText.disabled
= true;
printAlertButton.disabled = false; printPdfButton.disabled =
false;
// appendMessage('receiver', tr.noPaymentMessage); // Avoid
repetitive messages
return;
}

upiQrData = `${upiBaseUrl}&am=$
{totalCost.toFixed(2)}&cu=INR&tn=MuseumTicket${Date.now()}`;
upiAmountSpan.textContent = `${totalCost.toFixed(2)}`;
scanPayText.textContent = tr.scanPayText.replace('{amount}',
totalCost.toFixed(2));

generateQRCode(upiQrData, ownerQrCodeDiv);
ownerQrCodeDiv.classList.remove('hidden');

paymentStatus.textContent = tr.scanUpi.replace('{cost}',
totalCost.toFixed(2));
paymentStatus.classList.remove('hidden');

appendMessage('receiver', tr.scanUpi.replace('{cost}',
totalCost.toFixed(2)));

confirmTicketButton.classList.add('hidden');
confirmTicketButton.disabled = true;
checkPaymentButtonText.disabled = false; showQrButtonText.disabled =
false;
printAlertButton.disabled = true; printPdfButton.disabled = true;

chatContainer.scrollTop = chatContainer.scrollHeight;
}

function checkPayment() {
hideErrorMessage();
const lang = currentLanguage;
const tr = translations[lang];

if (totalCost <= 0) return; // Should be disabled anyway

const userConfirmedPayment = confirm("Have you completed the payment


via UPI? Click 'OK' if yes, 'Cancel' if no.");

if (userConfirmedPayment) {
paymentVerifiedGlobally = true;
paymentStatus.textContent = tr.paymentVerified;
paymentStatus.classList.remove('hidden');
confirmTicketButton.classList.remove('hidden');
confirmTicketButton.disabled = false;
ownerQrCodeDiv.classList.add('hidden'); // Hide QR now
checkPaymentButtonText.disabled = true; // Disable check btn
showQrButtonText.disabled = true; // Disable show QR btn
printAlertButton.disabled = false; // Enable print
printPdfButton.disabled = false;
appendMessage('receiver', tr.paymentVerified);
} else {
paymentVerifiedGlobally = false;
paymentStatus.textContent = tr.paymentNotConfirmed;
paymentStatus.classList.remove('hidden');
confirmTicketButton.classList.add('hidden');
confirmTicketButton.disabled = true;
checkPaymentButtonText.disabled = false; // Keep enabled
showQrButtonText.disabled = false; // Keep enabled
appendMessage('receiver', tr.paymentNotConfirmedMessage);
}
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function printTicket(type) {
if (!paymentVerifiedGlobally) {
appendMessage('receiver', "Please confirm payment before
printing.");
showError("Payment not verified. Cannot print ticket."); return;
}
if (!selectedMuseum || !selectedService || !selectedTime ||
personDetails.length !== numberOfPersons) {
appendMessage('receiver', "Ticket details incomplete. Cannot
print.");
showError("Cannot print ticket, details missing."); return;
}

const lang = currentLanguage;


const tr = translations[lang];
const museumName = serviceData[selectedMuseum]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.name;
const serviceName =
serviceData[selectedMuseum]?.services[selectedService]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.services[selectedService]?.name;

let ticketInfo = `** ${museumName} - ${serviceName} **\n`;


ticketInfo += `------------------------------------\n`;
ticketInfo += `Date: ${formatDate(selectedTime.date)}\nTime: $
{selectedTime.time}\n`;
ticketInfo += `Persons: ${numberOfPersons}\nTotal Cost: ₹$
{totalCost.toFixed(2)}\n`;
ticketInfo += `Reference: ${referenceNumber || 'PENDING
CONFIRMATION'}\n`;
ticketInfo += `Booked by: ${userEmail} / ${userPhone}\n`;
ticketInfo += `------------------------------------\n${tr.attendees}\
n`;
personDetails.forEach((person, index) => {
const genderDisplay = tr[person.gender.toLowerCase()] ||
person.gender;
ticketInfo += ` ${index + 1}. ${person.name} (Age: ${person.age},
Gender: ${genderDisplay})\n`;
});
ticketInfo += `------------------------------------\nStatus: $
{validationQrData ? 'CONFIRMED' : 'PAYMENT VERIFIED (Confirmation Pending)'}\n`;

if (type === 'alert') {


alert("--- MUSEUM TICKET ---\n" + ticketInfo);
} else if (type === 'pdf') {
let pdfHtmlContent = `
<h2 style="text-align: center; font-size: 20px; margin-bottom:
10px; color: #333;">${museumName}</h2>
<h3 style="text-align: center; font-size: 16px; margin-bottom:
15px; color: #555;">${serviceName}</h3>
<hr style="border-top: 1px dashed #ccc; margin: 15px 0;">
<p><strong>Date:</strong> ${formatDate(selectedTime.date)}</p>
<p><strong>Time:</strong> ${selectedTime.time}</p>
<p><strong>Persons:</strong> ${numberOfPersons}</p>
<p><strong>Total Cost:</strong> ₹${totalCost.toFixed(2)}</p>
`;
if (referenceNumber) {
pdfHtmlContent += `<p><strong>Reference:</strong> $
{referenceNumber}</p>`;
referenceNumberPdf.innerHTML = `<strong>Reference:</strong> $
{referenceNumber}`;
} else {
pdfHtmlContent += `<p><strong>Reference:</strong>
PENDING</p>`;
referenceNumberPdf.innerHTML = `<strong>Reference:</strong>
PENDING`;
}
pdfHtmlContent += `<p><strong>Booked by:</strong> ${userEmail} / $
{userPhone}</p>`;
pdfHtmlContent += `<hr style="border-top: 1px dashed #ccc; margin:
15px 0;">`;
pdfHtmlContent += `<h4 style="font-size: 14px; margin-bottom:
5px;">${tr.attendees}</h4><ul style="list-style: none; padding-left: 0; font-size:
13px;">`;
personDetails.forEach(person => {
const genderDisplay = tr[person.gender.toLowerCase()] ||
person.gender;
pdfHtmlContent += `<li style="margin-bottom: 3px;">- $
{person.name} (Age: ${person.age}, Gender: ${genderDisplay})</li>`;
});
pdfHtmlContent += `</ul>`;
ticketDetailsPdf.innerHTML = pdfHtmlContent;

validationQrCodePdf.innerHTML = ''; // Clear previous QR PDF area


if (validationQrData) { // Add validation QR only if confirmed
validationQrCodePdf.innerHTML = `<p style="font-weight: bold;
margin-bottom: 10px; font-size: 14px; color: #333;">${tr.validationQR}</p>`;
const qrCanvasPdf = document.createElement('canvas');
validationQrCodePdf.appendChild(qrCanvasPdf);
QRCode.toCanvas(qrCanvasPdf, validationQrData, { width: 120,
margin: 1 }, (error) => {
if (error) console.error("QR Code generation failed for
PDF:", error);
generatePdfFromContent(); // Generate PDF after QR attempt
});
} else {
validationQrCodePdf.innerHTML = `<p style="font-style: italic;
color: #888; font-size: 12px;">Validation QR will appear after confirmation.</p>`;
generatePdfFromContent(); // Generate PDF without QR
}
}
}

function generatePdfFromContent() {
const element = document.getElementById('ticket-content');
const pdfFilename = `Museum_Ticket_${referenceNumber ||
Date.now()}.pdf`;
const pdfOptions = { margin: 15, filename: pdfFilename, image:
{ type: 'jpeg', quality: 0.95 }, html2canvas: { scale: 2, useCORS: true }, jsPDF: {
unit: 'mm', format: 'a5', orientation: 'portrait' } };
try {
html2pdf().from(element).set(pdfOptions).save();
appendMessage('receiver', `Ticket PDF generated ($
{pdfFilename}).`);
} catch (error) {
console.error("PDF generation failed:", error);
appendMessage('receiver', "Failed to generate PDF.");
showError("Failed to generate PDF.");
}
}

function generateValidationQRData() {
const ticketData = { ref: referenceNumber, m: selectedMuseum, s:
selectedService, dt: selectedTime.date, tm: selectedTime.time, pax:
numberOfPersons, ph: userPhone, ts: Date.now() };
try { return JSON.stringify(ticketData); }
catch (error) { console.error("Failed to stringify QR data:", error);
return `ERROR_QR_${Date.now()}`; }
}

// Confirm Ticket and Send Email


async function confirmTicket() {
if (!paymentVerifiedGlobally && totalCost > 0)
{ showError(translations[currentLanguage].paymentVerifyPrompt); return; }
if (!selectedMuseum || !selectedService || !selectedTime ||
personDetails.length !== numberOfPersons) { showError("Cannot confirm ticket,
details incomplete."); return; }

confirmTicketButton.disabled = true;
confirmTicketButton.childNodes[confirmTicketButton.childNodes.length -
1].nodeValue = " Confirming..."; // Update button text

const lang = currentLanguage;


const tr = translations[lang];

try {
referenceNumber = generateReferenceNumber();
validationQrData = generateValidationQRData();

currentTicket = {
referenceNumber: referenceNumber, museumKey: selectedMuseum,
serviceKey: selectedService,
museum: serviceData[selectedMuseum]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.name,
service:
serviceData[selectedMuseum]?.services[selectedService]?.[`name_${lang}`] ||
serviceData[selectedMuseum]?.services[selectedService]?.name,
time: selectedTime.time, date: selectedTime.date, persons:
numberOfPersons, cost: totalCost, phone: userPhone, email: userEmail,
personDetails: personDetails, validationQrData:
validationQrData, confirmedAt: new Date().toISOString()
};
bookedTickets.push(currentTicket);
saveBookedTickets();

// Update UI immediately
referenceNumberValue.textContent = referenceNumber;
referenceNumberDiv.classList.remove('hidden');
referenceNumberPdf.innerHTML = `<strong>Reference:</strong> $
{referenceNumber}`;
validationQrCodeDiv.innerHTML = `<p>${tr.validationQR}</p>`;
generateQRCode(validationQrData, validationQrCodeDiv);
validationQrCodeDiv.classList.remove('hidden');
displayTicketSummary(); // Update summary with Ref#

ticketSection.classList.add('hidden'); // Hide actions


confirmationMessage.classList.remove('hidden'); // Show success
area
cancelTicketButton.disabled = false;

// Prepare Email
let attendeeDetailsString = "";
personDetails.forEach((p, i) => {
const genderDisplay = tr[p.gender.toLowerCase()] || p.gender;
attendeeDetailsString += `${i + 1}. ${p.name} (Age: ${p.age},
Gender: ${genderDisplay})\n`;
});
const templateParams = {
to_email: userEmail, user_name: personDetails[0]?.name ||
'Valued Customer',
museum: currentTicket.museum, service: currentTicket.service,
date: formatDate(currentTicket.date),
time: currentTicket.time, persons: currentTicket.persons,
cost: currentTicket.cost.toFixed(2),
reference_number: currentTicket.referenceNumber, phone_number:
currentTicket.phone,
attendee_details: attendeeDetailsString.trim(),
};

console.log("Sending email with params:", templateParams);

try { // Send Email


await emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID,
templateParams);
console.log('EmailJS SUCCESS!');
confirmationMessageText.textContent =
tr.ticketConfirmed.replace('{email}', userEmail);
appendMessage('receiver', tr.ticketConfirmedMessage);
} catch (emailError) { // Email failed, but ticket confirmed
locally
console.error('EmailJS FAILED...', emailError);
confirmationMessageText.textContent =
tr.emailFailed.replace('{referenceNumber}', referenceNumber);
appendMessage('receiver',
tr.emailFailedMessage.replace('{referenceNumber}', referenceNumber));
}

} catch (error) { // Error during local confirmation steps


console.error("Failed to confirm ticket locally:", error);
appendMessage('receiver', 'Error confirming ticket. Please try
again.');
showError("An unexpected error occurred during confirmation.");
confirmTicketButton.disabled = false; // Re-enable button

confirmTicketButton.childNodes[confirmTicketButton.childNodes.length - 1].nodeValue
= ` ${tr.confirmAndEmail}`; // Reset text
ticketSection.classList.remove('hidden'); // Show actions again
confirmationMessage.classList.add('hidden');
} finally {
if (isHistoryVisible) { displayPreviousTickets(); }
chatContainer.scrollTop = chatContainer.scrollHeight;
}
}

function cancelTicket() {
if (!currentTicket || !currentTicket.referenceNumber) return;

const refToCancel = currentTicket.referenceNumber;


const lang = currentLanguage;
const tr = translations[lang];

cancelTicketButton.disabled = true;
bookedTickets = bookedTickets.filter(ticket =>
ticket.referenceNumber !== refToCancel);
saveBookedTickets();

confirmationMessage.classList.add('hidden');
appendMessage('receiver',
tr.ticketCancelledMessage.replace('{referenceNumber}', refToCancel));
resetChatState(); // Partial reset

setTimeout(() => {
appendMessage('receiver', tr.selectMuseumPrompt);
museumSelection.classList.remove('hidden');
chatContainer.scrollTop = chatContainer.scrollHeight;
}, 1000);

if (isHistoryVisible) { displayPreviousTickets(); }
currentTicket = null;
}
// --- Utility Functions ---

function formatDate(dateString) {
if (!dateString || dateString.length < 10) return "Invalid Date"; //
Basic check
try {
const date = new Date(dateString + 'T00:00:00Z'); // Assume UTC
date input
const options = { year: 'numeric', month: 'long', day: 'numeric',
timeZone: 'UTC' };
let locale = 'en-US';
if (currentLanguage === 'hi') locale = 'hi-IN';
if (currentLanguage === 'ta') locale = 'ta-IN';
// Check if Intl is supported and locale is valid, otherwise
fallback
if (typeof Intl?.DateTimeFormat.supportedLocalesOf === 'function'
&& Intl.DateTimeFormat.supportedLocalesOf(locale).length > 0) {
return date.toLocaleDateString(locale, options);
} else {
// Simple fallback if locale isn't supported
return date.toISOString().slice(0, 10); // YYYY-MM-DD
}

} catch (e) {
console.error("Error formatting date:", e, "Input:", dateString);
return dateString; // Fallback
}
}

function generateReferenceNumber() {
const datePart = new Date().toISOString().slice(0, 10).replace(/-/g,
"");
const randomPart = Math.random().toString(36).substring(2,
8).toUpperCase();
return `M${datePart}-${randomPart}`;
}

function showError(message) {
errorMessageText.textContent = message;
errorMessage.classList.remove('hidden');
chatContainer.scrollTop = chatContainer.scrollHeight;
}

function hideErrorMessage() {
if(!errorMessage.classList.contains('hidden')){
errorMessage.classList.add('hidden');
errorMessageText.textContent = '';
}
}

function saveBookedTickets() {
try {
localStorage.setItem('bookedTickets',
JSON.stringify(bookedTickets));
} catch (error) {
console.error("Failed to save booked tickets:", error);
}
}

// --- Ticket History & Lookup ---

function displayPreviousTickets() {
previousTicketsList.innerHTML = ''; // Clear list
const lang = currentLanguage;
const tr = translations[lang];

if (bookedTickets.length === 0) {
previousTicketsList.innerHTML = `<p class="italic text-gray-500
dark:text-gray-400 p-2">${tr.noPreviousTickets}</p>`;
} else {
[...bookedTickets].reverse().forEach((ticket) => {
const ticketItem = document.createElement('div');
ticketItem.className = 'ticket-item p-2 border-b dark:border-
gray-600';
const museumName = serviceData[ticket.museumKey]?.[`name_$
{lang}`] || ticket.museum;
const serviceName =
serviceData[ticket.museumKey]?.services[ticket.serviceKey]?.[`name_${lang}`] ||
ticket.service;

let ticketText = `<strong>Ref: ${ticket.referenceNumber ||


'N/A'}</strong>\n`;
ticketText += `${museumName} - ${serviceName}\n`;
ticketText += `Date: ${formatDate(ticket.date)} at $
{ticket.time}\n`;
ticketText += `Persons: ${ticket.persons}, Cost: ₹$
{ticket.cost.toFixed(2)}\n`;
if (ticket.personDetails && ticket.personDetails.length > 0) {
ticketText += `Attendees: ${ticket.personDetails.map(p =>
p.name).join(', ')}\n`;
}
ticketText += `Booked: ${new Date(ticket.confirmedAt ||
Date.now()).toLocaleString(lang + '-' + (lang === 'en' ? 'US' : 'IN'))}`; //
Localized booking time

ticketItem.innerHTML = ticketText.replace(/\n/g, '<br>');


previousTicketsList.appendChild(ticketItem);
});
}
}

async function lookupBooking() {


hideErrorMessage();
const lang = currentLanguage;
const tr = translations[lang];
const lookupType = prompt(tr.lookupPromptType);
let identifier;

if (lookupType === '1') { // By Reference


identifier = prompt(tr.lookupPromptRef);
if (!identifier) return;
identifier = identifier.trim().toUpperCase();
const booking = bookedTickets.find(t =>
t.referenceNumber?.toUpperCase() === identifier);
if (booking) { displayBookingDetails(booking); } else
{ alert(tr.bookingNotFound); }

} else if (lookupType === '2') { // By Phone


identifier = prompt(tr.lookupPromptPhone);
if (!identifier || !/^\d{10}$/.test(identifier.trim()))
{ alert(tr.invalidPhone); return; }
identifier = identifier.trim();
const userBookings = bookedTickets.filter(t => t.phone ===
identifier);

if (userBookings.length === 1)
{ displayBookingDetails(userBookings[0]); }
else if (userBookings.length > 1) {
let bookingListText = ""; const newestFirst =
[...userBookings].reverse();
newestFirst.forEach((b, i) => {
const museumName = serviceData[b.museumKey]?.[`name_$
{lang}`] || b.museum;
bookingListText += `${i + 1}. ${museumName} on $
{formatDate(b.date)} (${b.referenceNumber})\n`;
});
const selectionPrompt = tr.bookingsFound.replace('{list}',
bookingListText);
const chosenIndexStr = prompt(selectionPrompt);
const chosenIndex = parseInt(chosenIndexStr) - 1;
if (!isNaN(chosenIndex) && chosenIndex >= 0 && chosenIndex <
newestFirst.length) {
displayBookingDetails(newestFirst[chosenIndex]);
} else if (chosenIndexStr !== null) { alert("Invalid
selection."); }
} else { alert(tr.bookingNotFound); }
} else if (lookupType !== null) { alert("Invalid lookup type
selected."); }
}

function displayBookingDetails(booking) {
const lang = currentLanguage;
const tr = translations[lang];
const museumName = serviceData[booking.museumKey]?.[`name_${lang}`] ||
booking.museum;
const serviceName =
serviceData[booking.museumKey]?.services[booking.serviceKey]?.[`name_${lang}`] ||
booking.service;

let details = `** ${tr.bookingDetailsTitle} **\


n------------------------------------\n`;
details += `Reference: ${booking.referenceNumber}\nMuseum: $
{museumName}\nService: ${serviceName}\n`;
details += `Date: ${formatDate(booking.date)}\nTime: ${booking.time}\
nPersons: ${booking.persons}\n`;
details += `Cost: ₹${booking.cost.toFixed(2)}\nPhone: $
{booking.phone}\nEmail: ${booking.email}\n`;
if (booking.personDetails && booking.personDetails.length > 0) {
details += `------------------------------------\n${tr.attendees}\
n`;
booking.personDetails.forEach(p => {
const genderDisplay = tr[p.gender.toLowerCase()] || p.gender;
details += `- ${p.name} (${p.age}, ${genderDisplay})\n`;
});
}
details += `------------------------------------\nBooked At: ${new
Date(booking.confirmedAt).toLocaleString(lang + '-' + (lang === 'en' ? 'US' :
'IN'))}\n`;

alert(details);
appendMessage('receiver', `Booking details for Ref: $
{booking.referenceNumber} displayed.`);
}

</script>
</body>
</html>
```

You might also like