wixdivin commited on
Commit
5614052
·
verified ·
1 Parent(s): e73f884

MVP Description: QR-Based Event Ticketing and Validation System

Browse files

This MVP (Minimum Viable Product) describes a complete QR Code Ticketing and Validation System, where tickets are generated as QR codes embedding secure numeric identifiers. The system’s goal is to simplify event access verification by combining a numeric encoding scheme, QR generation, manual OTP fallback, and database validation. It ensures both offline usability and fast validation, providing a seamless experience for organizers and attendees.

1. MVP Core Concept

The app’s foundation is built on a numeric QR encoding process. Instead of embedding user data directly (which can be insecure and verbose), each QR code contains a simple 6-digit numeric code, uniquely identifying a purchased ticket.

The structure of the numeric code is defined as follows:[EE][UUUU]
EE (2 digits) – represents the event ID.

UUUU (4 digits) – a random numeric code assigned to a user’s ticket.

For example, if the event ID is 05 (Music Festival) and the user’s random code is 1234, the resulting QR and OTP would be 051234. This number acts as both the encoded QR payload and a manual OTP fallback, ensuring validation works even if QR scanning fails.

2. User Flow

The MVP flow is simple, efficient, and covers all user scenarios:

Ticket Purchase

The user selects an event and enters their name.

The system generates a random 4-digit code.

Combines event ID + user code → final OTP like 051234.

QR Code Generation

The backend encodes 051234 using a QR generator (e.g., QRCode.toDataURL).

It stores the QR data (base64), event ID, buyer name, OTP, and usage status in the database.

Ticket Validation

At the event, the admin scans the QR using a web or mobile scanner.

The scanner decodes the number (e.g., “051234”).

The backend looks up the database entry for that OTP.

If found and unused → mark as used → return Valid ✅.

If not found or already used → return Invalid ❌.

Manual Validation

If a QR scan fails, the admin can type the same 6-digit OTP manually.

The system performs the same lookup and validation logic.

3. MVP Feature Breakdown
🎫 Ticket Generation Module

Input: eventId, buyerName

Process:

Generate random user code (4 digits)

Combine with eventId

Generate QR code

Store record in DB

Return QR image and OTP code

Output: { qrCode: "base64", otpCode: "051234" }

🔍 Ticket Validation Module

Input: code (from QR scan or manual)

Process:

Find ticket by otpCode

If exists and not used → mark used

If invalid or used → return false

Output: { valid: true, eventId, buyerName }

📷 QR Scanner Module

Uses camera to detect and decode numeric QR data.

If no camera access, fallback to manual OTP input.

Displays validation feedback visually.

💻 Admin Validation Page

Real-time QR scanning using React/JS QR library.

Manual code entry field.

Displays “✅ Valid” or “❌ Invalid”.

Logs validation events.

4. Database Structure

A minimal Tickets table captures all essential fields.
This allows tracking of each generated QR code, its corresponding event, and usage state.

Column Type Description
id UUID Primary key
eventId INT 2-digit event identifier
userCode INT 4-digit random ticket code
otpCode STRING Combination of eventId + userCode
qrCode STRING Base64 image data
buyerName STRING Purchaser name
used BOOLEAN Marks if the ticket has been validated
createdAt DATETIME Timestamp of creation{
"id": "uuid-1234",
"eventId": 5,
"userCode": 1234,
"otpCode": "051234",
"qrCode": "data:image/png;base64,...",
"buyerName": "John Doe",
"used": false,
"createdAt": "2025-10-17T08:00:00Z"
}
{
"backend": {
"api": {
"/generateTicket": {
"method": "POST",
"input": {
"eventId": "number",
"buyerName": "string"
},
"process": [
"Generate 4 random digits",
"Combine with eventId to form otp",
"Generate QR image for otp",
"Save record to database",
"Return qrCode and otp"
],
"response": {
"qrCode": "base64 image string",
"otpCode": "string"
}
},
"/validateTicket": {
"method": "POST",
"input": {
"code": "string"
},
"process": [
"Check if code exists and not used",
"If valid: mark as used, return success",
"Else return invalid"
],
"response": {
"valid": "boolean",
"buyerName": "string",
"eventId": "number"
}
}
}
}
}
{
"frontend": {
"pages": {
"HomePage": {
"features": ["Show available events", "Buy Ticket button"]
},
"BuyTicketPage": {
"features": [
"Form to select event and enter name",
"Call /generateTicket API",
"Display QR image + numeric OTP",
"Option to download or share QR"
]
},
"ValidationPage": {
"features": [
"Live QR Scanner (camera)",
"Manual OTP entry field",
"Real-time validation result display"
]
},
"AdminPage": {
"features": [
"View all generated tickets",
"See validation logs",
"Reset or manage event codes"
]
}
},
"components": {
"QRCodeDisplay": "Shows generated QR and OTP",
"QRScanner": "Handles scanning and decoding",
"ValidationFeedback": "Displays valid/invalid status"
}
}
}
{
"frontend": {
"pages": {
"HomePage": {
"features": ["Show available events", "Buy Ticket button"]
},
"BuyTicketPage": {
"features": [
"Form to select event and enter name",
"Call /generateTicket API",
"Display QR image + numeric OTP",
"Option to download or share QR"
]
},
"ValidationPage": {
"features": [
"Live QR Scanner (camera)",
"Manual OTP entry field",
"Real-time validation result display"
]
},
"AdminPage": {
"features": [
"View all generated tickets",
"See validation logs",
"Reset or manage event codes"
]
}
},
"components": {
"QRCodeDisplay": "Shows generated QR and OTP",
"QRScanner": "Handles scanning and decoding",
"ValidationFeedback": "Displays valid/invalid status"
}
}
}
7. Security and Integrity

