Unistay / app.py
Jofax's picture
Update app.py
7d1a302 verified
import gradio as gr
import pandas as pd
import os
# --- SYSTEM SETTINGS ---
DB_FILE = "unistay_neuro_v3.csv"
BRAND = "UNISTAY"
def init_db():
if not os.path.exists(DB_FILE):
data = {
"ID": ["1", "2", "3", "4"],
"Name": ["Velaa Azure Estate", "Soneva Aqua Reserve", "Gili Lankanfushi", "Maafushi Luxe"],
"Location": ["Noonu Atoll", "Baa Atoll", "North Malé", "South Malé"],
"Price": [45000, 22000, 18500, 2400],
"Inventory": [2, 1, 4, 0],
"Transit": ["Private Jet", "Seaplane: 15:45", "Speedboat: 10:15", "Ferry: 14:00"],
"Img": [
"https://images.unsplash.com/photo-1514282401047-d79a71a590e8?w=800&q=80",
"https://images.unsplash.com/photo-1540202404-a2f290328295?w=800&q=80",
"https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=800&q=80",
"https://images.unsplash.com/photo-1506929113675-b55f9d3bb838?w=800&q=80"
],
"Token": ["9601", "9602", "9603", "9604"]
}
pd.DataFrame(data).to_csv(DB_FILE, index=False)
init_db()
# --- THE NEURO-AESTHETIC CSS (PHD HCI STANDARD) ---
theme_css = """
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;600;800&family=Space+Grotesk:wght@300;500;700&display=swap');
:root {
--carbon-12: #050505; /* Prevents Halation (#2) */
--onyx-deep: #0d0d0d;
--luxury-blue: #00D1FF; /* Re-balanced saturation (#7) */
--text-primary: #F2F2F7;
--text-muted: #8E8E93;
--gold-ratio: 1.618;
}
/* Base Container - Reducing Ocular Fatigue */
.gradio-container {
background-color: var(--carbon-12) !important;
color: var(--text-primary) !important;
font-family: 'Plus Jakarta Sans', sans-serif !important;
}
/* Hide Navigation Tabs (#13) */
.tabs .tab-nav { display: none !important; }
/* Orbital Navigation Hub (#11, #4) */
.orbital-hub {
position: fixed; bottom: 0; left: 0; right: 0;
background: rgba(13, 13, 13, 0.9);
backdrop-filter: blur(40px);
border-top: 1px solid rgba(255,255,255,0.08);
padding: 24px 24px 48px 24px;
z-index: 1000;
}
/* Card Hierarchy & Layered Shadows (#1, #5, #10) */
.sovereign-grid {
display: grid; gap: 40px; padding: 20px 20px 280px 20px;
}
.luxe-card {
background: var(--onyx-deep);
border-radius: 32px;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.05);
/* Multi-layered shadows for depth (#1) */
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.5), 0 10px 20px -5px rgba(0,0,0,0.8);
transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Status Indicator - Trust Signaling (#30, #22) */
.sync-badge {
position: absolute; top: 25px; left: 25px;
background: rgba(0,0,0,0.6); backdrop-filter: blur(15px);
padding: 8px 18px; border-radius: 100px; font-size: 11px;
font-weight: 800; color: var(--luxury-blue);
border: 1px solid var(--luxury-blue);
}
/* Prime Haptic-Ready Buttons (#12, #8) */
.btn-prime {
background: #FFFFFF !important; color: #000000 !important;
height: 62px !important; border-radius: 20px !important;
font-weight: 800 !important; font-size: 1.05rem !important;
border: none !important; transition: all 0.4s ease;
}
.btn-prime:hover { background: var(--luxury-blue) !important; transform: scale(1.02); }
/* Host Interface Styling (#47) */
.host-panel {
background: var(--onyx-deep) !important;
padding: 40px !important;
border-radius: 40px !important;
border: 1px solid rgba(255,255,255,0.05) !important;
}
"""
def render_discovery(query=""):
df = pd.read_csv(DB_FILE)
html = '<div class="sovereign-grid">'
if query:
df = df[df['Name'].str.contains(query, case=False) | df['Location'].str.contains(query, case=False)]
for _, row in df.iterrows():
is_avail = row['Inventory'] > 0
status_color = "#00D1FF" if is_avail else "#FF453A"
status_label = "SOVEREIGN LINK" if is_avail else "CAPACITY FULL"
html += f'''
<div class="luxe-card" style="opacity: {1 if is_avail else 0.6};">
<div style="position: relative;">
<img src="{row['Img']}" style="width:100%; height:320px; object-fit:cover; filter:brightness(0.9);">
<div class="sync-badge" style="border-color:{status_color}; color:{status_color};">● {status_label}</div>
</div>
<div style="padding: 35px;">
<div style="color:var(--luxury-blue); font-size:0.75rem; font-weight:800; letter-spacing:2px; margin-bottom:12px;">{row['Transit'].upper()}</div>
<h2 style="margin: 0; font-family:'Space Grotesk'; font-size:2.2rem; letter-spacing:-1px;">{row['Name']}</h2>
<p style="color:var(--text-muted); margin: 12px 0 35px 0; font-size:0.95rem;">{row['Location']} • Verified Asset 💎</p>
<div style="display:flex; justify-content:space-between; align-items:flex-end; border-top:1px solid rgba(255,255,255,0.05); padding-top:30px;">
<div>
<span style="color:var(--text-muted); font-size:0.75rem;">Global Value / Stay</span><br>
<span style="font-family:'Space Grotesk'; font-size:2rem; font-weight:700;">MVR {row['Price']:,}</span>
</div>
<button class="btn-prime" style="padding: 0 45px;">{"SECURE" if is_avail else "NOTIFY"}</button>
</div>
</div>
</div>
'''
html += '</div>'
return html
def update_inventory(token, count):
df = pd.read_csv(DB_FILE, dtype={'Token': str})
token = str(token).strip()
if token in df['Token'].values:
df.loc[df['Token'] == token, 'Inventory'] = int(count)
df.to_csv(DB_FILE, index=False)
return "🛰️ **Neuro-Sync Successful.** Inventory updated."
return "❌ **Authentication Failed.** Invalid Node ID."
# --- INTERFACE ASSEMBLY ---
with gr.Blocks(title="UNISTAY Sovereign") as demo:
# Header (#49)
gr.HTML(f"<div style='text-align:center; padding: 70px 0 30px 0;'><h1 style='font-family:\"Space Grotesk\"; font-size: 3.5rem; letter-spacing: -5px;'>{BRAND}<span style='color:var(--luxury-blue)'>.</span></h1></div>")
with gr.Tabs() as nav_tabs:
# Discovery Portal
with gr.TabItem("Discovery", id="discovery"):
viewport = gr.HTML(render_discovery())
# Host Sovereign Hub
with gr.TabItem("Host", id="host"):
with gr.Column(elem_classes="host-panel"):
gr.Markdown("### 🛰️ Sovereign Node Sync")
t_in = gr.Textbox(label="Access Token", placeholder="Enter ID")
c_in = gr.Slider(label="Available Inventory", minimum=0, maximum=10, step=1)
sync_btn = gr.Button("BROADCAST SYNC", variant="primary", elem_classes="btn-prime")
sync_out = gr.Markdown()
# THE ORBITAL HUB (The Ultimate Fix for #11, #4, #13)
with gr.Column(elem_classes="orbital-hub"):
search_engine = gr.Textbox(show_label=False, placeholder="Where should we take you?", container=False)
with gr.Row():
btn_discover = gr.Button("💠 EXPLORE", elem_classes="btn-prime")
btn_host = gr.Button("🛰️ BROADCAST", elem_classes="btn-prime")
# BINDINGS
search_engine.change(fn=render_discovery, inputs=search_engine, outputs=viewport)
sync_btn.click(fn=update_inventory, inputs=[t_in, c_in], outputs=sync_out)
# Programmatic Navigation
btn_discover.click(fn=lambda: gr.Tabs(selected="discovery"), outputs=nav_tabs)
btn_host.click(fn=lambda: gr.Tabs(selected="host"), outputs=nav_tabs)
# --- DEPLOYMENT ---
if __name__ == "__main__":
demo.launch(css=theme_css)