Spaces:
Sleeping
Sleeping
| """ | |
| TSU-WAVE Dashboard - Streamlit Interface | |
| Main dashboard for visualizing tsunami parameters and CHI index | |
| Complete redesign matching official TSUNAMI landing page style | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from datetime import datetime, timedelta | |
| import time | |
| import sys | |
| import os | |
| import random | |
| # Add main path to PYTHONPATH | |
| sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) | |
| # Try to import TSU-WAVE modules (optional) | |
| try: | |
| from tsuwave import TSUWave | |
| from tsuwave.core.chi import compute_chi | |
| from tsuwave.core.parameters import ( | |
| compute_wcc, compute_kpr, compute_hfsi, | |
| compute_becf, compute_sdb, compute_sbsp, compute_smvi | |
| ) | |
| TSUWAVE_AVAILABLE = True | |
| except ImportError: | |
| TSUWAVE_AVAILABLE = False | |
| # Page configuration | |
| st.set_page_config( | |
| page_title="TSUNAMI - Early Warning System", | |
| page_icon="๐", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # ========== OFFICIAL CSS FROM INDEX.HTML ========== | |
| st.markdown(""" | |
| <style> | |
| /* Global Styles - Direct from official page */ | |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} | |
| html{scroll-behavior:smooth;font-size:18px} | |
| body{font-family:'Segoe UI',Arial,sans-serif;background:#020b18;color:#e0f4ff;overflow-x:hidden;line-height:1.6} | |
| /* Main header */ | |
| .main-header { | |
| background: radial-gradient(ellipse at 50% 85%,rgba(0,119,182,.45) 0%,transparent 65%),linear-gradient(180deg,#020b18 0%,#03045e 55%,#0077b6 100%); | |
| padding: 4rem 2rem; | |
| border-radius: 0 0 30px 30px; | |
| margin-bottom: 2rem; | |
| position: relative; | |
| overflow: hidden; | |
| text-align: center; | |
| } | |
| /* Wave animations */ | |
| .waves{position:relative;height:100px;overflow:hidden;margin-top:-50px} | |
| .waves svg{position:absolute;bottom:0;width:200%;animation:wm 10s linear infinite} | |
| .waves svg:nth-child(2){animation-duration:15s;animation-direction:reverse;opacity:.6;bottom:8px} | |
| .waves svg:nth-child(3){animation-duration:20s;opacity:.4;bottom:16px} | |
| @keyframes wm{from{transform:translateX(0)}to{transform:translateX(-50%)}} | |
| /* Badge */ | |
| .badge{display:inline-block;margin-bottom:1.8rem;border:1px solid #f72585;color:#f72585;background:rgba(247,37,133,.12);padding:.5rem 1.4rem;border-radius:50px;font-size:1rem;letter-spacing:2px;text-transform:uppercase;font-weight:600} | |
| /* Title */ | |
| .ht{font-size:clamp(3.4rem,9vw,6.5rem);font-weight:900;line-height:1.05;color:#fff;text-shadow:0 0 60px rgba(0,180,216,.55);margin-bottom:.6rem} | |
| .ht span{color:#00b4d8} | |
| .hl{font-size:clamp(1.05rem,2.2vw,1.25rem);color:#90e0ef;letter-spacing:.5px;margin-bottom:1.1rem;font-style:italic} | |
| .hd{font-size:clamp(1.05rem,2vw,1.2rem);color:#caf0f8;line-height:1.8;margin-bottom:2.8rem} | |
| /* Buttons */ | |
| .btn{width:100%;max-width:360px;padding:1.05rem 1.8rem;border-radius:10px;font-size:1.15rem;font-weight:700;text-decoration:none;border:none;cursor:pointer;transition:all .25s;text-align:center;display:inline-block;margin:0.5rem} | |
| .bp{background:linear-gradient(135deg,#0077b6,#00b4d8);color:#fff;box-shadow:0 4px 20px rgba(0,180,216,.4)} | |
| .bp:hover{transform:translateY(-2px);box-shadow:0 8px 28px rgba(0,180,216,.5)} | |
| .bd{background:linear-gradient(135deg,#e63946,#f72585);color:#fff;box-shadow:0 4px 20px rgba(247,37,133,.35)} | |
| .bd:hover{transform:translateY(-2px)} | |
| .bo{background:transparent;color:#00b4d8;border:2px solid #00b4d8} | |
| .bo:hover{background:rgba(0,180,216,.1);transform:translateY(-2px)} | |
| /* Stats cards */ | |
| .stats{padding:3.5rem 1.5rem;background:rgba(0,15,40,.65);border-radius:20px;margin:2rem 0} | |
| .stats-title{text-align:center;font-size:1.05rem;font-weight:700;letter-spacing:3px;text-transform:uppercase;color:#00b4d8;margin-bottom:2rem} | |
| .sc{display:flex;flex-direction:column;gap:1.1rem;max-width:620px;margin:0 auto} | |
| .sc-card{background:rgba(255,255,255,.04);border:1px solid rgba(0,180,216,.2);border-radius:16px;padding:1.6rem 2.2rem;display:flex;align-items:center;gap:2rem;backdrop-filter:blur(10px);transition:all .3s;position:relative;overflow:hidden;width:100%} | |
| .sc-card::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(0,180,216,.07) 0%,transparent 60%);pointer-events:none} | |
| .sc-card:hover{border-color:rgba(0,180,216,.5);transform:translateX(5px);background:rgba(0,180,216,.08)} | |
| .sc-card .sn{font-size:clamp(2.4rem,5vw,3.2rem);font-weight:900;color:#00b4d8;line-height:1;letter-spacing:-1px;min-width:140px} | |
| .sc-card .sl{font-size:1.15rem;color:#caf0f8;line-height:1.4;font-weight:500} | |
| /* Section headers */ | |
| section{padding:2rem 1.5rem} | |
| .stl{font-size:clamp(2rem,4vw,2.8rem);font-weight:800;color:#fff;text-align:center;margin-bottom:.6rem} | |
| .ssb{text-align:center;color:#90e0ef;font-size:1.15rem;margin-bottom:.6rem;line-height:1.6} | |
| .div{width:60px;height:4px;background:linear-gradient(90deg,#0077b6,#00b4d8);border-radius:2px;margin:1rem auto 3rem} | |
| /* Feature cards */ | |
| .fl{display:flex;flex-direction:column;gap:2.6rem} | |
| .fi{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.9rem;background:rgba(3,4,94,.22);padding:2rem;border-radius:20px} | |
| .fi:hover{background:rgba(0,119,182,.15);transform:translateY(-5px);transition:all .3s} | |
| .fic{font-size:3rem} | |
| .fi h3{font-size:1.4rem;font-weight:700;color:#fff} | |
| .fi p{font-size:1.1rem;color:#90e0ef;line-height:1.75;max-width:600px} | |
| /* Parameter cards */ | |
| .pl{display:flex;flex-direction:column;gap:1.1rem} | |
| .pi{background:rgba(0,119,182,.07);border-left:4px solid #00b4d8;border-radius:0 14px 14px 0;padding:1.4rem 1.8rem} | |
| .pi:hover{background:rgba(0,119,182,.15);transform:translateX(5px);transition:all .3s} | |
| .pi h4{font-size:1.15rem;font-weight:700;color:#fff;margin-bottom:.4rem} | |
| .pi p{font-size:1.05rem;color:#90e0ef;line-height:1.65} | |
| .pt{display:inline-block;margin-top:.6rem;font-size:1rem;color:#00b4d8;background:rgba(0,180,216,.1);border:1px solid rgba(0,180,216,.35);padding:.25rem .8rem;border-radius:5px;font-weight:600} | |
| /* Alert levels - exactly as in official page */ | |
| .al{display:flex;flex-direction:column;gap:1.5rem} | |
| .ac{border-radius:18px;padding:2.2rem 2rem;border:1px solid transparent;display:flex;flex-direction:column;align-items:center;text-align:center;gap:.8rem;backdrop-filter:blur(10px);position:relative;overflow:hidden;transition:all .3s} | |
| .ac::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(255,255,255,.06) 0%,transparent 60%);pointer-events:none} | |
| .ac:hover{transform:translateY(-4px)} | |
| .l1{background:rgba(0,200,100,.07);border-color:rgba(0,200,100,.3)} | |
| .l1:hover{background:rgba(0,200,100,.12);border-color:rgba(0,200,100,.6)} | |
| .l2{background:rgba(255,200,0,.07);border-color:rgba(255,200,0,.3)} | |
| .l2:hover{background:rgba(255,200,0,.12);border-color:rgba(255,200,0,.6)} | |
| .l3{background:rgba(255,130,0,.07);border-color:rgba(255,130,0,.3)} | |
| .l3:hover{background:rgba(255,130,0,.12);border-color:rgba(255,130,0,.6)} | |
| .l4{background:rgba(230,40,50,.08);border-color:rgba(230,40,50,.35)} | |
| .l4:hover{background:rgba(230,40,50,.14);border-color:rgba(230,40,50,.65)} | |
| .alv{font-size:1rem;font-weight:800;letter-spacing:3px;text-transform:uppercase;padding:.45rem 1.2rem;border-radius:50px} | |
| .l1 .alv{color:#4dffb0;background:rgba(77,255,176,.12);border:1px solid rgba(77,255,176,.3)} | |
| .l2 .alv{color:#ffe566;background:rgba(255,229,102,.12);border:1px solid rgba(255,229,102,.3)} | |
| .l3 .alv{color:#ffac45;background:rgba(255,172,69,.12);border:1px solid rgba(255,172,69,.3)} | |
| .l4 .alv{color:#ff4d5e;background:rgba(255,77,94,.12);border:1px solid rgba(255,77,94,.3)} | |
| .ac-level-num{font-size:clamp(2.8rem,7vw,4rem);font-weight:900;line-height:1;margin:.2rem 0} | |
| .l1 .ac-level-num{color:#4dffb0} | |
| .l2 .ac-level-num{color:#ffe566} | |
| .l3 .ac-level-num{color:#ffac45} | |
| .l4 .ac-level-num{color:#ff4d5e} | |
| .ac h4{font-size:1.4rem;font-weight:700;color:#fff} | |
| .ac p{font-size:1.1rem;color:#caf0f8;line-height:1.7;max-width:520px} | |
| /* Metric cards for parameters */ | |
| .metric-card { | |
| background: rgba(0,119,182,.07); | |
| border-left: 4px solid #00b4d8; | |
| border-radius: 0 14px 14px 0; | |
| padding: 1rem 1.5rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .metric-card:hover { | |
| background: rgba(0,119,182,.15); | |
| transform: translateX(5px); | |
| transition: all .3s; | |
| } | |
| .metric-value { | |
| font-size: 1.8rem; | |
| font-weight: 700; | |
| color: #00b4d8; | |
| } | |
| .metric-label { | |
| font-size: 0.9rem; | |
| color: #90e0ef; | |
| } | |
| /* Footer */ | |
| footer{background:#010d1a;border-top:1px solid rgba(0,180,216,.2);padding:3.5rem 1.5rem 2rem;color:#90e0ef;margin-top:3rem} | |
| .fb{max-width:1200px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:2.8rem} | |
| .fbn{font-size:1.5rem;font-weight:800;color:#00b4d8;margin-bottom:.5rem} | |
| .fbd{font-size:1.05rem;color:#90e0ef;line-height:1.65} | |
| .fbv{font-size:.95rem;color:rgba(144,224,239,.5);margin-top:.5rem} | |
| .fc h4{font-size:1.15rem;font-weight:700;color:#fff;margin-bottom:1.1rem} | |
| .fc ul{list-style:none;padding:0} | |
| .fc ul li{margin-bottom:.75rem} | |
| .fc ul li a{color:#90e0ef;text-decoration:none;font-size:1.05rem;transition:color .25s} | |
| .fc ul li a:hover{color:#00b4d8} | |
| .fbot{text-align:center;margin-top:2rem;padding-top:1.5rem;border-top:1px solid rgba(0,180,216,.12);font-size:1rem;color:rgba(144,224,239,.5)} | |
| /* Responsive */ | |
| @media(max-width:768px){ | |
| .sc-card{flex-direction:column;align-items:flex-start;gap:.5rem;padding:1.4rem 1.6rem} | |
| .sc-card .sn{min-width:unset} | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ========== JAVASCRIPT FROM OFFICIAL PAGE ========== | |
| st.markdown(""" | |
| <script> | |
| // Smooth scroll for navigation | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| document.querySelector(this.getAttribute('href')).scrollIntoView({ | |
| behavior: 'smooth' | |
| }); | |
| }); | |
| }); | |
| // Mobile menu toggle (for any future mobile implementation) | |
| function toggleMenu() { | |
| const navLinks = document.querySelector('.nav-links'); | |
| if (navLinks) navLinks.classList.toggle('open'); | |
| } | |
| // Performance monitoring | |
| const performanceData = { | |
| loadTime: performance.now(), | |
| interactions: 0 | |
| }; | |
| // Track user interactions | |
| document.addEventListener('click', function() { | |
| performanceData.interactions++; | |
| console.log('Dashboard ready - User interactions:', performanceData.interactions); | |
| }); | |
| // Initialize on page load | |
| window.addEventListener('load', function() { | |
| console.log('TSUNAMI Dashboard loaded in', (performance.now() - performanceData.loadTime).toFixed(2), 'ms'); | |
| // Add wave animation class to main header | |
| const header = document.querySelector('.main-header'); | |
| if (header) { | |
| header.classList.add('waves-animated'); | |
| } | |
| }); | |
| // Handle visibility change (pause animations when tab not active) | |
| document.addEventListener('visibilitychange', function() { | |
| if (document.hidden) { | |
| console.log('Dashboard paused'); | |
| } else { | |
| console.log('Dashboard resumed'); | |
| } | |
| }); | |
| </script> | |
| """, unsafe_allow_html=True) | |
| # Initialize session state | |
| if 'auto_refresh' not in st.session_state: | |
| st.session_state.auto_refresh = False | |
| if 'last_update' not in st.session_state: | |
| st.session_state.last_update = datetime.now() | |
| if 'selected_zone' not in st.session_state: | |
| st.session_state.selected_zone = "hilo_bay_hawaii" | |
| if 'chi_history' not in st.session_state: | |
| st.session_state.chi_history = [] | |
| # ========== MAIN HEADER WITH WAVES (EXACTLY LIKE INDEX.HTML) ========== | |
| st.markdown(""" | |
| <div class="main-header"> | |
| <div class="badge">โ ๏ธ Ocean Disaster Early Warning System</div> | |
| <h1 class="ht">๐ <span>TSUNAMI</span></h1> | |
| <p class="hl">Tidal-wave Unified System for Undersea Notification and Alert Management Integration</p> | |
| <p class="hd">An intelligent real-time framework for early detection of tsunami waves and coastal community protection โ powered by AI, seismic sensors, and global satellite networks.</p> | |
| <div> | |
| <a href="#dashboard" class="btn bp">๐ Launch Dashboard</a> | |
| <a href="#alerts-section" class="btn bd">๐จ Alert Levels</a> | |
| <a href="#footer" class="btn bo">๐ก Contact</a> | |
| </div> | |
| </div> | |
| <div class="waves"> | |
| <svg viewBox="0 0 1440 100" preserveAspectRatio="none" style="height:100px"><path fill="rgba(0,119,182,.45)" d="M0,50 C360,100 1080,0 1440,50 L1440,100 L0,100Z"/><path fill="rgba(0,119,182,.45)" d="M1440,50 C1080,100 360,0 0,50 L0,100 L1440,100Z"/></svg> | |
| <svg viewBox="0 0 1440 100" preserveAspectRatio="none" style="height:100px"><path fill="rgba(0,180,216,.3)" d="M0,35 C480,90 960,0 1440,35 L1440,100 L0,100Z"/><path fill="rgba(0,180,216,.3)" d="M1440,35 C960,90 480,0 0,35 L0,100 L1440,100Z"/></svg> | |
| <svg viewBox="0 0 1440 100" preserveAspectRatio="none" style="height:100px"><path fill="rgba(144,224,239,.18)" d="M0,70 C300,20 1100,90 1440,45 L1440,100 L0,100Z"/><path fill="rgba(144,224,239,.18)" d="M1440,70 C1100,20 300,90 0,45 L0,100 L1440,100Z"/></svg> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ========== STATS CARDS (EXACTLY LIKE INDEX.HTML) ========== | |
| st.markdown(""" | |
| <div class="stats"> | |
| <p class="stats-title">โ Live System Performance โ</p> | |
| <div class="sc"> | |
| <div class="sc-card"><div class="sn">98.6%</div><div class="sl">Early Detection Accuracy</div></div> | |
| <div class="sc-card"><div class="sn">< 3 min</div><div class="sl">Alert Response Time</div></div> | |
| <div class="sc-card"><div class="sn">120+</div><div class="sl">Ocean Monitoring Stations</div></div> | |
| <div class="sc-card"><div class="sn">47</div><div class="sl">Countries Covered</div></div> | |
| <div class="sc-card"><div class="sn">24/7</div><div class="sl">Continuous Monitoring</div></div> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Mock data function | |
| def get_mock_data(zone): | |
| """Generate realistic mock data for display""" | |
| np.random.seed(hash(zone) % 100 + int(time.time()) % 1000) | |
| # Base values with some randomness | |
| base_chi = 0.35 + np.random.random() * 0.5 | |
| return { | |
| 'chi': base_chi, | |
| 'wcc': 1.2 + np.random.random() * 0.8, | |
| 'kpr': 1.5 + np.random.random() * 1.2, | |
| 'hfsi': 0.3 + np.random.random() * 0.3, | |
| 'becf': 4.0 + np.random.random() * 4.0, | |
| 'sdb': 0.8 + np.random.random() * 0.6, | |
| 'sbsp': 0.9 + np.random.random() * 0.8, | |
| 'smvi': 0.4 + np.random.random() * 0.5 | |
| } | |
| # Get data | |
| if TSUWAVE_AVAILABLE: | |
| try: | |
| tsw = TSUWave() | |
| chi = tsw.get_chi(zone=st.session_state.selected_zone) | |
| params = tsw.get_parameters(zone=st.session_state.selected_zone) | |
| except Exception as e: | |
| st.warning(f"โ ๏ธ TSU-WAVE error: {e}. Using simulation data.") | |
| data = get_mock_data(st.session_state.selected_zone) | |
| chi = data['chi'] | |
| params = {k: v for k, v in data.items() if k != 'chi'} | |
| else: | |
| data = get_mock_data(st.session_state.selected_zone) | |
| chi = data['chi'] | |
| params = {k: v for k, v in data.items() if k != 'chi'} | |
| # Update history | |
| st.session_state.chi_history.append({ | |
| 'time': datetime.now(), | |
| 'chi': chi, | |
| 'zone': st.session_state.selected_zone | |
| }) | |
| if len(st.session_state.chi_history) > 100: | |
| st.session_state.chi_history = st.session_state.chi_history[-100:] | |
| # ========== DASHBOARD SECTION ========== | |
| st.markdown('<div id="dashboard"></div>', unsafe_allow_html=True) | |
| st.markdown('<h2 class="stl">๐ Live Tsunami Dashboard</h2>', unsafe_allow_html=True) | |
| st.markdown('<p class="ssb">Real-time monitoring of seven hydrodynamic parameters</p>', unsafe_allow_html=True) | |
| st.markdown('<div class="div"></div>', unsafe_allow_html=True) | |
| # Sidebar for controls | |
| with st.sidebar: | |
| st.markdown('<div style="background:#020b18; padding:1rem; border-radius:10px">', unsafe_allow_html=True) | |
| st.markdown('<h3 style="color:#00b4d8">โ๏ธ Dashboard Controls</h3>', unsafe_allow_html=True) | |
| # Zone selection | |
| zones = { | |
| "hilo_bay_hawaii": "๐บ Hilo Bay, Hawaii", | |
| "khao_lak": "๐๏ธ Khao Lak, Thailand", | |
| "sendai": "๐พ Sendai, Japan", | |
| "illapel": "๐๏ธ Illapel, Chile", | |
| "peru": "๐๏ธ Peru Coast" | |
| } | |
| selected_zone = st.selectbox( | |
| "Select Coastal Zone", | |
| options=list(zones.keys()), | |
| format_func=lambda x: zones[x], | |
| index=0 | |
| ) | |
| st.session_state.selected_zone = selected_zone | |
| # Auto-refresh | |
| auto_refresh = st.checkbox( | |
| "๐ Auto-refresh (every 30s)", | |
| value=st.session_state.auto_refresh | |
| ) | |
| if auto_refresh != st.session_state.auto_refresh: | |
| st.session_state.auto_refresh = auto_refresh | |
| st.rerun() | |
| refresh_interval = 30 | |
| if st.session_state.auto_refresh: | |
| refresh_interval = st.slider( | |
| "Refresh interval (seconds)", | |
| min_value=10, | |
| max_value=120, | |
| value=30, | |
| step=10 | |
| ) | |
| # Manual refresh | |
| if st.button("๐ Refresh Now", use_container_width=True): | |
| st.session_state.last_update = datetime.now() | |
| st.rerun() | |
| # System info | |
| st.markdown("---") | |
| st.markdown(f"**Last Update:** {st.session_state.last_update.strftime('%H:%M:%S')}") | |
| st.markdown(f"**Status:** {'๐ข Live' if TSUWAVE_AVAILABLE else '๐ก Simulation'}") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Main metrics | |
| col1, col2 = st.columns([1, 1]) | |
| with col1: | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="metric-value">{chi:.3f}</div> | |
| <div class="metric-label">Composite Hazard Index (CHI)</div> | |
| <span class="pt">Threshold: {'๐ข' if chi < 0.35 else '๐ก' if chi < 0.55 else '๐ ' if chi < 0.75 else '๐ด'} {chi:.3f}</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Alert level | |
| if chi < 0.35: | |
| alert_class = "l1" | |
| alert_level = "LEVEL 01" | |
| alert_title = "Monitor" | |
| alert_desc = "No significant hazard. Passive monitoring." | |
| elif chi < 0.55: | |
| alert_class = "l2" | |
| alert_level = "LEVEL 02" | |
| alert_title = "Watch" | |
| alert_desc = "Elevated โ Advisory issued. Heightened readiness." | |
| elif chi < 0.75: | |
| alert_class = "l3" | |
| alert_level = "LEVEL 03" | |
| alert_title = "Warning" | |
| alert_desc = "High โ Evacuation recommended. Activate protocols." | |
| else: | |
| alert_class = "l4" | |
| alert_level = "LEVEL 04" | |
| alert_title = "Extreme" | |
| alert_desc = "Imminent โ Immediate evacuation. Full emergency response." | |
| st.markdown(f""" | |
| <div class="ac {alert_class}" style="margin-top:1rem"> | |
| <div class="alv">{alert_level}</div> | |
| <div class="ac-level-num">{chi:.3f}</div> | |
| <h4>{alert_title}</h4> | |
| <p>{alert_desc}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col2: | |
| st.markdown('<h4 style="color:#fff">Seven Hydrodynamic Parameters</h4>', unsafe_allow_html=True) | |
| st.markdown(f""" | |
| <div class="metric-card"><span class="metric-value">{params.get('wcc', 0):.3f}</span> <span class="metric-label">WCC - Wave Front Celerity</span> <span class="pt">>1.58</span></div> | |
| <div class="metric-card"><span class="metric-value">{params.get('kpr', 0):.3f}</span> <span class="metric-label">KPR - Kinetic/Potential Ratio</span> <span class="pt">>2.0</span></div> | |
| <div class="metric-card"><span class="metric-value">{params.get('hfsi', 0):.3f}</span> <span class="metric-label">HFSI - Front Stability</span> <span class="pt"><0.40</span></div> | |
| <div class="metric-card"><span class="metric-value">{params.get('becf', 0):.3f}</span> <span class="metric-label">BECF - Bathymetric Concentration</span> <span class="pt">>6.0</span></div> | |
| <div class="metric-card"><span class="metric-value">{params.get('sdb', 0):.3f}</span> <span class="metric-label">SDB - Spectral Dispersion</span> <span class="pt"><1.0</span></div> | |
| <div class="metric-card"><span class="metric-value">{params.get('sbsp', 0):.3f}</span> <span class="metric-label">SBSP - Shoreline Stress</span> <span class="pt">>1.2</span></div> | |
| <div class="metric-card"><span class="metric-value">{params.get('smvi', 0):.3f}</span> <span class="metric-label">SMVI - Micro-Vorticity</span> <span class="pt">>0.6</span></div> | |
| """, unsafe_allow_html=True) | |
| # CHI History Chart | |
| st.markdown('<div class="chart-container" style="background:rgba(0,15,40,.65); border-radius:20px; padding:2rem; margin:2rem 0">', unsafe_allow_html=True) | |
| st.markdown('<h4 style="color:#fff">๐ CHI Evolution Over Time</h4>', unsafe_allow_html=True) | |
| if len(st.session_state.chi_history) > 1: | |
| df = pd.DataFrame(st.session_state.chi_history) | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter( | |
| x=df['time'], | |
| y=df['chi'], | |
| mode='lines+markers', | |
| name='CHI', | |
| line=dict(color='#00b4d8', width=3), | |
| marker=dict(size=8, color='#0077b6') | |
| )) | |
| # Add threshold zones | |
| fig.add_hrect(y0=0.35, y1=0.55, line_width=0, fillcolor="#ffe566", opacity=0.1, annotation_text="WATCH") | |
| fig.add_hrect(y0=0.55, y1=0.75, line_width=0, fillcolor="#ffac45", opacity=0.1, annotation_text="WARNING") | |
| fig.add_hrect(y0=0.75, y1=1.0, line_width=0, fillcolor="#ff4d5e", opacity=0.1, annotation_text="EXTREME") | |
| fig.update_layout( | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| font=dict(color='#90e0ef'), | |
| xaxis=dict(gridcolor='rgba(144,224,239,.1)'), | |
| yaxis=dict(gridcolor='rgba(144,224,239,.1)'), | |
| hovermode='x unified' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # ========== ALERT LEVELS SECTION (EXACTLY LIKE INDEX.HTML) ========== | |
| st.markdown('<div id="alerts-section"></div>', unsafe_allow_html=True) | |
| st.markdown('<h2 class="stl">๐จ Alert Levels</h2>', unsafe_allow_html=True) | |
| st.markdown('<p class="ssb">A graduated warning system ensuring the right response at every stage</p>', unsafe_allow_html=True) | |
| st.markdown('<div class="div"></div>', unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="al"> | |
| <div class="ac l1"> | |
| <div class="alv">LEVEL 01</div> | |
| <div class="ac-level-num">01</div> | |
| <h4>Watch</h4> | |
| <p>Weak seismic activity detected and under surveillance. No immediate danger. Monitor official channels and stay informed.</p> | |
| </div> | |
| <div class="ac l2"> | |
| <div class="alv">LEVEL 02</div> | |
| <div class="ac-level-num">02</div> | |
| <h4>Advisory</h4> | |
| <p>Low but non-zero probability of wave formation. Stay away from beaches and harbors. Await further instructions from authorities.</p> | |
| </div> | |
| <div class="ac l3"> | |
| <div class="alv">LEVEL 03</div> | |
| <div class="ac-level-num">03</div> | |
| <h4>Warning</h4> | |
| <p>Real and confirmed threat. Move immediately to higher ground. Do not return to the coast until the all-clear is issued.</p> | |
| </div> | |
| <div class="ac l4"> | |
| <div class="alv">LEVEL 04</div> | |
| <div class="ac-level-num">04</div> | |
| <h4>Imminent Danger</h4> | |
| <p>Wave confirmed and approaching with high confidence. Evacuate the coastal zone immediately. Follow designated evacuation routes. Do not wait.</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ========== CTA SECTION ========== | |
| st.markdown(""" | |
| <div style="background:linear-gradient(160deg,#03045e 0%,#0077b6 100%); padding:4rem 2rem; border-radius:20px; text-align:center; margin:3rem 0"> | |
| <h2 style="font-size:clamp(2rem,4vw,2.8rem); font-weight:900; color:#fff; margin-bottom:1rem">Is Your Coastal Community Protected?</h2> | |
| <p style="color:#caf0f8; margin-bottom:2.8rem; font-size:1.2rem">Join the global TSUNAMI network and ensure your region is ready before the wave begins.</p> | |
| <div> | |
| <a href="#" class="btn bp">๐ก Request Coverage</a> | |
| <a href="#" class="btn bo">๐ Technical Documentation</a> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Auto-refresh logic | |
| if st.session_state.auto_refresh: | |
| st.caption(f"๐ Auto-refreshing every {refresh_interval} seconds...") | |
| time.sleep(refresh_interval) | |
| st.rerun() | |
| # ========== FOOTER (EXACTLY LIKE INDEX.HTML) ========== | |
| st.markdown(""" | |
| <footer id="footer"> | |
| <div class="fb"> | |
| <div> | |
| <div class="fbn">๐ TSUNAMI</div> | |
| <p class="fbd">Integrated Early Warning System for Tsunami Waves and Coastal Community Protection</p> | |
| <p class="fbv">Version 1.0.0 (AI Edition) โ 2026</p> | |
| </div> | |
| <div class="fc"> | |
| <h4>Quick Links</h4> | |
| <ul> | |
| <li><a href="#home">Home Page</a></li> | |
| <li><a href="#features">Key Features</a></li> | |
| <li><a href="#how">How It Works</a></li> | |
| <li><a href="#params">Parameters</a></li> | |
| <li><a href="#alerts-section">Alert Levels</a></li> | |
| </ul> | |
| </div> | |
| <div class="fc"> | |
| <h4>Resources</h4> | |
| <ul> | |
| <li><a href="https://gitlab.com/gitdeeper4/tsu-wave/-/wikis/home" target="_blank">Wikis Documentation</a></li> | |
| <li><a href="https://doi.org/10.5281/zenodo.18667713" target="_blank">Research Paper (DOI)</a></li> | |
| <li><a href="https://pypi.org/project/stalwart-bridge/" target="_blank">PyPI Package</a></li> | |
| <li><a href="https://osf.io/anrtv" target="_blank">OSF Repository</a></li> | |
| <li><a href="https://huggingface.co/spaces/Gitdeeper4/tsunami" target="_blank">Hugging Face</a></li> | |
| </ul> | |
| </div> | |
| <div class="fc"> | |
| <h4>Platforms</h4> | |
| <ul> | |
| <li><a href="https://gitlab.com/gitdeeper4/tsu-wave" target="_blank">GitLab</a></li> | |
| <li><a href="https://github.com/gitdeeper4/tsu-wave" target="_blank">GitHub</a></li> | |
| <li><a href="https://codeberg.org/gitdeeper4/tsu-wave" target="_blank">Codeberg</a></li> | |
| <li><a href="https://bitbucket.org/gitdeeper7/tsu-wave" target="_blank">Bitbucket</a></li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="fbot">Copyright © TSUNAMI ๐ โ 2026 | All rights reserved</div> | |
| </footer> | |
| """, unsafe_allow_html=True) | |
| def run_dashboard(): | |
| """Entry point for running the dashboard""" | |
| pass | |
| if __name__ == "__main__": | |
| run_dashboard() | |