Hmlcod / Hmmlz
Jofax's picture
Create Hmmlz
351a54d verified
import gradio as gr
import pandas as pd
from datetime import datetime
# --- LUXURY CUSTOM CSS ---
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;600;700&family=Montserrat:wght@300;400;500;600&display=swap');
:root {
--gold: #D4AF37;
--gold-dark: #B8941F;
--navy: #0A1828;
--navy-light: #1A2F42;
--cream: #F8F6F0;
--white: #FFFFFF;
--shadow: rgba(10, 24, 40, 0.15);
--shadow-heavy: rgba(10, 24, 40, 0.3);
}
.gradio-container {
background: linear-gradient(135deg, #0A1828 0%, #1A2F42 50%, #0A1828 100%) !important;
font-family: 'Montserrat', sans-serif !important;
position: relative;
overflow-x: hidden;
}
.gradio-container::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 20% 30%, rgba(212, 175, 55, 0.05) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(212, 175, 55, 0.03) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
/* Header Styling */
.luxury-header {
text-align: center;
padding: 60px 20px 40px;
position: relative;
z-index: 1;
}
.luxury-header h1 {
font-family: 'Cormorant Garamond', serif !important;
font-size: 4.5em !important;
font-weight: 300 !important;
color: var(--cream) !important;
margin: 0 0 15px 0 !important;
letter-spacing: 3px !important;
text-transform: uppercase;
animation: fadeInDown 1s ease-out;
}
.luxury-header .subtitle {
font-size: 1.1em;
color: var(--gold);
letter-spacing: 4px;
text-transform: uppercase;
font-weight: 300;
margin-top: 10px;
animation: fadeInUp 1s ease-out 0.3s both;
}
.luxury-header .divider {
width: 100px;
height: 2px;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
margin: 25px auto;
animation: expandWidth 1.2s ease-out 0.5s both;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes expandWidth {
from {
width: 0;
}
to {
width: 100px;
}
}
/* Promotion Banner */
.promo-banner {
background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%);
color: var(--navy);
padding: 35px;
border-radius: 0;
margin: 30px 0;
text-align: center;
position: relative;
overflow: hidden;
box-shadow: 0 10px 40px var(--shadow-heavy);
animation: slideInFromTop 0.8s ease-out;
}
.promo-banner::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.15) 50%, transparent 70%);
animation: shimmer 3s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
100% {
transform: translateX(100%) translateY(100%) rotate(45deg);
}
}
@keyframes slideInFromTop {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.promo-banner .promo-title {
font-family: 'Cormorant Garamond', serif;
font-size: 1.4em;
font-weight: 600;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 10px;
}
.promo-banner .promo-message {
font-size: 2em;
font-weight: 600;
margin: 15px 0;
font-family: 'Cormorant Garamond', serif;
}
.promo-banner .promo-time {
font-size: 0.85em;
opacity: 0.8;
letter-spacing: 1px;
text-transform: uppercase;
}
/* Tab Styling */
.tabitem {
background: rgba(248, 246, 240, 0.05) !important;
border: none !important;
border-radius: 15px !important;
padding: 40px !important;
margin-top: 20px !important;
backdrop-filter: blur(10px);
}
button.selected {
background: var(--gold) !important;
color: var(--navy) !important;
font-weight: 600 !important;
border: none !important;
letter-spacing: 1px !important;
}
.tabs button {
color: var(--cream) !important;
font-size: 1em !important;
padding: 15px 35px !important;
border: 1px solid rgba(212, 175, 55, 0.3) !important;
background: transparent !important;
transition: all 0.3s ease !important;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 500;
}
.tabs button:hover {
background: rgba(212, 175, 55, 0.1) !important;
border-color: var(--gold) !important;
}
/* Hotel Cards - Luxury Table Styling */
.dataframe {
background: var(--cream) !important;
border-radius: 15px !important;
overflow: hidden !important;
box-shadow: 0 15px 50px var(--shadow-heavy) !important;
font-family: 'Montserrat', sans-serif !important;
border: 1px solid rgba(212, 175, 55, 0.2) !important;
}
.dataframe thead {
background: linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%) !important;
}
.dataframe thead th {
color: var(--gold) !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 1.5px !important;
padding: 20px !important;
font-size: 0.85em !important;
border: none !important;
}
.dataframe tbody td {
padding: 25px 20px !important;
color: var(--navy) !important;
font-size: 0.95em !important;
border-bottom: 1px solid rgba(10, 24, 40, 0.08) !important;
transition: all 0.3s ease;
}
.dataframe tbody tr {
transition: all 0.3s ease;
}
.dataframe tbody tr:hover {
background: rgba(212, 175, 55, 0.08) !important;
transform: scale(1.01);
}
/* Input Styling */
.gr-box,
input,
textarea,
select {
background: rgba(248, 246, 240, 0.95) !important;
border: 1px solid rgba(212, 175, 55, 0.3) !important;
color: var(--navy) !important;
border-radius: 8px !important;
transition: all 0.3s ease !important;
font-family: 'Montserrat', sans-serif !important;
}
.gr-box:focus,
input:focus,
textarea:focus,
select:focus {
border-color: var(--gold) !important;
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.15) !important;
outline: none !important;
}
label {
color: var(--cream) !important;
font-weight: 500 !important;
text-transform: uppercase !important;
letter-spacing: 1px !important;
font-size: 0.85em !important;
margin-bottom: 8px !important;
}
/* Radio Buttons */
.gr-radio {
background: transparent !important;
}
.gr-radio label {
color: var(--cream) !important;
padding: 12px 20px !important;
border: 1px solid rgba(212, 175, 55, 0.3) !important;
border-radius: 8px !important;
margin: 5px !important;
transition: all 0.3s ease !important;
cursor: pointer !important;
text-transform: none !important;
letter-spacing: 0.5px !important;
}
.gr-radio label:hover {
background: rgba(212, 175, 55, 0.1) !important;
border-color: var(--gold) !important;
}
.gr-radio input:checked + label {
background: var(--gold) !important;
color: var(--navy) !important;
border-color: var(--gold) !important;
font-weight: 600 !important;
}
/* Buttons */
.gr-button {
background: linear-gradient(135deg, var(--gold-dark) 0%, var(--gold) 100%) !important;
color: var(--navy) !important;
border: none !important;
padding: 15px 35px !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 2px !important;
border-radius: 8px !important;
transition: all 0.4s ease !important;
box-shadow: 0 5px 20px rgba(212, 175, 55, 0.3) !important;
font-size: 0.9em !important;
position: relative !important;
overflow: hidden !important;
}
.gr-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.gr-button:hover::before {
width: 300px;
height: 300px;
}
.gr-button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 30px rgba(212, 175, 55, 0.5) !important;
}
.gr-button-secondary {
background: transparent !important;
border: 2px solid var(--gold) !important;
color: var(--gold) !important;
}
.gr-button-secondary:hover {
background: var(--gold) !important;
color: var(--navy) !important;
}
/* Info Box */
.info-box {
background: rgba(212, 175, 55, 0.1);
border-left: 4px solid var(--gold);
padding: 20px 25px;
border-radius: 0 8px 8px 0;
color: var(--cream);
margin: 20px 0;
font-size: 0.95em;
line-height: 1.6;
backdrop-filter: blur(5px);
}
.info-box strong {
color: var(--gold);
font-weight: 600;
}
/* Dashboard Cards */
.dashboard-section {
background: rgba(248, 246, 240, 0.05);
border: 1px solid rgba(212, 175, 55, 0.2);
border-radius: 15px;
padding: 30px;
margin: 20px 0;
backdrop-filter: blur(10px);
}
.dashboard-title {
font-family: 'Cormorant Garamond', serif;
font-size: 1.8em;
color: var(--gold);
margin-bottom: 10px;
font-weight: 600;
letter-spacing: 1px;
}
.dashboard-subtitle {
color: var(--cream);
opacity: 0.8;
font-size: 0.9em;
letter-spacing: 1px;
text-transform: uppercase;
margin-bottom: 25px;
}
/* Footer */
.luxury-footer {
text-align: center;
margin-top: 80px;
padding: 40px 20px;
border-top: 1px solid rgba(212, 175, 55, 0.2);
color: var(--cream);
opacity: 0.6;
font-size: 0.85em;
letter-spacing: 1.5px;
text-transform: uppercase;
}
/* Status Badge */
.status-available {
color: #2ECC71;
font-weight: 600;
}
.status-occupied {
color: #E74C3C;
font-weight: 600;
}
/* Markdown Styling */
.markdown-text {
color: var(--cream) !important;
line-height: 1.8 !important;
}
.markdown-text h3 {
font-family: 'Cormorant Garamond', serif !important;
color: var(--gold) !important;
font-size: 1.6em !important;
margin-bottom: 15px !important;
font-weight: 600 !important;
}
.markdown-text p {
color: var(--cream) !important;
opacity: 0.9 !important;
}
/* Success Message */
.success-message {
background: linear-gradient(135deg, rgba(46, 204, 113, 0.2) 0%, rgba(46, 204, 113, 0.1) 100%);
border: 1px solid rgba(46, 204, 113, 0.5);
color: #2ECC71;
padding: 20px;
border-radius: 8px;
text-align: center;
font-weight: 600;
letter-spacing: 1px;
animation: slideInFromRight 0.5s ease-out;
}
@keyframes slideInFromRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.luxury-header h1 {
font-size: 2.5em !important;
}
.promo-banner .promo-message {
font-size: 1.4em;
}
.tabs button {
padding: 12px 20px !important;
font-size: 0.85em !important;
}
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: var(--navy);
}
::-webkit-scrollbar-thumb {
background: var(--gold);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--gold-dark);
}
"""
# --- ENHANCED DATA ---
data = {
"🏨 Hotel Name": [
"Coral Reef View Resort",
"Male' Grand Stay Hotel",
"Villi Blue Inn",
"Azure Lagoon Suites",
"Paradise Isle Boutique"
],
"πŸ“ Location": [
"Hulhumale Phase 1",
"Male' City Center",
"Villingili Island",
"Hulhumale Phase 2",
"Male' Waterfront"
],
"πŸ’° Price/Night": [
"850 MVR",
"1,100 MVR",
"650 MVR",
"950 MVR",
"1,250 MVR"
],
"⭐ Rating": [
"4.8/5.0",
"4.6/5.0",
"4.5/5.0",
"4.9/5.0",
"4.7/5.0"
],
"βœ… Status": [
"Available",
"Available",
"Occupied",
"Available",
"Available"
],
"πŸ“ž Direct Contact": [
"+960 777-1234",
"+960 778-5678",
"+960 779-9101",
"+960 780-2345",
"+960 781-6789"
]
}
df = pd.DataFrame(data)
# --- FUNCTIONS ---
def filter_hotels(location):
if location == "All":
return df
elif location == "Male' City":
filtered = df[df["πŸ“ Location"].str.contains("Male'", case=False, na=False)]
elif location == "Hulhumale":
filtered = df[df["πŸ“ Location"].str.contains("Hulhumale", case=False, na=False)]
elif location == "Villingili":
filtered = df[df["πŸ“ Location"].str.contains("Villingili", case=False, na=False)]
else:
filtered = df
return filtered
def refresh_data():
return df, "✨ Availability refreshed successfully!"
def send_promotion(hotel, message):
if not hotel or not message:
return gr.update(visible=False), "⚠️ Please select a hotel and enter a promotion message."
time_now = datetime.now().strftime("%I:%M %p")
date_now = datetime.now().strftime("%B %d, %Y")
promo_html = f"""
<div class='promo-banner'>
<div class='promo-title'>⚑ Exclusive Flash Offer</div>
<div class='promo-message'>{message}</div>
<div style='margin: 15px 0; font-size: 1.1em; font-weight: 600;'>πŸ“ {hotel}</div>
<div class='promo-time'>Posted {time_now} β€’ {date_now} β€’ Valid Tonight Only</div>
</div>
"""
return gr.update(value=promo_html, visible=True), f"<div class='success-message'>βœ… Promotion successfully broadcast to all users!</div>"
def update_listing(hotel, status, price):
if not hotel:
return "⚠️ Please select your property first."
success_msg = f"""
<div class='success-message'>
βœ… <strong>{hotel}</strong> updated successfully!<br>
Status: {status} | Price: {price} MVR/night
</div>
"""
return success_msg
# --- UI LAYOUT ---
with gr.Blocks(css=custom_css, title="Hulhumale Luxury Hotel Direct", theme=gr.themes.Base()) as demo:
# Luxury Header
gr.HTML("""
<div class='luxury-header'>
<h1>HULHUMALE DIRECT</h1>
<div class='divider'></div>
<div class='subtitle'>Luxury Accommodations β€’ Direct Booking β€’ Zero Commission</div>
</div>
""")
# Promotion Display (Hidden by default)
promo_display = gr.HTML(visible=False)
with gr.Tabs() as tabs:
# --- TRAVELER TAB ---
with gr.Tab("πŸ” Discover Your Stay"):
gr.HTML("<div class='dashboard-section'>")
with gr.Row():
filter_location = gr.Radio(
choices=["All", "Male' City", "Hulhumale", "Villingili"],
value="All",
label="Filter by Destination",
elem_classes="location-filter"
)
room_table = gr.DataFrame(
value=df,
interactive=False,
label="Available Properties",
wrap=True
)
gr.HTML("""
<div class='info-box'>
<strong>🌟 How to Book:</strong> Contact hotels directly using the numbers listed above.
Speak with owners personally, negotiate rates, and enjoy commission-free bookings.
All properties are verified and family-operated.
</div>
""")
with gr.Row():
refresh_btn = gr.Button("πŸ”„ Refresh Availability", variant="secondary", size="lg")
refresh_status = gr.Markdown("")
gr.HTML("</div>")
# --- OWNER TAB ---
with gr.Tab("βš™οΈ Property Management Portal"):
gr.HTML("""
<div class='dashboard-section'>
<div class='dashboard-title'>Welcome to Your Dashboard</div>
<div class='dashboard-subtitle'>Premium Client Portal β€’ 350 MVR per Month</div>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.HTML("<div class='dashboard-section'>")
gr.Markdown("### πŸ“Š Update Your Listing")
owner_hotel = gr.Dropdown(
choices=list(data["🏨 Hotel Name"]),
label="Select Your Property",
value=None
)
new_status = gr.Radio(
choices=["Available", "Occupied"],
label="Current Availability Status",
value="Available"
)
update_price = gr.Textbox(
label="Tonight's Rate (MVR)",
placeholder="e.g., 850",
value=""
)
update_btn = gr.Button("πŸ’Ύ Update Live Listing", variant="primary", size="lg")
update_status = gr.HTML("")
gr.HTML("</div>")
with gr.Column(scale=1):
gr.HTML("<div class='dashboard-section'>")
gr.Markdown("### πŸ“£ Marketing & Promotions")
gr.HTML("""
<div class='info-box'>
Broadcast instant promotions to all active users browsing the platform.
Perfect for last-minute deals, early bird specials, or flash sales.
</div>
""")
promo_hotel = gr.Dropdown(
choices=list(data["🏨 Hotel Name"]),
label="Property Name",
value=None
)
promo_text = gr.Textbox(
label="Promotion Message",
placeholder="e.g., 20% OFF for airport arrivals before midnight!",
lines=3
)
promo_btn = gr.Button("πŸš€ Broadcast Promotion", variant="primary", size="lg")
promo_status = gr.HTML("")
gr.HTML("</div>")
# --- EVENT HANDLERS ---
filter_location.change(
fn=filter_hotels,
inputs=[filter_location],
outputs=[room_table]
)
refresh_btn.click(
fn=refresh_data,
inputs=[],
outputs=[room_table, refresh_status]
)
update_btn.click(
fn=update_listing,
inputs=[owner_hotel, new_status, update_price],
outputs=[update_status]
)
promo_btn.click(
fn=send_promotion,
inputs=[promo_hotel, promo_text],
outputs=[promo_display, promo_status]
)
# Luxury Footer
gr.HTML("""
<div class='luxury-footer'>
<div style='margin-bottom: 10px;'>✦ ✦ ✦</div>
Hulhumale Direct β€’ Connecting Travelers With Local Hospitality Since 2024
<div style='margin-top: 10px; font-size: 0.75em; opacity: 0.5;'>
A Premium Local Marketplace
</div>
</div>
""")
# Launch the app
if __name__ == "__main__":
demo.launch(share=False)