Jofax commited on
Commit
eefc9a3
Β·
verified Β·
1 Parent(s): bc22a71

Upload 2 files

Browse files
Files changed (2) hide show
  1. hulhumale_luxury.html +886 -0
  2. hulhumale_luxury.py +761 -0
hulhumale_luxury.html ADDED
@@ -0,0 +1,886 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Hulhumale Luxury Hotel Direct</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;600;700&family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ :root {
16
+ --gold: #D4AF37;
17
+ --gold-dark: #B8941F;
18
+ --navy: #0A1828;
19
+ --navy-light: #1A2F42;
20
+ --cream: #F8F6F0;
21
+ --white: #FFFFFF;
22
+ }
23
+
24
+ body {
25
+ font-family: 'Montserrat', sans-serif;
26
+ background: linear-gradient(135deg, #0A1828 0%, #1A2F42 50%, #0A1828 100%);
27
+ min-height: 100vh;
28
+ position: relative;
29
+ overflow-x: hidden;
30
+ }
31
+
32
+ body::before {
33
+ content: '';
34
+ position: fixed;
35
+ top: 0;
36
+ left: 0;
37
+ right: 0;
38
+ bottom: 0;
39
+ background-image:
40
+ radial-gradient(circle at 20% 30%, rgba(212, 175, 55, 0.05) 0%, transparent 50%),
41
+ radial-gradient(circle at 80% 70%, rgba(212, 175, 55, 0.03) 0%, transparent 50%);
42
+ pointer-events: none;
43
+ z-index: 0;
44
+ }
45
+
46
+ .container {
47
+ max-width: 1400px;
48
+ margin: 0 auto;
49
+ padding: 20px;
50
+ position: relative;
51
+ z-index: 1;
52
+ }
53
+
54
+ /* Header */
55
+ .luxury-header {
56
+ text-align: center;
57
+ padding: 60px 20px 40px;
58
+ animation: fadeInDown 1s ease-out;
59
+ }
60
+
61
+ .luxury-header h1 {
62
+ font-family: 'Cormorant Garamond', serif;
63
+ font-size: 4.5em;
64
+ font-weight: 300;
65
+ color: var(--cream);
66
+ margin: 0 0 15px 0;
67
+ letter-spacing: 3px;
68
+ text-transform: uppercase;
69
+ }
70
+
71
+ .divider {
72
+ width: 100px;
73
+ height: 2px;
74
+ background: linear-gradient(90deg, transparent, var(--gold), transparent);
75
+ margin: 25px auto;
76
+ animation: expandWidth 1.2s ease-out 0.5s both;
77
+ }
78
+
79
+ .subtitle {
80
+ font-size: 1.1em;
81
+ color: var(--gold);
82
+ letter-spacing: 4px;
83
+ text-transform: uppercase;
84
+ font-weight: 300;
85
+ margin-top: 10px;
86
+ animation: fadeInUp 1s ease-out 0.3s both;
87
+ }
88
+
89
+ /* Animations */
90
+ @keyframes fadeInDown {
91
+ from { opacity: 0; transform: translateY(-30px); }
92
+ to { opacity: 1; transform: translateY(0); }
93
+ }
94
+
95
+ @keyframes fadeInUp {
96
+ from { opacity: 0; transform: translateY(30px); }
97
+ to { opacity: 1; transform: translateY(0); }
98
+ }
99
+
100
+ @keyframes expandWidth {
101
+ from { width: 0; }
102
+ to { width: 100px; }
103
+ }
104
+
105
+ @keyframes slideInFromTop {
106
+ from { opacity: 0; transform: translateY(-50px); }
107
+ to { opacity: 1; transform: translateY(0); }
108
+ }
109
+
110
+ @keyframes shimmer {
111
+ 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
112
+ 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
113
+ }
114
+
115
+ /* Promo Banner */
116
+ .promo-banner {
117
+ background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%);
118
+ color: var(--navy);
119
+ padding: 35px;
120
+ margin: 30px 0;
121
+ text-align: center;
122
+ position: relative;
123
+ overflow: hidden;
124
+ box-shadow: 0 10px 40px rgba(10, 24, 40, 0.3);
125
+ animation: slideInFromTop 0.8s ease-out;
126
+ display: none;
127
+ }
128
+
129
+ .promo-banner.active {
130
+ display: block;
131
+ }
132
+
133
+ .promo-banner::before {
134
+ content: '';
135
+ position: absolute;
136
+ top: -50%;
137
+ left: -50%;
138
+ width: 200%;
139
+ height: 200%;
140
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.15) 50%, transparent 70%);
141
+ animation: shimmer 3s infinite;
142
+ }
143
+
144
+ .promo-title {
145
+ font-family: 'Cormorant Garamond', serif;
146
+ font-size: 1.4em;
147
+ font-weight: 600;
148
+ letter-spacing: 2px;
149
+ text-transform: uppercase;
150
+ margin-bottom: 10px;
151
+ position: relative;
152
+ }
153
+
154
+ .promo-message {
155
+ font-size: 2em;
156
+ font-weight: 600;
157
+ margin: 15px 0;
158
+ font-family: 'Cormorant Garamond', serif;
159
+ position: relative;
160
+ }
161
+
162
+ .promo-hotel {
163
+ font-size: 1.1em;
164
+ font-weight: 600;
165
+ margin: 15px 0;
166
+ position: relative;
167
+ }
168
+
169
+ .promo-time {
170
+ font-size: 0.85em;
171
+ opacity: 0.8;
172
+ letter-spacing: 1px;
173
+ text-transform: uppercase;
174
+ position: relative;
175
+ }
176
+
177
+ /* Tabs */
178
+ .tabs {
179
+ display: flex;
180
+ gap: 15px;
181
+ margin: 30px 0 20px;
182
+ justify-content: center;
183
+ flex-wrap: wrap;
184
+ }
185
+
186
+ .tab-button {
187
+ background: transparent;
188
+ color: var(--cream);
189
+ border: 1px solid rgba(212, 175, 55, 0.3);
190
+ padding: 15px 35px;
191
+ font-size: 1em;
192
+ cursor: pointer;
193
+ transition: all 0.3s ease;
194
+ text-transform: uppercase;
195
+ letter-spacing: 1.5px;
196
+ font-weight: 500;
197
+ font-family: 'Montserrat', sans-serif;
198
+ }
199
+
200
+ .tab-button:hover {
201
+ background: rgba(212, 175, 55, 0.1);
202
+ border-color: var(--gold);
203
+ }
204
+
205
+ .tab-button.active {
206
+ background: var(--gold);
207
+ color: var(--navy);
208
+ font-weight: 600;
209
+ border: none;
210
+ }
211
+
212
+ .tab-content {
213
+ display: none;
214
+ background: rgba(248, 246, 240, 0.05);
215
+ border-radius: 15px;
216
+ padding: 40px;
217
+ backdrop-filter: blur(10px);
218
+ }
219
+
220
+ .tab-content.active {
221
+ display: block;
222
+ }
223
+
224
+ /* Filter Section */
225
+ .filter-section {
226
+ margin-bottom: 30px;
227
+ }
228
+
229
+ .filter-label {
230
+ color: var(--cream);
231
+ font-weight: 500;
232
+ text-transform: uppercase;
233
+ letter-spacing: 1px;
234
+ font-size: 0.85em;
235
+ margin-bottom: 15px;
236
+ display: block;
237
+ }
238
+
239
+ .filter-options {
240
+ display: flex;
241
+ gap: 10px;
242
+ flex-wrap: wrap;
243
+ }
244
+
245
+ .filter-radio {
246
+ display: none;
247
+ }
248
+
249
+ .filter-label-button {
250
+ color: var(--cream);
251
+ padding: 12px 20px;
252
+ border: 1px solid rgba(212, 175, 55, 0.3);
253
+ border-radius: 8px;
254
+ cursor: pointer;
255
+ transition: all 0.3s ease;
256
+ font-size: 0.95em;
257
+ }
258
+
259
+ .filter-label-button:hover {
260
+ background: rgba(212, 175, 55, 0.1);
261
+ border-color: var(--gold);
262
+ }
263
+
264
+ .filter-radio:checked + .filter-label-button {
265
+ background: var(--gold);
266
+ color: var(--navy);
267
+ border-color: var(--gold);
268
+ font-weight: 600;
269
+ }
270
+
271
+ /* Hotel Table */
272
+ .hotel-table-container {
273
+ overflow-x: auto;
274
+ background: var(--cream);
275
+ border-radius: 15px;
276
+ box-shadow: 0 15px 50px rgba(10, 24, 40, 0.3);
277
+ margin: 30px 0;
278
+ }
279
+
280
+ .hotel-table {
281
+ width: 100%;
282
+ border-collapse: collapse;
283
+ }
284
+
285
+ .hotel-table thead {
286
+ background: linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%);
287
+ }
288
+
289
+ .hotel-table th {
290
+ color: var(--gold);
291
+ font-weight: 600;
292
+ text-transform: uppercase;
293
+ letter-spacing: 1.5px;
294
+ padding: 20px;
295
+ font-size: 0.85em;
296
+ text-align: left;
297
+ }
298
+
299
+ .hotel-table td {
300
+ padding: 25px 20px;
301
+ color: var(--navy);
302
+ font-size: 0.95em;
303
+ border-bottom: 1px solid rgba(10, 24, 40, 0.08);
304
+ }
305
+
306
+ .hotel-table tbody tr {
307
+ transition: all 0.3s ease;
308
+ cursor: pointer;
309
+ }
310
+
311
+ .hotel-table tbody tr:hover {
312
+ background: rgba(212, 175, 55, 0.08);
313
+ transform: scale(1.01);
314
+ }
315
+
316
+ .status-available {
317
+ color: #2ECC71;
318
+ font-weight: 600;
319
+ }
320
+
321
+ .status-occupied {
322
+ color: #E74C3C;
323
+ font-weight: 600;
324
+ }
325
+
326
+ /* Info Box */
327
+ .info-box {
328
+ background: rgba(212, 175, 55, 0.1);
329
+ border-left: 4px solid var(--gold);
330
+ padding: 20px 25px;
331
+ border-radius: 0 8px 8px 0;
332
+ color: var(--cream);
333
+ margin: 20px 0;
334
+ font-size: 0.95em;
335
+ line-height: 1.6;
336
+ backdrop-filter: blur(5px);
337
+ }
338
+
339
+ .info-box strong {
340
+ color: var(--gold);
341
+ font-weight: 600;
342
+ }
343
+
344
+ /* Buttons */
345
+ .btn {
346
+ background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%);
347
+ color: var(--navy);
348
+ border: none;
349
+ padding: 15px 35px;
350
+ font-weight: 600;
351
+ text-transform: uppercase;
352
+ letter-spacing: 2px;
353
+ border-radius: 8px;
354
+ cursor: pointer;
355
+ transition: all 0.4s ease;
356
+ box-shadow: 0 5px 20px rgba(212, 175, 55, 0.3);
357
+ font-size: 0.9em;
358
+ position: relative;
359
+ overflow: hidden;
360
+ font-family: 'Montserrat', sans-serif;
361
+ }
362
+
363
+ .btn::before {
364
+ content: '';
365
+ position: absolute;
366
+ top: 50%;
367
+ left: 50%;
368
+ width: 0;
369
+ height: 0;
370
+ border-radius: 50%;
371
+ background: rgba(255, 255, 255, 0.3);
372
+ transform: translate(-50%, -50%);
373
+ transition: width 0.6s, height 0.6s;
374
+ }
375
+
376
+ .btn:hover::before {
377
+ width: 300px;
378
+ height: 300px;
379
+ }
380
+
381
+ .btn:hover {
382
+ transform: translateY(-2px);
383
+ box-shadow: 0 8px 30px rgba(212, 175, 55, 0.5);
384
+ }
385
+
386
+ .btn-secondary {
387
+ background: transparent;
388
+ border: 2px solid var(--gold);
389
+ color: var(--gold);
390
+ }
391
+
392
+ .btn-secondary:hover {
393
+ background: var(--gold);
394
+ color: var(--navy);
395
+ }
396
+
397
+ /* Dashboard Section */
398
+ .dashboard-section {
399
+ background: rgba(248, 246, 240, 0.05);
400
+ border: 1px solid rgba(212, 175, 55, 0.2);
401
+ border-radius: 15px;
402
+ padding: 30px;
403
+ margin: 20px 0;
404
+ backdrop-filter: blur(10px);
405
+ }
406
+
407
+ .dashboard-title {
408
+ font-family: 'Cormorant Garamond', serif;
409
+ font-size: 1.8em;
410
+ color: var(--gold);
411
+ margin-bottom: 10px;
412
+ font-weight: 600;
413
+ letter-spacing: 1px;
414
+ }
415
+
416
+ .dashboard-subtitle {
417
+ color: var(--cream);
418
+ opacity: 0.8;
419
+ font-size: 0.9em;
420
+ letter-spacing: 1px;
421
+ text-transform: uppercase;
422
+ margin-bottom: 25px;
423
+ }
424
+
425
+ .dashboard-grid {
426
+ display: grid;
427
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
428
+ gap: 30px;
429
+ margin-top: 30px;
430
+ }
431
+
432
+ /* Form Elements */
433
+ .form-group {
434
+ margin-bottom: 25px;
435
+ }
436
+
437
+ .form-label {
438
+ color: var(--cream);
439
+ font-weight: 500;
440
+ text-transform: uppercase;
441
+ letter-spacing: 1px;
442
+ font-size: 0.85em;
443
+ margin-bottom: 8px;
444
+ display: block;
445
+ }
446
+
447
+ .form-input,
448
+ .form-select,
449
+ .form-textarea {
450
+ width: 100%;
451
+ background: rgba(248, 246, 240, 0.95);
452
+ border: 1px solid rgba(212, 175, 55, 0.3);
453
+ color: var(--navy);
454
+ border-radius: 8px;
455
+ padding: 12px 15px;
456
+ font-family: 'Montserrat', sans-serif;
457
+ font-size: 0.95em;
458
+ transition: all 0.3s ease;
459
+ }
460
+
461
+ .form-input:focus,
462
+ .form-select:focus,
463
+ .form-textarea:focus {
464
+ border-color: var(--gold);
465
+ box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.15);
466
+ outline: none;
467
+ }
468
+
469
+ .form-textarea {
470
+ resize: vertical;
471
+ min-height: 100px;
472
+ }
473
+
474
+ /* Success Message */
475
+ .success-message {
476
+ background: linear-gradient(135deg, rgba(46, 204, 113, 0.2) 0%, rgba(46, 204, 113, 0.1) 100%);
477
+ border: 1px solid rgba(46, 204, 113, 0.5);
478
+ color: #2ECC71;
479
+ padding: 20px;
480
+ border-radius: 8px;
481
+ text-align: center;
482
+ font-weight: 600;
483
+ letter-spacing: 1px;
484
+ margin-top: 20px;
485
+ display: none;
486
+ }
487
+
488
+ .success-message.show {
489
+ display: block;
490
+ animation: slideInFromRight 0.5s ease-out;
491
+ }
492
+
493
+ @keyframes slideInFromRight {
494
+ from { opacity: 0; transform: translateX(30px); }
495
+ to { opacity: 1; transform: translateX(0); }
496
+ }
497
+
498
+ /* Footer */
499
+ .luxury-footer {
500
+ text-align: center;
501
+ margin-top: 80px;
502
+ padding: 40px 20px;
503
+ border-top: 1px solid rgba(212, 175, 55, 0.2);
504
+ color: var(--cream);
505
+ opacity: 0.6;
506
+ font-size: 0.85em;
507
+ letter-spacing: 1.5px;
508
+ text-transform: uppercase;
509
+ }
510
+
511
+ /* Responsive */
512
+ @media (max-width: 768px) {
513
+ .luxury-header h1 {
514
+ font-size: 2.5em;
515
+ }
516
+
517
+ .promo-message {
518
+ font-size: 1.4em;
519
+ }
520
+
521
+ .tabs {
522
+ flex-direction: column;
523
+ }
524
+
525
+ .tab-button {
526
+ width: 100%;
527
+ }
528
+
529
+ .dashboard-grid {
530
+ grid-template-columns: 1fr;
531
+ }
532
+
533
+ .hotel-table {
534
+ font-size: 0.85em;
535
+ }
536
+
537
+ .hotel-table th,
538
+ .hotel-table td {
539
+ padding: 15px 10px;
540
+ }
541
+ }
542
+
543
+ /* Scrollbar */
544
+ ::-webkit-scrollbar {
545
+ width: 10px;
546
+ }
547
+
548
+ ::-webkit-scrollbar-track {
549
+ background: var(--navy);
550
+ }
551
+
552
+ ::-webkit-scrollbar-thumb {
553
+ background: var(--gold);
554
+ border-radius: 5px;
555
+ }
556
+
557
+ ::-webkit-scrollbar-thumb:hover {
558
+ background: var(--gold-dark);
559
+ }
560
+ </style>
561
+ </head>
562
+ <body>
563
+ <div class="container">
564
+ <!-- Header -->
565
+ <div class="luxury-header">
566
+ <h1>HULHUMALE DIRECT</h1>
567
+ <div class="divider"></div>
568
+ <div class="subtitle">Luxury Accommodations β€’ Direct Booking β€’ Zero Commission</div>
569
+ </div>
570
+
571
+ <!-- Promo Banner -->
572
+ <div id="promoBanner" class="promo-banner">
573
+ <div class="promo-title">⚑ Exclusive Flash Offer</div>
574
+ <div class="promo-message" id="promoMessage"></div>
575
+ <div class="promo-hotel" id="promoHotel"></div>
576
+ <div class="promo-time" id="promoTime"></div>
577
+ </div>
578
+
579
+ <!-- Tabs -->
580
+ <div class="tabs">
581
+ <button class="tab-button active" onclick="switchTab('discover')">πŸ” Discover Your Stay</button>
582
+ <button class="tab-button" onclick="switchTab('management')">βš™οΈ Property Management Portal</button>
583
+ </div>
584
+
585
+ <!-- Discover Tab -->
586
+ <div id="discoverTab" class="tab-content active">
587
+ <div class="filter-section">
588
+ <label class="filter-label">Filter by Destination</label>
589
+ <div class="filter-options">
590
+ <input type="radio" name="location" id="all" class="filter-radio" value="All" checked onchange="filterHotels()">
591
+ <label for="all" class="filter-label-button">All</label>
592
+
593
+ <input type="radio" name="location" id="male" class="filter-radio" value="Male'" onchange="filterHotels()">
594
+ <label for="male" class="filter-label-button">Male' City</label>
595
+
596
+ <input type="radio" name="location" id="hulhumale" class="filter-radio" value="Hulhumale" onchange="filterHotels()">
597
+ <label for="hulhumale" class="filter-label-button">Hulhumale</label>
598
+
599
+ <input type="radio" name="location" id="villingili" class="filter-radio" value="Villingili" onchange="filterHotels()">
600
+ <label for="villingili" class="filter-label-button">Villingili</label>
601
+ </div>
602
+ </div>
603
+
604
+ <div class="hotel-table-container">
605
+ <table class="hotel-table" id="hotelTable">
606
+ <thead>
607
+ <tr>
608
+ <th>🏨 Hotel Name</th>
609
+ <th>πŸ“ Location</th>
610
+ <th>πŸ’° Price/Night</th>
611
+ <th>⭐ Rating</th>
612
+ <th>βœ… Status</th>
613
+ <th>πŸ“ž Direct Contact</th>
614
+ </tr>
615
+ </thead>
616
+ <tbody id="hotelTableBody">
617
+ <!-- Data will be populated by JavaScript -->
618
+ </tbody>
619
+ </table>
620
+ </div>
621
+
622
+ <div class="info-box">
623
+ <strong>🌟 How to Book:</strong> Contact hotels directly using the numbers listed above.
624
+ Speak with owners personally, negotiate rates, and enjoy commission-free bookings.
625
+ All properties are verified and family-operated.
626
+ </div>
627
+
628
+ <button class="btn btn-secondary" onclick="refreshData()">πŸ”„ Refresh Availability</button>
629
+ <div id="refreshStatus" class="success-message"></div>
630
+ </div>
631
+
632
+ <!-- Management Tab -->
633
+ <div id="managementTab" class="tab-content">
634
+ <div class="dashboard-section">
635
+ <div class="dashboard-title">Welcome to Your Dashboard</div>
636
+ <div class="dashboard-subtitle">Premium Client Portal β€’ 350 MVR per Month</div>
637
+ </div>
638
+
639
+ <div class="dashboard-grid">
640
+ <!-- Update Listing Section -->
641
+ <div class="dashboard-section">
642
+ <h3 style="font-family: 'Cormorant Garamond', serif; color: var(--gold); font-size: 1.5em; margin-bottom: 20px;">πŸ“Š Update Your Listing</h3>
643
+
644
+ <div class="form-group">
645
+ <label class="form-label">Select Your Property</label>
646
+ <select id="ownerHotel" class="form-select">
647
+ <option value="">Choose a property...</option>
648
+ <option value="Coral Reef View Resort">Coral Reef View Resort</option>
649
+ <option value="Male' Grand Stay Hotel">Male' Grand Stay Hotel</option>
650
+ <option value="Villi Blue Inn">Villi Blue Inn</option>
651
+ <option value="Azure Lagoon Suites">Azure Lagoon Suites</option>
652
+ <option value="Paradise Isle Boutique">Paradise Isle Boutique</option>
653
+ </select>
654
+ </div>
655
+
656
+ <div class="form-group">
657
+ <label class="form-label">Current Availability Status</label>
658
+ <div class="filter-options">
659
+ <input type="radio" name="status" id="statusAvailable" class="filter-radio" value="Available" checked>
660
+ <label for="statusAvailable" class="filter-label-button">Available</label>
661
+
662
+ <input type="radio" name="status" id="statusOccupied" class="filter-radio" value="Occupied">
663
+ <label for="statusOccupied" class="filter-label-button">Occupied</label>
664
+ </div>
665
+ </div>
666
+
667
+ <div class="form-group">
668
+ <label class="form-label">Tonight's Rate (MVR)</label>
669
+ <input type="text" id="updatePrice" class="form-input" placeholder="e.g., 850">
670
+ </div>
671
+
672
+ <button class="btn" onclick="updateListing()">πŸ’Ύ Update Live Listing</button>
673
+ <div id="updateStatus" class="success-message"></div>
674
+ </div>
675
+
676
+ <!-- Marketing Section -->
677
+ <div class="dashboard-section">
678
+ <h3 style="font-family: 'Cormorant Garamond', serif; color: var(--gold); font-size: 1.5em; margin-bottom: 20px;">πŸ“£ Marketing & Promotions</h3>
679
+
680
+ <div class="info-box" style="margin-bottom: 25px;">
681
+ Broadcast instant promotions to all active users browsing the platform.
682
+ Perfect for last-minute deals, early bird specials, or flash sales.
683
+ </div>
684
+
685
+ <div class="form-group">
686
+ <label class="form-label">Property Name</label>
687
+ <select id="promoHotelSelect" class="form-select">
688
+ <option value="">Choose a property...</option>
689
+ <option value="Coral Reef View Resort">Coral Reef View Resort</option>
690
+ <option value="Male' Grand Stay Hotel">Male' Grand Stay Hotel</option>
691
+ <option value="Villi Blue Inn">Villi Blue Inn</option>
692
+ <option value="Azure Lagoon Suites">Azure Lagoon Suites</option>
693
+ <option value="Paradise Isle Boutique">Paradise Isle Boutique</option>
694
+ </select>
695
+ </div>
696
+
697
+ <div class="form-group">
698
+ <label class="form-label">Promotion Message</label>
699
+ <textarea id="promoText" class="form-textarea" placeholder="e.g., 20% OFF for airport arrivals before midnight!"></textarea>
700
+ </div>
701
+
702
+ <button class="btn" onclick="broadcastPromo()">πŸš€ Broadcast Promotion</button>
703
+ <div id="promoStatus" class="success-message"></div>
704
+ </div>
705
+ </div>
706
+ </div>
707
+
708
+ <!-- Footer -->
709
+ <div class="luxury-footer">
710
+ <div style="margin-bottom: 10px;">✦ ✦ ✦</div>
711
+ Hulhumale Direct β€’ Connecting Travelers With Local Hospitality Since 2024
712
+ <div style="margin-top: 10px; font-size: 0.75em; opacity: 0.5;">
713
+ A Premium Local Marketplace
714
+ </div>
715
+ </div>
716
+ </div>
717
+
718
+ <script>
719
+ // Hotel Data
720
+ const hotelData = [
721
+ {
722
+ name: "Coral Reef View Resort",
723
+ location: "Hulhumale Phase 1",
724
+ price: "850 MVR",
725
+ rating: "4.8/5.0",
726
+ status: "Available",
727
+ contact: "+960 777-1234"
728
+ },
729
+ {
730
+ name: "Male' Grand Stay Hotel",
731
+ location: "Male' City Center",
732
+ price: "1,100 MVR",
733
+ rating: "4.6/5.0",
734
+ status: "Available",
735
+ contact: "+960 778-5678"
736
+ },
737
+ {
738
+ name: "Villi Blue Inn",
739
+ location: "Villingili Island",
740
+ price: "650 MVR",
741
+ rating: "4.5/5.0",
742
+ status: "Occupied",
743
+ contact: "+960 779-9101"
744
+ },
745
+ {
746
+ name: "Azure Lagoon Suites",
747
+ location: "Hulhumale Phase 2",
748
+ price: "950 MVR",
749
+ rating: "4.9/5.0",
750
+ status: "Available",
751
+ contact: "+960 780-2345"
752
+ },
753
+ {
754
+ name: "Paradise Isle Boutique",
755
+ location: "Male' Waterfront",
756
+ price: "1,250 MVR",
757
+ rating: "4.7/5.0",
758
+ status: "Available",
759
+ contact: "+960 781-6789"
760
+ }
761
+ ];
762
+
763
+ // Initialize
764
+ document.addEventListener('DOMContentLoaded', function() {
765
+ renderHotels(hotelData);
766
+ });
767
+
768
+ // Render Hotels
769
+ function renderHotels(hotels) {
770
+ const tbody = document.getElementById('hotelTableBody');
771
+ tbody.innerHTML = '';
772
+
773
+ hotels.forEach(hotel => {
774
+ const row = document.createElement('tr');
775
+ const statusClass = hotel.status === 'Available' ? 'status-available' : 'status-occupied';
776
+
777
+ row.innerHTML = `
778
+ <td><strong>${hotel.name}</strong></td>
779
+ <td>${hotel.location}</td>
780
+ <td><strong>${hotel.price}</strong></td>
781
+ <td>${hotel.rating}</td>
782
+ <td class="${statusClass}">${hotel.status}</td>
783
+ <td><strong>${hotel.contact}</strong></td>
784
+ `;
785
+
786
+ tbody.appendChild(row);
787
+ });
788
+ }
789
+
790
+ // Filter Hotels
791
+ function filterHotels() {
792
+ const selectedLocation = document.querySelector('input[name="location"]:checked').value;
793
+ let filteredHotels = hotelData;
794
+
795
+ if (selectedLocation !== 'All') {
796
+ filteredHotels = hotelData.filter(hotel =>
797
+ hotel.location.includes(selectedLocation)
798
+ );
799
+ }
800
+
801
+ renderHotels(filteredHotels);
802
+ }
803
+
804
+ // Switch Tabs
805
+ function switchTab(tabName) {
806
+ // Hide all tabs
807
+ document.querySelectorAll('.tab-content').forEach(tab => {
808
+ tab.classList.remove('active');
809
+ });
810
+
811
+ // Remove active class from all buttons
812
+ document.querySelectorAll('.tab-button').forEach(btn => {
813
+ btn.classList.remove('active');
814
+ });
815
+
816
+ // Show selected tab
817
+ if (tabName === 'discover') {
818
+ document.getElementById('discoverTab').classList.add('active');
819
+ document.querySelectorAll('.tab-button')[0].classList.add('active');
820
+ } else {
821
+ document.getElementById('managementTab').classList.add('active');
822
+ document.querySelectorAll('.tab-button')[1].classList.add('active');
823
+ }
824
+ }
825
+
826
+ // Refresh Data
827
+ function refreshData() {
828
+ renderHotels(hotelData);
829
+ showMessage('refreshStatus', '✨ Availability refreshed successfully!');
830
+ }
831
+
832
+ // Update Listing
833
+ function updateListing() {
834
+ const hotel = document.getElementById('ownerHotel').value;
835
+ const status = document.querySelector('input[name="status"]:checked').value;
836
+ const price = document.getElementById('updatePrice').value;
837
+
838
+ if (!hotel) {
839
+ alert('⚠️ Please select your property first.');
840
+ return;
841
+ }
842
+
843
+ const message = `βœ… <strong>${hotel}</strong> updated successfully!<br>Status: ${status} | Price: ${price} MVR/night`;
844
+ showMessage('updateStatus', message);
845
+ }
846
+
847
+ // Broadcast Promo
848
+ function broadcastPromo() {
849
+ const hotel = document.getElementById('promoHotelSelect').value;
850
+ const message = document.getElementById('promoText').value;
851
+
852
+ if (!hotel || !message) {
853
+ alert('⚠️ Please select a hotel and enter a promotion message.');
854
+ return;
855
+ }
856
+
857
+ // Show promo banner
858
+ const now = new Date();
859
+ const time = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
860
+ const date = now.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
861
+
862
+ document.getElementById('promoMessage').textContent = message;
863
+ document.getElementById('promoHotel').textContent = `πŸ“ ${hotel}`;
864
+ document.getElementById('promoTime').textContent = `Posted ${time} β€’ ${date} β€’ Valid Tonight Only`;
865
+ document.getElementById('promoBanner').classList.add('active');
866
+
867
+ // Show success message
868
+ showMessage('promoStatus', 'βœ… Promotion successfully broadcast to all users!');
869
+
870
+ // Scroll to top
871
+ window.scrollTo({ top: 0, behavior: 'smooth' });
872
+ }
873
+
874
+ // Show Message Helper
875
+ function showMessage(elementId, message) {
876
+ const element = document.getElementById(elementId);
877
+ element.innerHTML = message;
878
+ element.classList.add('show');
879
+
880
+ setTimeout(() => {
881
+ element.classList.remove('show');
882
+ }, 5000);
883
+ }
884
+ </script>
885
+ </body>
886
+ </html>
hulhumale_luxury.py ADDED
@@ -0,0 +1,761 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from datetime import datetime
4
+
5
+ # --- LUXURY CUSTOM CSS ---
6
+ custom_css = """
7
+ @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;600;700&family=Montserrat:wght@300;400;500;600&display=swap');
8
+
9
+ :root {
10
+ --gold: #D4AF37;
11
+ --gold-dark: #B8941F;
12
+ --navy: #0A1828;
13
+ --navy-light: #1A2F42;
14
+ --cream: #F8F6F0;
15
+ --white: #FFFFFF;
16
+ --shadow: rgba(10, 24, 40, 0.15);
17
+ --shadow-heavy: rgba(10, 24, 40, 0.3);
18
+ }
19
+
20
+ .gradio-container {
21
+ background: linear-gradient(135deg, #0A1828 0%, #1A2F42 50%, #0A1828 100%) !important;
22
+ font-family: 'Montserrat', sans-serif !important;
23
+ position: relative;
24
+ overflow-x: hidden;
25
+ }
26
+
27
+ .gradio-container::before {
28
+ content: '';
29
+ position: fixed;
30
+ top: 0;
31
+ left: 0;
32
+ right: 0;
33
+ bottom: 0;
34
+ background-image:
35
+ radial-gradient(circle at 20% 30%, rgba(212, 175, 55, 0.05) 0%, transparent 50%),
36
+ radial-gradient(circle at 80% 70%, rgba(212, 175, 55, 0.03) 0%, transparent 50%);
37
+ pointer-events: none;
38
+ z-index: 0;
39
+ }
40
+
41
+ /* Header Styling */
42
+ .luxury-header {
43
+ text-align: center;
44
+ padding: 60px 20px 40px;
45
+ position: relative;
46
+ z-index: 1;
47
+ }
48
+
49
+ .luxury-header h1 {
50
+ font-family: 'Cormorant Garamond', serif !important;
51
+ font-size: 4.5em !important;
52
+ font-weight: 300 !important;
53
+ color: var(--cream) !important;
54
+ margin: 0 0 15px 0 !important;
55
+ letter-spacing: 3px !important;
56
+ text-transform: uppercase;
57
+ animation: fadeInDown 1s ease-out;
58
+ }
59
+
60
+ .luxury-header .subtitle {
61
+ font-size: 1.1em;
62
+ color: var(--gold);
63
+ letter-spacing: 4px;
64
+ text-transform: uppercase;
65
+ font-weight: 300;
66
+ margin-top: 10px;
67
+ animation: fadeInUp 1s ease-out 0.3s both;
68
+ }
69
+
70
+ .luxury-header .divider {
71
+ width: 100px;
72
+ height: 2px;
73
+ background: linear-gradient(90deg, transparent, var(--gold), transparent);
74
+ margin: 25px auto;
75
+ animation: expandWidth 1.2s ease-out 0.5s both;
76
+ }
77
+
78
+ @keyframes fadeInDown {
79
+ from {
80
+ opacity: 0;
81
+ transform: translateY(-30px);
82
+ }
83
+ to {
84
+ opacity: 1;
85
+ transform: translateY(0);
86
+ }
87
+ }
88
+
89
+ @keyframes fadeInUp {
90
+ from {
91
+ opacity: 0;
92
+ transform: translateY(30px);
93
+ }
94
+ to {
95
+ opacity: 1;
96
+ transform: translateY(0);
97
+ }
98
+ }
99
+
100
+ @keyframes expandWidth {
101
+ from {
102
+ width: 0;
103
+ }
104
+ to {
105
+ width: 100px;
106
+ }
107
+ }
108
+
109
+ /* Promotion Banner */
110
+ .promo-banner {
111
+ background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%);
112
+ color: var(--navy);
113
+ padding: 35px;
114
+ border-radius: 0;
115
+ margin: 30px 0;
116
+ text-align: center;
117
+ position: relative;
118
+ overflow: hidden;
119
+ box-shadow: 0 10px 40px var(--shadow-heavy);
120
+ animation: slideInFromTop 0.8s ease-out;
121
+ }
122
+
123
+ .promo-banner::before {
124
+ content: '';
125
+ position: absolute;
126
+ top: -50%;
127
+ left: -50%;
128
+ width: 200%;
129
+ height: 200%;
130
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.15) 50%, transparent 70%);
131
+ animation: shimmer 3s infinite;
132
+ }
133
+
134
+ @keyframes shimmer {
135
+ 0% {
136
+ transform: translateX(-100%) translateY(-100%) rotate(45deg);
137
+ }
138
+ 100% {
139
+ transform: translateX(100%) translateY(100%) rotate(45deg);
140
+ }
141
+ }
142
+
143
+ @keyframes slideInFromTop {
144
+ from {
145
+ opacity: 0;
146
+ transform: translateY(-50px);
147
+ }
148
+ to {
149
+ opacity: 1;
150
+ transform: translateY(0);
151
+ }
152
+ }
153
+
154
+ .promo-banner .promo-title {
155
+ font-family: 'Cormorant Garamond', serif;
156
+ font-size: 1.4em;
157
+ font-weight: 600;
158
+ letter-spacing: 2px;
159
+ text-transform: uppercase;
160
+ margin-bottom: 10px;
161
+ }
162
+
163
+ .promo-banner .promo-message {
164
+ font-size: 2em;
165
+ font-weight: 600;
166
+ margin: 15px 0;
167
+ font-family: 'Cormorant Garamond', serif;
168
+ }
169
+
170
+ .promo-banner .promo-time {
171
+ font-size: 0.85em;
172
+ opacity: 0.8;
173
+ letter-spacing: 1px;
174
+ text-transform: uppercase;
175
+ }
176
+
177
+ /* Tab Styling */
178
+ .tabitem {
179
+ background: rgba(248, 246, 240, 0.05) !important;
180
+ border: none !important;
181
+ border-radius: 15px !important;
182
+ padding: 40px !important;
183
+ margin-top: 20px !important;
184
+ backdrop-filter: blur(10px);
185
+ }
186
+
187
+ button.selected {
188
+ background: var(--gold) !important;
189
+ color: var(--navy) !important;
190
+ font-weight: 600 !important;
191
+ border: none !important;
192
+ letter-spacing: 1px !important;
193
+ }
194
+
195
+ .tabs button {
196
+ color: var(--cream) !important;
197
+ font-size: 1em !important;
198
+ padding: 15px 35px !important;
199
+ border: 1px solid rgba(212, 175, 55, 0.3) !important;
200
+ background: transparent !important;
201
+ transition: all 0.3s ease !important;
202
+ text-transform: uppercase;
203
+ letter-spacing: 1.5px;
204
+ font-weight: 500;
205
+ }
206
+
207
+ .tabs button:hover {
208
+ background: rgba(212, 175, 55, 0.1) !important;
209
+ border-color: var(--gold) !important;
210
+ }
211
+
212
+ /* Hotel Cards - Luxury Table Styling */
213
+ .dataframe {
214
+ background: var(--cream) !important;
215
+ border-radius: 15px !important;
216
+ overflow: hidden !important;
217
+ box-shadow: 0 15px 50px var(--shadow-heavy) !important;
218
+ font-family: 'Montserrat', sans-serif !important;
219
+ border: 1px solid rgba(212, 175, 55, 0.2) !important;
220
+ }
221
+
222
+ .dataframe thead {
223
+ background: linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%) !important;
224
+ }
225
+
226
+ .dataframe thead th {
227
+ color: var(--gold) !important;
228
+ font-weight: 600 !important;
229
+ text-transform: uppercase !important;
230
+ letter-spacing: 1.5px !important;
231
+ padding: 20px !important;
232
+ font-size: 0.85em !important;
233
+ border: none !important;
234
+ }
235
+
236
+ .dataframe tbody td {
237
+ padding: 25px 20px !important;
238
+ color: var(--navy) !important;
239
+ font-size: 0.95em !important;
240
+ border-bottom: 1px solid rgba(10, 24, 40, 0.08) !important;
241
+ transition: all 0.3s ease;
242
+ }
243
+
244
+ .dataframe tbody tr {
245
+ transition: all 0.3s ease;
246
+ }
247
+
248
+ .dataframe tbody tr:hover {
249
+ background: rgba(212, 175, 55, 0.08) !important;
250
+ transform: scale(1.01);
251
+ }
252
+
253
+ /* Input Styling */
254
+ .gr-box,
255
+ input,
256
+ textarea,
257
+ select {
258
+ background: rgba(248, 246, 240, 0.95) !important;
259
+ border: 1px solid rgba(212, 175, 55, 0.3) !important;
260
+ color: var(--navy) !important;
261
+ border-radius: 8px !important;
262
+ transition: all 0.3s ease !important;
263
+ font-family: 'Montserrat', sans-serif !important;
264
+ }
265
+
266
+ .gr-box:focus,
267
+ input:focus,
268
+ textarea:focus,
269
+ select:focus {
270
+ border-color: var(--gold) !important;
271
+ box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.15) !important;
272
+ outline: none !important;
273
+ }
274
+
275
+ label {
276
+ color: var(--cream) !important;
277
+ font-weight: 500 !important;
278
+ text-transform: uppercase !important;
279
+ letter-spacing: 1px !important;
280
+ font-size: 0.85em !important;
281
+ margin-bottom: 8px !important;
282
+ }
283
+
284
+ /* Radio Buttons */
285
+ .gr-radio {
286
+ background: transparent !important;
287
+ }
288
+
289
+ .gr-radio label {
290
+ color: var(--cream) !important;
291
+ padding: 12px 20px !important;
292
+ border: 1px solid rgba(212, 175, 55, 0.3) !important;
293
+ border-radius: 8px !important;
294
+ margin: 5px !important;
295
+ transition: all 0.3s ease !important;
296
+ cursor: pointer !important;
297
+ text-transform: none !important;
298
+ letter-spacing: 0.5px !important;
299
+ }
300
+
301
+ .gr-radio label:hover {
302
+ background: rgba(212, 175, 55, 0.1) !important;
303
+ border-color: var(--gold) !important;
304
+ }
305
+
306
+ .gr-radio input:checked + label {
307
+ background: var(--gold) !important;
308
+ color: var(--navy) !important;
309
+ border-color: var(--gold) !important;
310
+ font-weight: 600 !important;
311
+ }
312
+
313
+ /* Buttons */
314
+ .gr-button {
315
+ background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%) !important;
316
+ color: var(--navy) !important;
317
+ border: none !important;
318
+ padding: 15px 35px !important;
319
+ font-weight: 600 !important;
320
+ text-transform: uppercase !important;
321
+ letter-spacing: 2px !important;
322
+ border-radius: 8px !important;
323
+ transition: all 0.4s ease !important;
324
+ box-shadow: 0 5px 20px rgba(212, 175, 55, 0.3) !important;
325
+ font-size: 0.9em !important;
326
+ position: relative !important;
327
+ overflow: hidden !important;
328
+ }
329
+
330
+ .gr-button::before {
331
+ content: '';
332
+ position: absolute;
333
+ top: 50%;
334
+ left: 50%;
335
+ width: 0;
336
+ height: 0;
337
+ border-radius: 50%;
338
+ background: rgba(255, 255, 255, 0.3);
339
+ transform: translate(-50%, -50%);
340
+ transition: width 0.6s, height 0.6s;
341
+ }
342
+
343
+ .gr-button:hover::before {
344
+ width: 300px;
345
+ height: 300px;
346
+ }
347
+
348
+ .gr-button:hover {
349
+ transform: translateY(-2px) !important;
350
+ box-shadow: 0 8px 30px rgba(212, 175, 55, 0.5) !important;
351
+ }
352
+
353
+ .gr-button-secondary {
354
+ background: transparent !important;
355
+ border: 2px solid var(--gold) !important;
356
+ color: var(--gold) !important;
357
+ }
358
+
359
+ .gr-button-secondary:hover {
360
+ background: var(--gold) !important;
361
+ color: var(--navy) !important;
362
+ }
363
+
364
+ /* Info Box */
365
+ .info-box {
366
+ background: rgba(212, 175, 55, 0.1);
367
+ border-left: 4px solid var(--gold);
368
+ padding: 20px 25px;
369
+ border-radius: 0 8px 8px 0;
370
+ color: var(--cream);
371
+ margin: 20px 0;
372
+ font-size: 0.95em;
373
+ line-height: 1.6;
374
+ backdrop-filter: blur(5px);
375
+ }
376
+
377
+ .info-box strong {
378
+ color: var(--gold);
379
+ font-weight: 600;
380
+ }
381
+
382
+ /* Dashboard Cards */
383
+ .dashboard-section {
384
+ background: rgba(248, 246, 240, 0.05);
385
+ border: 1px solid rgba(212, 175, 55, 0.2);
386
+ border-radius: 15px;
387
+ padding: 30px;
388
+ margin: 20px 0;
389
+ backdrop-filter: blur(10px);
390
+ }
391
+
392
+ .dashboard-title {
393
+ font-family: 'Cormorant Garamond', serif;
394
+ font-size: 1.8em;
395
+ color: var(--gold);
396
+ margin-bottom: 10px;
397
+ font-weight: 600;
398
+ letter-spacing: 1px;
399
+ }
400
+
401
+ .dashboard-subtitle {
402
+ color: var(--cream);
403
+ opacity: 0.8;
404
+ font-size: 0.9em;
405
+ letter-spacing: 1px;
406
+ text-transform: uppercase;
407
+ margin-bottom: 25px;
408
+ }
409
+
410
+ /* Footer */
411
+ .luxury-footer {
412
+ text-align: center;
413
+ margin-top: 80px;
414
+ padding: 40px 20px;
415
+ border-top: 1px solid rgba(212, 175, 55, 0.2);
416
+ color: var(--cream);
417
+ opacity: 0.6;
418
+ font-size: 0.85em;
419
+ letter-spacing: 1.5px;
420
+ text-transform: uppercase;
421
+ }
422
+
423
+ /* Status Badge */
424
+ .status-available {
425
+ color: #2ECC71;
426
+ font-weight: 600;
427
+ }
428
+
429
+ .status-occupied {
430
+ color: #E74C3C;
431
+ font-weight: 600;
432
+ }
433
+
434
+ /* Markdown Styling */
435
+ .markdown-text {
436
+ color: var(--cream) !important;
437
+ line-height: 1.8 !important;
438
+ }
439
+
440
+ .markdown-text h3 {
441
+ font-family: 'Cormorant Garamond', serif !important;
442
+ color: var(--gold) !important;
443
+ font-size: 1.6em !important;
444
+ margin-bottom: 15px !important;
445
+ font-weight: 600 !important;
446
+ }
447
+
448
+ .markdown-text p {
449
+ color: var(--cream) !important;
450
+ opacity: 0.9 !important;
451
+ }
452
+
453
+ /* Success Message */
454
+ .success-message {
455
+ background: linear-gradient(135deg, rgba(46, 204, 113, 0.2) 0%, rgba(46, 204, 113, 0.1) 100%);
456
+ border: 1px solid rgba(46, 204, 113, 0.5);
457
+ color: #2ECC71;
458
+ padding: 20px;
459
+ border-radius: 8px;
460
+ text-align: center;
461
+ font-weight: 600;
462
+ letter-spacing: 1px;
463
+ animation: slideInFromRight 0.5s ease-out;
464
+ }
465
+
466
+ @keyframes slideInFromRight {
467
+ from {
468
+ opacity: 0;
469
+ transform: translateX(30px);
470
+ }
471
+ to {
472
+ opacity: 1;
473
+ transform: translateX(0);
474
+ }
475
+ }
476
+
477
+ /* Responsive Design */
478
+ @media (max-width: 768px) {
479
+ .luxury-header h1 {
480
+ font-size: 2.5em !important;
481
+ }
482
+
483
+ .promo-banner .promo-message {
484
+ font-size: 1.4em;
485
+ }
486
+
487
+ .tabs button {
488
+ padding: 12px 20px !important;
489
+ font-size: 0.85em !important;
490
+ }
491
+ }
492
+
493
+ /* Scrollbar Styling */
494
+ ::-webkit-scrollbar {
495
+ width: 10px;
496
+ }
497
+
498
+ ::-webkit-scrollbar-track {
499
+ background: var(--navy);
500
+ }
501
+
502
+ ::-webkit-scrollbar-thumb {
503
+ background: var(--gold);
504
+ border-radius: 5px;
505
+ }
506
+
507
+ ::-webkit-scrollbar-thumb:hover {
508
+ background: var(--gold-dark);
509
+ }
510
+ """
511
+
512
+ # --- ENHANCED DATA ---
513
+ data = {
514
+ "🏨 Hotel Name": [
515
+ "Coral Reef View Resort",
516
+ "Male' Grand Stay Hotel",
517
+ "Villi Blue Inn",
518
+ "Azure Lagoon Suites",
519
+ "Paradise Isle Boutique"
520
+ ],
521
+ "πŸ“ Location": [
522
+ "Hulhumale Phase 1",
523
+ "Male' City Center",
524
+ "Villingili Island",
525
+ "Hulhumale Phase 2",
526
+ "Male' Waterfront"
527
+ ],
528
+ "πŸ’° Price/Night": [
529
+ "850 MVR",
530
+ "1,100 MVR",
531
+ "650 MVR",
532
+ "950 MVR",
533
+ "1,250 MVR"
534
+ ],
535
+ "⭐ Rating": [
536
+ "4.8/5.0",
537
+ "4.6/5.0",
538
+ "4.5/5.0",
539
+ "4.9/5.0",
540
+ "4.7/5.0"
541
+ ],
542
+ "βœ… Status": [
543
+ "Available",
544
+ "Available",
545
+ "Occupied",
546
+ "Available",
547
+ "Available"
548
+ ],
549
+ "πŸ“ž Direct Contact": [
550
+ "+960 777-1234",
551
+ "+960 778-5678",
552
+ "+960 779-9101",
553
+ "+960 780-2345",
554
+ "+960 781-6789"
555
+ ]
556
+ }
557
+ df = pd.DataFrame(data)
558
+
559
+ # --- FUNCTIONS ---
560
+ def filter_hotels(location):
561
+ if location == "All":
562
+ return df
563
+ elif location == "Male' City":
564
+ filtered = df[df["πŸ“ Location"].str.contains("Male'", case=False, na=False)]
565
+ elif location == "Hulhumale":
566
+ filtered = df[df["πŸ“ Location"].str.contains("Hulhumale", case=False, na=False)]
567
+ elif location == "Villingili":
568
+ filtered = df[df["πŸ“ Location"].str.contains("Villingili", case=False, na=False)]
569
+ else:
570
+ filtered = df
571
+ return filtered
572
+
573
+ def refresh_data():
574
+ return df, "✨ Availability refreshed successfully!"
575
+
576
+ def send_promotion(hotel, message):
577
+ if not hotel or not message:
578
+ return gr.update(visible=False), "⚠️ Please select a hotel and enter a promotion message."
579
+
580
+ time_now = datetime.now().strftime("%I:%M %p")
581
+ date_now = datetime.now().strftime("%B %d, %Y")
582
+
583
+ promo_html = f"""
584
+ <div class='promo-banner'>
585
+ <div class='promo-title'>⚑ Exclusive Flash Offer</div>
586
+ <div class='promo-message'>{message}</div>
587
+ <div style='margin: 15px 0; font-size: 1.1em; font-weight: 600;'>πŸ“ {hotel}</div>
588
+ <div class='promo-time'>Posted {time_now} β€’ {date_now} β€’ Valid Tonight Only</div>
589
+ </div>
590
+ """
591
+
592
+ return gr.update(value=promo_html, visible=True), f"<div class='success-message'>βœ… Promotion successfully broadcast to all users!</div>"
593
+
594
+ def update_listing(hotel, status, price):
595
+ if not hotel:
596
+ return "⚠️ Please select your property first."
597
+
598
+ success_msg = f"""
599
+ <div class='success-message'>
600
+ βœ… <strong>{hotel}</strong> updated successfully!<br>
601
+ Status: {status} | Price: {price} MVR/night
602
+ </div>
603
+ """
604
+ return success_msg
605
+
606
+ # --- UI LAYOUT ---
607
+ with gr.Blocks(css=custom_css, title="Hulhumale Luxury Hotel Direct", theme=gr.themes.Base()) as demo:
608
+
609
+ # Luxury Header
610
+ gr.HTML("""
611
+ <div class='luxury-header'>
612
+ <h1>HULHUMALE DIRECT</h1>
613
+ <div class='divider'></div>
614
+ <div class='subtitle'>Luxury Accommodations β€’ Direct Booking β€’ Zero Commission</div>
615
+ </div>
616
+ """)
617
+
618
+ # Promotion Display (Hidden by default)
619
+ promo_display = gr.HTML(visible=False)
620
+
621
+ with gr.Tabs() as tabs:
622
+
623
+ # --- TRAVELER TAB ---
624
+ with gr.Tab("πŸ” Discover Your Stay"):
625
+
626
+ gr.HTML("<div class='dashboard-section'>")
627
+
628
+ with gr.Row():
629
+ filter_location = gr.Radio(
630
+ choices=["All", "Male' City", "Hulhumale", "Villingili"],
631
+ value="All",
632
+ label="Filter by Destination",
633
+ elem_classes="location-filter"
634
+ )
635
+
636
+ room_table = gr.DataFrame(
637
+ value=df,
638
+ interactive=False,
639
+ label="Available Properties",
640
+ wrap=True
641
+ )
642
+
643
+ gr.HTML("""
644
+ <div class='info-box'>
645
+ <strong>🌟 How to Book:</strong> Contact hotels directly using the numbers listed above.
646
+ Speak with owners personally, negotiate rates, and enjoy commission-free bookings.
647
+ All properties are verified and family-operated.
648
+ </div>
649
+ """)
650
+
651
+ with gr.Row():
652
+ refresh_btn = gr.Button("πŸ”„ Refresh Availability", variant="secondary", size="lg")
653
+ refresh_status = gr.Markdown("")
654
+
655
+ gr.HTML("</div>")
656
+
657
+ # --- OWNER TAB ---
658
+ with gr.Tab("βš™οΈ Property Management Portal"):
659
+
660
+ gr.HTML("""
661
+ <div class='dashboard-section'>
662
+ <div class='dashboard-title'>Welcome to Your Dashboard</div>
663
+ <div class='dashboard-subtitle'>Premium Client Portal β€’ 350 MVR per Month</div>
664
+ </div>
665
+ """)
666
+
667
+ with gr.Row():
668
+ with gr.Column(scale=1):
669
+ gr.HTML("<div class='dashboard-section'>")
670
+ gr.Markdown("### πŸ“Š Update Your Listing")
671
+
672
+ owner_hotel = gr.Dropdown(
673
+ choices=list(data["🏨 Hotel Name"]),
674
+ label="Select Your Property",
675
+ value=None
676
+ )
677
+
678
+ new_status = gr.Radio(
679
+ choices=["Available", "Occupied"],
680
+ label="Current Availability Status",
681
+ value="Available"
682
+ )
683
+
684
+ update_price = gr.Textbox(
685
+ label="Tonight's Rate (MVR)",
686
+ placeholder="e.g., 850",
687
+ value=""
688
+ )
689
+
690
+ update_btn = gr.Button("πŸ’Ύ Update Live Listing", variant="primary", size="lg")
691
+ update_status = gr.HTML("")
692
+
693
+ gr.HTML("</div>")
694
+
695
+ with gr.Column(scale=1):
696
+ gr.HTML("<div class='dashboard-section'>")
697
+ gr.Markdown("### πŸ“£ Marketing & Promotions")
698
+
699
+ gr.HTML("""
700
+ <div class='info-box'>
701
+ Broadcast instant promotions to all active users browsing the platform.
702
+ Perfect for last-minute deals, early bird specials, or flash sales.
703
+ </div>
704
+ """)
705
+
706
+ promo_hotel = gr.Dropdown(
707
+ choices=list(data["🏨 Hotel Name"]),
708
+ label="Property Name",
709
+ value=None
710
+ )
711
+
712
+ promo_text = gr.Textbox(
713
+ label="Promotion Message",
714
+ placeholder="e.g., 20% OFF for airport arrivals before midnight!",
715
+ lines=3
716
+ )
717
+
718
+ promo_btn = gr.Button("πŸš€ Broadcast Promotion", variant="primary", size="lg")
719
+ promo_status = gr.HTML("")
720
+
721
+ gr.HTML("</div>")
722
+
723
+ # --- EVENT HANDLERS ---
724
+ filter_location.change(
725
+ fn=filter_hotels,
726
+ inputs=[filter_location],
727
+ outputs=[room_table]
728
+ )
729
+
730
+ refresh_btn.click(
731
+ fn=refresh_data,
732
+ inputs=[],
733
+ outputs=[room_table, refresh_status]
734
+ )
735
+
736
+ update_btn.click(
737
+ fn=update_listing,
738
+ inputs=[owner_hotel, new_status, update_price],
739
+ outputs=[update_status]
740
+ )
741
+
742
+ promo_btn.click(
743
+ fn=send_promotion,
744
+ inputs=[promo_hotel, promo_text],
745
+ outputs=[promo_display, promo_status]
746
+ )
747
+
748
+ # Luxury Footer
749
+ gr.HTML("""
750
+ <div class='luxury-footer'>
751
+ <div style='margin-bottom: 10px;'>✦ ✦ ✦</div>
752
+ Hulhumale Direct β€’ Connecting Travelers With Local Hospitality Since 2024
753
+ <div style='margin-top: 10px; font-size: 0.75em; opacity: 0.5;'>
754
+ A Premium Local Marketplace
755
+ </div>
756
+ </div>
757
+ """)
758
+
759
+ # Launch the app
760
+ if __name__ == "__main__":
761
+ demo.launch(share=False)