The app’s security model emphasizes simplicity and one-time-use validation.

Mechanism Purpose
Short Numeric Codes Prevents leakage of user data inside QR
Server Validation Ensures no fake or tampered QR passes
One-Time Use Flag Prevents multiple entries with same ticket
Manual OTP Fallback Keeps system functional in camera-restricted environments
Optional Expiry Event codes can expire automatically after event date

Files changed (4) hide show
  1. README.md +8 -5
  2. generate.html +274 -0
  3. index.html +257 -18
  4. validate.html +326 -0
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Qrush Ticketing Magic
3
- emoji: 😻
4
- colorFrom: green
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: QRush Ticketing Magic
3
+ colorFrom: blue
4
+ colorTo: gray
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
generate.html ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Generate Tickets | QRush</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/qrcode-generator/qrcode.min.js"></script>
10
+ <style>
11
+ .ticket-card {
12
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
13
+ transition: all 0.3s ease;
14
+ }
15
+ .ticket-card:hover {
16
+ transform: translateY(-5px);
17
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
18
+ }
19
+ .qr-placeholder {
20
+ background-color: #f0f0f0;
21
+ background-image:
22
+ linear-gradient(45deg, #e0e0e0 25%, transparent 25%, transparent 75%, #e0e0e0 75%, #e0e0e0),
23
+ linear-gradient(45deg, #e0e0e0 25%, transparent 25%, transparent 75%, #e0e0e0 75%, #e0e0e0);
24
+ background-size: 20px 20px;
25
+ background-position: 0 0, 10px 10px;
26
+ }
27
+ </style>
28
+ </head>
29
+ <body class="min-h-screen bg-gray-50">
30
+ <!-- Navigation -->
31
+ <nav class="bg-white shadow-sm">
32
+ <div class="container mx-auto px-6 py-3 flex justify-between items-center">
33
+ <div class="flex items-center space-x-4">
34
+ <i data-feather="qr-code" class="w-6 h-6 text-purple-600"></i>
35
+ <span class="text-xl font-bold text-gray-800">QRush</span>
36
+ </div>
37
+ <a href="index.html" class="text-gray-600 hover:text-purple-600 flex items-center">
38
+ <i data-feather="home" class="w-5 h-5 mr-1"></i>
39
+ Back to Home
40
+ </a>
41
+ </div>
42
+ </nav>
43
+
44
+ <!-- Main Content -->
45
+ <main class="container mx-auto px-6 py-12">
46
+ <div class="text-center mb-12">
47
+ <h1 class="text-3xl font-bold text-gray-800 mb-3">Generate Tickets</h1>
48
+ <p class="text-gray-600 max-w-2xl mx-auto">Create QR code tickets with OTP fallback for your event.</p>
49
+ </div>
50
+
51
+ <div class="flex flex-col lg:flex-row gap-10">
52
+ <!-- Form Section -->
53
+ <div class="w-full lg:w-1/2">
54
+ <div class="bg-white p-6 rounded-xl shadow-md">
55
+ <h2 class="text-xl font-semibold text-gray-800 mb-6">Ticket Details</h2>
56
+
57
+ <form id="ticket-form">
58
+ <div class="mb-6">
59
+ <label for="event-select" class="block text-sm font-medium text-gray-700 mb-2">Select Event</label>
60
+ <select id="event-select" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
61
+ <option value="">-- Select an event --</option>
62
+ <option value="05">Music Festival (ID: 05)</option>
63
+ <option value="12">Tech Conference (ID: 12)</option>
64
+ <option value="18">Art Exhibition (ID: 18)</option>
65
+ <option value="23">Sports Game (ID: 23)</option>
66
+ </select>
67
+ </div>
68
+
69
+ <div class="mb-6">
70
+ <label for="ticket-type" class="block text-sm font-medium text-gray-700 mb-2">Ticket Type</label>
71
+ <select id="ticket-type" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
72
+ <option value="general">General Admission</option>
73
+ <option value="vip">VIP</option>
74
+ <option value="student">Student</option>
75
+ <option value="earlybird">Early Bird</option>
76
+ </select>
77
+ </div>
78
+
79
+ <div class="mb-6">
80
+ <label for="attendee-name" class="block text-sm font-medium text-gray-700 mb-2">Attendee Name</label>
81
+ <input
82
+ type="text"
83
+ id="attendee-name"
84
+ placeholder="Enter attendee's full name"
85
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
86
+ required
87
+ >
88
+ </div>
89
+
90
+ <div class="mb-6">
91
+ <label for="ticket-count" class="block text-sm font-medium text-gray-700 mb-2">Number of Tickets</label>
92
+ <input
93
+ type="number"
94
+ id="ticket-count"
95
+ min="1"
96
+ max="10"
97
+ value="1"
98
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
99
+ >
100
+ </div>
101
+
102
+ <div class="flex justify-end">
103
+ <button
104
+ type="submit"
105
+ class="bg-purple-600 text-white px-6 py-3 rounded-lg hover:bg-purple-700 transition duration-300 flex items-center"
106
+ >
107
+ <i data-feather="plus" class="w-5 h-5 mr-2"></i>
108
+ Generate Tickets
109
+ </button>
110
+ </div>
111
+ </form>
112
+ </div>
113
+ </div>
114
+
115
+ <!-- Preview Section -->
116
+ <div class="w-full lg:w-1/2">
117
+ <div class="bg-white p-6 rounded-xl shadow-md">
118
+ <h2 class="text-xl font-semibold text-gray-800 mb-6">Ticket Preview</h2>
119
+
120
+ <div id="ticket-preview" class="ticket-card p-6 rounded-lg border border-gray-200 mb-6">
121
+ <div id="empty-state" class="text-center py-12">
122
+ <div class="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
123
+ <i data-feather="ticket" class="w-8 h-8 text-gray-400"></i>
124
+ </div>
125
+ <h3 class="text-lg font-medium text-gray-500 mb-2">No ticket generated yet</h3>
126
+ <p class="text-gray-400 text-sm">Fill out the form to generate a new ticket.</p>
127
+ </div>
128
+
129
+ <div id="ticket-content" class="hidden">
130
+ <div class="flex justify-between items-start mb-4">
131
+ <div>
132
+ <h3 id="preview-event" class="font-bold text-lg text-gray-800">Music Festival</h3>
133
+ <p id="preview-date" class="text-sm text-gray-600">October 25, 2023</p>
134
+ </div>
135
+ <div id="preview-type" class="bg-purple-100 px-3 py-1 rounded-full">
136
+ <span class="text-purple-700 text-sm font-medium">General</span>
137
+ </div>
138
+ </div>
139
+
140
+ <div class="flex flex-col items-center mb-4">
141
+ <div id="qr-code" class="w-40 h-40 mb-3 bg-white p-2 rounded qr-placeholder flex items-center justify-center">
142
+ <i data-feather="image" class="w-10 h-10 text-gray-300"></i>
143
+ </div>
144
+ <p class="text-sm text-gray-600">OTP: <span id="preview-otp" class="font-mono font-bold">051234</span></p>
145
+ </div>
146
+
147
+ <div class="text-center">
148
+ <p class="text-sm text-gray-600">Holder: <span id="preview-name" class="font-medium">John Doe</span></p>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div id="actions" class="hidden">
154
+ <div class="grid grid-cols-2 gap-4">
155
+ <button id="download-btn" class="bg-white border border-purple-600 text-purple-600 py-2 rounded-lg hover:bg-purple-50 transition duration-300 flex items-center justify-center">
156
+ <i data-feather="download" class="w-4 h-4 mr-2"></i>
157
+ Download
158
+ </button>
159
+ <button id="print-btn" class="bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700 transition duration-300 flex items-center justify-center">
160
+ <i data-feather="printer" class="w-4 h-4 mr-2"></i>
161
+ Print
162
+ </button>
163
+ </div>
164
+
165
+ <div class="mt-4">
166
+ <button id="new-ticket-btn" class="w-full bg-white border border-gray-300 text-gray-700 py-2 rounded-lg hover:bg-gray-50 transition duration-300 flex items-center justify-center">
167
+ <i data-feather="plus" class="w-4 h-4 mr-2"></i>
168
+ Generate Another
169
+ </button>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </main>
176
+
177
+ <script>
178
+ feather.replace();
179
+
180
+ document.addEventListener('DOMContentLoaded', function() {
181
+ const form = document.getElementById('ticket-form');
182
+ const emptyState = document.getElementById('empty-state');
183
+ const ticketContent = document.getElementById('ticket-content');
184
+ const actions = document.getElementById('actions');
185
+ const qrCodeDiv = document.getElementById('qr-code');
186
+ const newTicketBtn = document.getElementById('new-ticket-btn');
187
+
188
+ // Event data
189
+ const eventData = {
190
+ '05': { name: 'Music Festival', date: 'October 25, 2023' },
191
+ '12': { name: 'Tech Conference', date: 'November 15, 2023' },
192
+ '18': { name: 'Art Exhibition', date: 'December 5, 2023' },
193
+ '23': { name: 'Sports Game', date: 'January 20, 2024' }
194
+ };
195
+
196
+ // Ticket type colors
197
+ const typeColors = {
198
+ 'general': { bg: 'purple-100', text: 'purple-700' },
199
+ 'vip': { bg: 'gold-100', text: 'gold-700' },
200
+ 'student': { bg: 'blue-100', text: 'blue-700' },
201
+ 'earlybird': { bg: 'green-100', text: 'green-700' }
202
+ };
203
+
204
+ // Generate ticket
205
+ form.addEventListener('submit', function(e) {
206
+ e.preventDefault();
207
+
208
+ const eventId = document.getElementById('event-select').value;
209
+ const ticketType = document.getElementById('ticket-type').value;
210
+ const attendeeName = document.getElementById('attendee-name').value;
211
+ const ticketCount = document.getElementById('ticket-count').value;
212
+
213
+ if (!eventId) {
214
+ alert('Please select an event');
215
+ return;
216
+ }
217
+
218
+ if (!attendeeName) {
219
+ alert('Please enter attendee name');
220
+ return;
221
+ }
222
+
223
+ // Generate random 4-digit code
224
+ const userCode = Math.floor(1000 + Math.random() * 9000);
225
+ const otpCode = eventId + userCode;
226
+
227
+ // Update preview
228
+ document.getElementById('preview-event').textContent = eventData[eventId].name;
229
+ document.getElementById('preview-date').textContent = eventData[eventId].date;
230
+ document.getElementById('preview-name').textContent = attendeeName;
231
+ document.getElementById('preview-otp').textContent = otpCode;
232
+
233
+ // Update ticket type badge
234
+ const typeBadge = document.getElementById('preview-type');
235
+ typeBadge.className = `bg-${typeColors[ticketType].bg} px-3 py-1 rounded-full`;
236
+ typeBadge.innerHTML = `<span class="text-${typeColors[ticketType].text} text-sm font-medium capitalize">${ticketType}</span>`;
237
+
238
+ // Generate QR code
239
+ qrCodeDiv.innerHTML = '';
240
+ const qr = qrcode(0, 'L');
241
+ qr.addData(otpCode);
242
+ qr.make();
243
+ qrCodeDiv.innerHTML = qr.createImgTag(4);
244
+
245
+ // Show ticket and actions
246
+ emptyState.classList.add('hidden');
247
+ ticketContent.classList.remove('hidden');
248
+ actions.classList.remove('hidden');
249
+
250
+ // Scroll to preview
251
+ ticketContent.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
252
+ });
253
+
254
+ // New ticket button
255
+ newTicketBtn.addEventListener('click', function() {
256
+ emptyState.classList.remove('hidden');
257
+ ticketContent.classList.add('hidden');
258
+ actions.classList.add('hidden');
259
+ form.reset();
260
+ });
261
+
262
+ // Download button
263
+ document.getElementById('download-btn').addEventListener('click', function() {
264
+ alert('Ticket downloaded! (This would download the QR code and ticket details in a real implementation)');
265
+ });
266
+
267
+ // Print button
268
+ document.getElementById('print-btn').addEventListener('click', function() {
269
+ window.print();
270
+ });
271
+ });
272
+ </script>
273
+ </body>
274
+ </html>
index.html CHANGED
@@ -1,19 +1,258 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>QRush - Event Ticketing System</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/qrcode-generator/qrcode.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/jsqr/dist/jsQR.min.js"></script>
11
+ <style>
12
+ .gradient-bg {
13
+ background: linear-gradient(135deg, #6e8efb 0%, #a777e3 100%);
14
+ }
15
+ .qr-scanner {
16
+ width: 100%;
17
+ max-width: 500px;
18
+ position: relative;
19
+ border-radius: 1rem;
20
+ overflow: hidden;
21
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
22
+ }
23
+ .scanner-overlay {
24
+ position: absolute;
25
+ top: 0;
26
+ left: 0;
27
+ width: 100%;
28
+ height: 100%;
29
+ border: 2px dashed rgba(255, 255, 255, 0.7);
30
+ pointer-events: none;
31
+ }
32
+ .ticket-card {
33
+ transition: all 0.3s ease;
34
+ transform-style: preserve-3d;
35
+ }
36
+ .ticket-card:hover {
37
+ transform: translateY(-5px) rotateX(5deg);
38
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
39
+ }
40
+ </style>
41
+ </head>
42
+ <body class="min-h-screen bg-gray-50">
43
+ <!-- Navigation -->
44
+ <nav class="gradient-bg text-white shadow-lg">
45
+ <div class="container mx-auto px-6 py-3 flex justify-between items-center">
46
+ <div class="flex items-center space-x-4">
47
+ <i data-feather="qr-code" class="w-8 h-8"></i>
48
+ <span class="text-xl font-bold">QRush</span>
49
+ </div>
50
+ <div class="hidden md:flex space-x-8">
51
+ <a href="#" class="hover:text-purple-200 font-medium">Events</a>
52
+ <a href="#" class="hover:text-purple-200 font-medium">Buy Tickets</a>
53
+ <a href="#" class="hover:text-purple-200 font-medium">Validate</a>
54
+ <a href="#" class="hover:text-purple-200 font-medium">Admin</a>
55
+ </div>
56
+ <button class="md:hidden focus:outline-none">
57
+ <i data-feather="menu" class="w-6 h-6"></i>
58
+ </button>
59
+ </div>
60
+ </nav>
61
+
62
+ <!-- Hero Section -->
63
+ <section class="gradient-bg text-white py-20">
64
+ <div class="container mx-auto px-6 text-center">
65
+ <h1 class="text-5xl font-bold mb-6">Your Event, Simplified</h1>
66
+ <p class="text-xl mb-8 max-w-2xl mx-auto">QR-based ticketing system that makes event access seamless for everyone.</p>
67
+ <div class="flex justify-center space-x-4">
68
+ <button class="bg-white text-purple-700 px-6 py-3 rounded-full font-semibold hover:bg-purple-100 transition duration-300">
69
+ Create Event
70
+ </button>
71
+ <button class="border-2 border-white text-white px-6 py-3 rounded-full font-semibold hover:bg-white hover:text-purple-700 transition duration-300">
72
+ Learn More
73
+ </button>
74
+ </div>
75
+ </div>
76
+ </section>
77
+
78
+ <!-- Features Section -->
79
+ <section class="py-16 bg-white">
80
+ <div class="container mx-auto px-6">
81
+ <h2 class="text-3xl font-bold text-center mb-16 text-gray-800">How It Works</h2>
82
+ <div class="grid md:grid-cols-3 gap-10">
83
+ <!-- Feature 1 -->
84
+ <div class="text-center px-6">
85
+ <div class="bg-purple-100 w-20 h-20 mx-auto rounded-full flex items-center justify-center mb-4">
86
+ <i data-feather="ticket" class="w-10 h-10 text-purple-700"></i>
87
+ </div>
88
+ <h3 class="text-xl font-semibold mb-3 text-gray-800">Generate Tickets</h3>
89
+ <p class="text-gray-600">Create unique QR code tickets with numeric OTP fallback in seconds.</p>
90
+ </div>
91
+ <!-- Feature 2 -->
92
+ <div class="text-center px-6">
93
+ <div class="bg-blue-100 w-20 h-20 mx-auto rounded-full flex items-center justify-center mb-4">
94
+ <i data-feather="smartphone" class="w-10 h-10 text-blue-700"></i>
95
+ </div>
96
+ <h3 class="text-xl font-semibold mb-3 text-gray-800">Easy Validation</h3>
97
+ <p class="text-gray-600">Scan QR codes or manually enter OTPs for fast, reliable validation.</p>
98
+ </div>
99
+ <!-- Feature 3 -->
100
+ <div class="text-center px-6">
101
+ <div class="bg-green-100 w-20 h-20 mx-auto rounded-full flex items-center justify-center mb-4">
102
+ <i data-feather="shield" class="w-10 h-10 text-green-700"></i>
103
+ </div>
104
+ <h3 class="text-xl font-semibold mb-3 text-gray-800">Secure System</h3>
105
+ <p class="text-gray-600">One-time use tickets prevent fraud and ensure event security.</p>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </section>
110
+
111
+ <!-- Demo Section -->
112
+ <section class="py-16 bg-gray-50">
113
+ <div class="container mx-auto px-6">
114
+ <h2 class="text-3xl font-bold text-center mb-12 text-gray-800">Try It Out</h2>
115
+ <div class="flex flex-col md:flex-row items-center justify-center gap-10">
116
+ <!-- QR Scanner Demo -->
117
+ <div class="w-full md:w-1/2">
118
+ <div class="bg-white p-8 rounded-xl shadow-lg">
119
+ <h3 class="text-xl font-semibold mb-4 text-gray-800">QR Scanner</h3>
120
+ <div class="qr-scanner bg-black mb-4">
121
+ <video id="scanner-video" class="w-full" autoplay playsinline></video>
122
+ <div class="scanner-overlay"></div>
123
+ </div>
124
+ <p class="text-gray-600 mb-4">Or enter OTP manually:</p>
125
+ <div class="flex">
126
+ <input type="text" placeholder="Enter 6-digit code" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
127
+ <button class="bg-purple-600 text-white px-4 py-2 rounded-r-lg hover:bg-purple-700 transition duration-300">Validate</button>
128
+ </div>
129
+ <div id="validation-result" class="mt-4 hidden">
130
+ <div class="flex items-center">
131
+ <i data-feather="check-circle" class="w-6 h-6 text-green-500 mr-2"></i>
132
+ <span class="text-green-600 font-medium">Valid Ticket</span>
133
+ </div>
134
+ <div class="mt-2 text-sm text-gray-600">
135
+ Event: <span class="font-medium">Music Festival</span><br>
136
+ Holder: <span class="font-medium">John Doe</span>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <!-- Ticket Demo -->
143
+ <div class="w-full md:w-1/2">
144
+ <div class="bg-white p-8 rounded-xl shadow-lg">
145
+ <h3 class="text-xl font-semibold mb-4 text-gray-800">Sample Ticket</h3>
146
+ <div class="ticket-card bg-gradient-to-r from-purple-50 to-blue-50 p-6 rounded-lg border border-purple-100 mb-4">
147
+ <div class="flex justify-between items-start mb-4">
148
+ <div>
149
+ <h4 class="font-bold text-lg text-gray-800">Music Festival</h4>
150
+ <p class="text-sm text-gray-600">October 25, 2023</p>
151
+ </div>
152
+ <div class="bg-purple-100 px-3 py-1 rounded-full">
153
+ <span class="text-purple-700 text-sm font-medium">VIP</span>
154
+ </div>
155
+ </div>
156
+ <div class="flex flex-col items-center mb-4">
157
+ <div id="demo-qr" class="w-40 h-40 mb-3 bg-white p-2 rounded"></div>
158
+ <p class="text-sm text-gray-600">OTP: <span class="font-mono font-bold">051234</span></p>
159
+ </div>
160
+ <div class="text-center">
161
+ <p class="text-sm text-gray-600">Holder: <span class="font-medium">John Doe</span></p>
162
+ </div>
163
+ </div>
164
+ <button class="w-full bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700 transition duration-300">
165
+ Generate New Ticket
166
+ </button>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </section>
172
+
173
+ <!-- Footer -->
174
+ <footer class="bg-gray-800 text-white py-12">
175
+ <div class="container mx-auto px-6">
176
+ <div class="grid md:grid-cols-4 gap-8">
177
+ <div>
178
+ <h3 class="text-xl font-bold mb-4 flex items-center">
179
+ <i data-feather="qr-code" class="w-6 h-6 mr-2"></i>
180
+ QRush
181
+ </h3>
182
+ <p class="text-gray-400">Making event ticketing simple, secure, and seamless.</p>
183
+ </div>
184
+ <div>
185
+ <h4 class="font-semibold mb-4">Product</h4>
186
+ <ul class="space-y-2">
187
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Features</a></li>
188
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Pricing</a></li>
189
+ <li><a href="#" class="text-gray-400 hover:text-white transition">API</a></li>
190
+ </ul>
191
+ </div>
192
+ <div>
193
+ <h4 class="font-semibold mb-4">Resources</h4>
194
+ <ul class="space-y-2">
195
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Documentation</a></li>
196
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Guides</a></li>
197
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Support</a></li>
198
+ </ul>
199
+ </div>
200
+ <div>
201
+ <h4 class="font-semibold mb-4">Company</h4>
202
+ <ul class="space-y-2">
203
+ <li><a href="#" class="text-gray-400 hover:text-white transition">About</a></li>
204
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Blog</a></li>
205
+ <li><a href="#" class="text-gray-400 hover:text-white transition">Contact</a></li>
206
+ </ul>
207
+ </div>
208
+ </div>
209
+ <div class="border-t border-gray-700 mt-8 pt-8 flex flex-col md:flex-row justify-between items-center">
210
+ <p class="text-gray-400 mb-4 md:mb-0">© 2023 QRush. All rights reserved.</p>
211
+ <div class="flex space-x-4">
212
+ <a href="#" class="text-gray-400 hover:text-white transition"><i data-feather="twitter"></i></a>
213
+ <a href="#" class="text-gray-400 hover:text-white transition"><i data-feather="facebook"></i></a>
214
+ <a href="#" class="text-gray-400 hover:text-white transition"><i data-feather="instagram"></i></a>
215
+ <a href="#" class="text-gray-400 hover:text-white transition"><i data-feather="github"></i></a>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </footer>
220
+
221
+ <script>
222
+ // Initialize Feather Icons
223
+ feather.replace();
224
+
225
+ // Generate sample QR code
226
+ document.addEventListener('DOMContentLoaded', function() {
227
+ const qr = qrcode(0, 'L');
228
+ qr.addData('051234');
229
+ qr.make();
230
+ document.getElementById('demo-qr').innerHTML = qr.createImgTag(4);
231
+
232
+ // Simulate scanner (placeholder for actual implementation)
233
+ document.querySelector('button').addEventListener('click', function() {
234
+ const resultDiv = document.getElementById('validation-result');
235
+ resultDiv.classList.remove('hidden');
236
+
237
+ // Hide after 3 seconds
238
+ setTimeout(() => {
239
+ resultDiv.classList.add('hidden');
240
+ }, 3000);
241
+ });
242
+
243
+ // Initialize camera for scanner demo
244
+ if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
245
+ navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
246
+ .then(function(stream) {
247
+ const video = document.getElementById('scanner-video');
248
+ video.srcObject = stream;
249
+ video.play();
250
+ })
251
+ .catch(function(err) {
252
+ console.error("Camera access error:", err);
253
+ });
254
+ }
255
+ });
256
+ </script>
257
+ </body>
258
  </html>
validate.html ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Validate Tickets | QRush</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/jsqr/dist/jsQR.min.js"></script>
10
+ <style>
11
+ .scanner-container {
12
+ position: relative;
13
+ width: 100%;
14
+ max-width: 600px;
15
+ margin: 0 auto;
16
+ }
17
+ .scanner-video {
18
+ width: 100%;
19
+ border-radius: 0.75rem;
20
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
21
+ }
22
+ .scanner-overlay {
23
+ position: absolute;
24
+ top: 0;
25
+ left: 0;
26
+ width: 100%;
27
+ height: 100%;
28
+ border: 3px dashed rgba(255, 255, 255, 0.7);
29
+ pointer-events: none;
30
+ }
31
+ .validation-result {
32
+ transition: all 0.3s ease;
33
+ }
34
+ </style>
35
+ </head>
36
+ <body class="min-h-screen bg-gray-100">
37
+ <!-- Navigation -->
38
+ <nav class="bg-white shadow-sm">
39
+ <div class="container mx-auto px-6 py-3 flex justify-between items-center">
40
+ <div class="flex items-center space-x-4">
41
+ <i data-feather="qr-code" class="w-6 h-6 text-purple-600"></i>
42
+ <span class="text-xl font-bold text-gray-800">QRush</span>
43
+ </div>
44
+ <a href="index.html" class="text-gray-600 hover:text-purple-600 flex items-center">
45
+ <i data-feather="home" class="w-5 h-5 mr-1"></i>
46
+ Back to Home
47
+ </a>
48
+ </div>
49
+ </nav>
50
+
51
+ <!-- Main Content -->
52
+ <main class="container mx-auto px-6 py-12">
53
+ <div class="text-center mb-12">
54
+ <h1 class="text-3xl font-bold text-gray-800 mb-3">Ticket Validation</h1>
55
+ <p class="text-gray-600 max-w-2xl mx-auto">Scan QR codes or manually enter OTP to validate event tickets.</p>
56
+ </div>
57
+
58
+ <div class="flex flex-col lg:flex-row gap-10">
59
+ <!-- Scanner Section -->
60
+ <div class="w-full lg:w-1/2">
61
+ <div class="bg-white p-6 rounded-xl shadow-md">
62
+ <div class="flex justify-between items-center mb-4">
63
+ <h2 class="text-xl font-semibold text-gray-800">QR Scanner</h2>
64
+ <button id="toggle-camera" class="text-purple-600 hover:text-purple-800 flex items-center">
65
+ <i data-feather="refresh-cw" class="w-4 h-4 mr-1"></i>
66
+ Switch Camera
67
+ </button>
68
+ </div>
69
+
70
+ <div class="scanner-container bg-black rounded-lg overflow-hidden mb-4">
71
+ <video id="scanner-video" class="scanner-video" autoplay playsinline></video>
72
+ <div class="scanner-overlay"></div>
73
+ </div>
74
+
75
+ <div class="flex items-center justify-center mb-4">
76
+ <div class="h-px bg-gray-200 flex-1"></div>
77
+ <span class="mx-4 text-gray-500 text-sm">OR</span>
78
+ <div class="h-px bg-gray-200 flex-1"></div>
79
+ </div>
80
+
81
+ <div class="mb-4">
82
+ <label for="manual-code" class="block text-sm font-medium text-gray-700 mb-2">Enter OTP Manually</label>
83
+ <div class="flex">
84
+ <input
85
+ type="text"
86
+ id="manual-code"
87
+ placeholder="Enter 6-digit code"
88
+ class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
89
+ maxlength="6"
90
+ pattern="\d{6}"
91
+ title="Please enter a 6-digit code"
92
+ >
93
+ <button id="validate-btn" class="bg-purple-600 text-white px-4 py-2 rounded-r-lg hover:bg-purple-700 transition duration-300">
94
+ Validate
95
+ </button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Results Section -->
102
+ <div class="w-full lg:w-1/2">
103
+ <div class="bg-white p-6 rounded-xl shadow-md h-full">
104
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">Validation Results</h2>
105
+
106
+ <div id="validation-result" class="validation-result hidden">
107
+ <div id="valid-ticket" class="hidden">
108
+ <div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
109
+ <div class="flex items-center">
110
+ <i data-feather="check-circle" class="w-6 h-6 text-green-500 mr-2"></i>
111
+ <span class="text-green-700 font-medium">Valid Ticket</span>
112
+ </div>
113
+ </div>
114
+ <div class="bg-white border border-gray-200 rounded-lg p-4">
115
+ <div class="grid grid-cols-2 gap-4 mb-4">
116
+ <div>
117
+ <p class="text-sm text-gray-500">Event ID</p>
118
+ <p id="event-id" class="font-medium">05</p>
119
+ </div>
120
+ <div>
121
+ <p class="text-sm text-gray-500">OTP Code</p>
122
+ <p id="otp-code" class="font-mono font-medium">051234</p>
123
+ </div>
124
+ <div>
125
+ <p class="text-sm text-gray-500">Ticket Holder</p>
126
+ <p id="ticket-holder" class="font-medium">John Doe</p>
127
+ </div>
128
+ <div>
129
+ <p class="text-sm text-gray-500">Status</p>
130
+ <p id="ticket-status" class="font-medium">Valid</p>
131
+ </div>
132
+ </div>
133
+ <div class="text-center">
134
+ <button class="bg-green-100 text-green-700 px-4 py-2 rounded-lg text-sm font-medium">
135
+ <i data-feather="check" class="w-4 h-4 inline mr-1"></i>
136
+ Entry Approved
137
+ </button>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <div id="invalid-ticket" class="hidden">
143
+ <div class="bg-red-50 border border-red-200 rounded-lg p-4">
144
+ <div class="flex items-center">
145
+ <i data-feather="x-circle" class="w-6 h-6 text-red-500 mr-2"></i>
146
+ <span class="text-red-700 font-medium">Invalid Ticket</span>
147
+ </div>
148
+ <p class="mt-2 text-sm text-gray-600" id="invalid-reason">This ticket has already been used or doesn't exist.</p>
149
+ </div>
150
+ <div class="mt-4 text-center">
151
+ <button class="bg-red-100 text-red-700 px-4 py-2 rounded-lg text-sm font-medium">
152
+ <i data-feather="alert-triangle" class="w-4 h-4 inline mr-1"></i>
153
+ Deny Entry
154
+ </button>
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <div id="no-result" class="text-center py-12">
160
+ <div class="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
161
+ <i data-feather="search" class="w-8 h-8 text-gray-400"></i>
162
+ </div>
163
+ <h3 class="text-lg font-medium text-gray-500 mb-2">No ticket scanned yet</h3>
164
+ <p class="text-gray-400 text-sm">Scan a QR code or enter OTP manually to validate a ticket.</p>
165
+ </div>
166
+
167
+ <div class="mt-6 border-t border-gray-200 pt-4">
168
+ <h3 class="text-sm font-medium text-gray-500 mb-2">Recent Validations</h3>
169
+ <div class="space-y-2">
170
+ <div class="flex justify-between text-sm">
171
+ <span class="text-gray-700">051234</span>
172
+ <span class="text-green-600">Valid</span>
173
+ </div>
174
+ <div class="flex justify-between text-sm">
175
+ <span class="text-gray-700">056789</span>
176
+ <span class="text-red-600">Invalid</span>
177
+ </div>
178
+ <div class="flex justify-between text-sm">
179
+ <span class="text-gray-700">052345</span>
180
+ <span class="text-green-600">Valid</span>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </main>
188
+
189
+ <script>
190
+ feather.replace();
191
+
192
+ document.addEventListener('DOMContentLoaded', function() {
193
+ const video = document.getElementById('scanner-video');
194
+ const validateBtn = document.getElementById('validate-btn');
195
+ const manualCodeInput = document.getElementById('manual-code');
196
+ const validationResult = document.getElementById('validation-result');
197
+ const validTicket = document.getElementById('valid-ticket');
198
+ const invalidTicket = document.getElementById('invalid-ticket');
199
+ const noResult = document.getElementById('no-result');
200
+ const toggleCameraBtn = document.getElementById('toggle-camera');
201
+
202
+ let currentFacingMode = 'environment';
203
+ let stream = null;
204
+
205
+ // Initialize scanner
206
+ function initScanner(facingMode) {
207
+ stopScanner();
208
+
209
+ navigator.mediaDevices.getUserMedia({
210
+ video: {
211
+ facingMode: facingMode,
212
+ width: { ideal: 1280 },
213
+ height: { ideal: 720 }
214
+ },
215
+ audio: false
216
+ }).then(function(s) {
217
+ stream = s;
218
+ video.srcObject = stream;
219
+ video.play();
220
+
221
+ // Start scanning
222
+ scanQRCode();
223
+ }).catch(function(err) {
224
+ console.error("Camera error:", err);
225
+ });
226
+ }
227
+
228
+ // Stop scanner
229
+ function stopScanner() {
230
+ if (stream) {
231
+ stream.getTracks().forEach(track => track.stop());
232
+ }
233
+ }
234
+
235
+ // Scan QR code from video stream
236
+ function scanQRCode() {
237
+ const canvas = document.createElement('canvas');
238
+ const context = canvas.getContext('2d');
239
+
240
+ function tick() {
241
+ if (video.readyState === video.HAVE_ENOUGH_DATA) {
242
+ canvas.width = video.videoWidth;
243
+ canvas.height = video.videoHeight;
244
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
245
+
246
+ const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
247
+ const code = jsQR(imageData.data, imageData.width, imageData.height);
248
+
249
+ if (code) {
250
+ validateTicket(code.data);
251
+ }
252
+ }
253
+
254
+ requestAnimationFrame(tick);
255
+ }
256
+
257
+ tick();
258
+ }
259
+
260
+ // Validate ticket (mock function)
261
+ function validateTicket(code) {
262
+ // Show loading state
263
+ validationResult.classList.remove('hidden');
264
+ validTicket.classList.add('hidden');
265
+ invalidTicket.classList.add('hidden');
266
+ noResult.classList.add('hidden');
267
+
268
+ // Simulate API call
269
+ setTimeout(() => {
270
+ // Mock validation - odd numbers are valid, even are invalid
271
+ const isValid = parseInt(code) % 2 !== 0;
272
+
273
+ if (isValid) {
274
+ document.getElementById('event-id').textContent = code.substring(0, 2);
275
+ document.getElementById('otp-code').textContent = code;
276
+ document.getElementById('ticket-holder').textContent = "Ticket Holder " + code.substring(2);
277
+ document.getElementById('ticket-status').textContent = "Valid";
278
+
279
+ validTicket.classList.remove('hidden');
280
+ invalidTicket.classList.add('hidden');
281
+ } else {
282
+ document.getElementById('invalid-reason').textContent =
283
+ code.length === 6 ? "This ticket has already been used." : "Invalid code format.";
284
+
285
+ validTicket.classList.add('hidden');
286
+ invalidTicket.classList.remove('hidden');
287
+ }
288
+
289
+ noResult.classList.add('hidden');
290
+ }, 800);
291
+
292
+ // Clear manual input
293
+ manualCodeInput.value = '';
294
+ }
295
+
296
+ // Toggle camera
297
+ toggleCameraBtn.addEventListener('click', function() {
298
+ currentFacingMode = currentFacingMode === 'environment' ? 'user' : 'environment';
299
+ initScanner(currentFacingMode);
300
+ });
301
+
302
+ // Manual validation
303
+ validateBtn.addEventListener('click', function() {
304
+ const code = manualCodeInput.value.trim();
305
+ if (code.length === 6 && /^\d+$/.test(code)) {
306
+ validateTicket(code);
307
+ } else {
308
+ alert("Please enter a valid 6-digit code");
309
+ }
310
+ });
311
+
312
+ // Initialize with back camera
313
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
314
+ initScanner(currentFacingMode);
315
+ } else {
316
+ alert("Camera access not available in your browser.");
317
+ }
318
+
319
+ // Clean up on page leave
320
+ window.addEventListener('beforeunload', function() {
321
+ stopScanner();
322
+ });
323
+ });
324
+ </script>
325
+ </body>
326
+ </html>