diff --git a/README.md b/README.md
index a91b61d..3aee200 100644
--- a/README.md
+++ b/README.md
@@ -122,7 +122,7 @@ The original mapping used in the repository is:
## 🧠Model Performance
-The ML pipeline was trained on the **Earthquake Alert Prediction Dataset** using `RandomizedSearchCV` for hyperparameter tuning and **SMOTE** to handle class imbalance.
+The ML pipeline was trained on the **Earthquake Alert Prediction Dataset** using `XGBClassifier` for hyperparameter tuning and **SMOTE** to handle class imbalance. The model configuration was chosen by running `RandomizedSearchCV`.
**Classification Report on Test Set (260 samples):**
@@ -192,8 +192,6 @@ SEISMOSENSE/
- Add user authentication and log predictions for research purposes.
-**Note:** Due to the absence of front-end web development expertise and the absence of other contributors for the project, I was compelled to utilize AI tools (such as LLM services like ChatGPTâ„¢, Grokâ„¢, and GitHub Copilotâ„¢) to develop a sophisticated front-end for the web app. I am looking forward to human contribution on this project in order to scale it even further.
-
---
## 📄 License
diff --git a/app.py b/app.py
index 9e0f47c..a192d6f 100644
--- a/app.py
+++ b/app.py
@@ -1,11 +1,13 @@
# app.py
-from flask import Flask, render_template, request
+from flask import Flask, render_template, request, redirect, url_for, session
import joblib
import numpy as np
from pathlib import Path
from fit import main
+import secrets
app = Flask(__name__)
+app.secret_key = secrets.token_hex(32)
# paths to the pickle files
MODEL_PATH = Path("models/estimator.pkl")
@@ -36,8 +38,6 @@ def ensure_models():
# main routes
@app.route("/", methods=["GET", "POST"])
def index():
- result = None
- confidence = None
if request.method == "POST":
try:
magnitude = float(request.form["magnitude"])
@@ -49,14 +49,23 @@ def index():
X = np.array([[magnitude, depth, cdi, mmi, sig]])
pred_label = model.predict(X)[0]
+ confidence = None
if hasattr(model.named_steps["model"], "predict_proba"):
proba_array = model.predict_proba(X)[0]
- confidence = round(100 * proba_array[pred_label], 2)
+ confidence = round((float(100 * proba_array[pred_label])),2)
- result = label_map[pred_label]
+ session["result"] = label_map[pred_label]
+ session["confidence"] = confidence
except Exception as e:
- result = f"Error: {str(e)}"
+ session["result"] = f"Error: {str(e)}"
+ session["confidence"] = None
+ # PRG: redirect so a page refresh won't re-submit the form
+ return redirect(url_for("index"))
+
+ # GET: consume the result from session (one-time display)
+ result = session.pop("result", None)
+ confidence = session.pop("confidence", None)
return render_template("index.html", result=result, confidence=confidence)
if __name__ == "__main__":
diff --git a/conf_mat.py b/conf_mat.py
index 1b94db7..9f12e74 100644
--- a/conf_mat.py
+++ b/conf_mat.py
@@ -34,7 +34,7 @@ def load_data(path="dataset/earthquake_data.csv") -> np.ndarray:
def plotting(x, y) -> None:
x_train, x_test, y_train, y_test = train_test_split(
- x, y, test_size=2/10, random_state=120, shuffle=True, stratify=y
+ x, y, test_size=2/10, random_state=4, shuffle=True, stratify=y
)
labels = ALERT_LABELS
@@ -46,11 +46,10 @@ def plotting(x, y) -> None:
# Professional dark theme matching the frontend
plt.style.use('dark_background')
- BG_COLOR = '#0f1419' # Dark background
- CARD_BG = '#1a1f26' # Card background
+ BG_COLOR = '#151C26' # Dark background
+ CARD_BG = "#050506" # Card background
TEXT_PRIMARY = '#e2e8f0' # Primary text
TEXT_SECONDARY = '#94a3b8' # Muted text
- ACCENT = '#00d4aa' # Teal accent
# Alert-specific colors for the heatmap
ALERT_HEATMAP_COLORS = [
diff --git a/static/confusion_matrix.png b/static/confusion_matrix.png
new file mode 100644
index 0000000..d5d263b
Binary files /dev/null and b/static/confusion_matrix.png differ
diff --git a/static/script.js b/static/script.js
new file mode 100644
index 0000000..8fd3f37
--- /dev/null
+++ b/static/script.js
@@ -0,0 +1,119 @@
+// ========================================
+// SeismoSense - JavaScript
+// ========================================
+
+// Tab Navigation
+const tabs = document.querySelectorAll('.nav-tab');
+const panels = document.querySelectorAll('.tab-panel');
+
+tabs.forEach(tab => {
+ tab.addEventListener('click', () => {
+ // Remove active class from all tabs and panels
+ tabs.forEach(t => t.classList.remove('active'));
+ panels.forEach(p => p.classList.remove('active'));
+
+ // Add active class to clicked tab and corresponding panel
+ tab.classList.add('active');
+ const panelId = tab.dataset.tab + '-panel';
+ document.getElementById(panelId).classList.add('active');
+ });
+});
+
+// Toast Notification Functions
+function getToastIcon(type) {
+ const icons = {
+ success: ' ',
+ error: ' ',
+ warning: ' ',
+ info: ' '
+ };
+ return icons[type] || icons.info;
+}
+
+function showToast(message, type = 'info') {
+ const container = document.getElementById('toast-container');
+
+ if (!container) return;
+
+ const toast = document.createElement('div');
+ toast.className = `toast ${type}`;
+ toast.innerHTML = `
+
+ ${getToastIcon(type)}
+
+ ${message}
+ `;
+ container.appendChild(toast);
+ setTimeout(() => {
+ toast.classList.add('toast-out');
+ setTimeout(() => {
+ toast.remove();
+ }, 300);
+ }, 3000);
+}
+
+// Form Submission - Show loading state and toast
+const form = document.getElementById('seismoForm');
+const btn = document.getElementById('predictBtn');
+const btnText = btn ? btn.querySelector('.btn-text') : null;
+const loader = document.getElementById('loader');
+
+if (form && btn && btnText) {
+ form.addEventListener('submit', () => {
+ // Show loading state
+ btn.disabled = true;
+ btnText.innerHTML = ' Analyzing...';
+
+ if (loader) {
+ loader.style.display = 'inline-block';
+ }
+ });
+}
+
+// Clear button — reset all inputs
+const clearBtn = document.getElementById('clearBtn');
+if (clearBtn && form) {
+ clearBtn.addEventListener('click', () => {
+ form.querySelectorAll('.form-input').forEach(input => {
+ input.value = '';
+ });
+ form.querySelector('.form-input').focus();
+ });
+}
+
+
+// On page load, reset button state and animate confidence bar
+document.addEventListener('DOMContentLoaded', () => {
+ // Reset button state (in case of page reload with result)
+ if (btn && btnText) {
+ btn.disabled = false;
+ btnText.innerHTML = ' Analyze & Predict';
+
+ if (loader) {
+ loader.style.display = 'none';
+ }
+ }
+
+ // Show toast if there's a result
+ const resultCard = document.querySelector('.result-card');
+ if (resultCard) {
+ const result = resultCard.classList.contains('green') ? 'Green Alert' :
+ resultCard.classList.contains('orange') ? 'Orange Alert' :
+ resultCard.classList.contains('red') ? 'Red Alert' :
+ resultCard.classList.contains('yellow') ? 'Yellow Alert' : null;
+ if (result) {
+ showToast('Prediction complete!', 'success');
+ }
+ }
+
+ // Animate confidence bar if result exists
+ const confFill = document.getElementById('confFill');
+ const confValue = document.getElementById('confValue');
+
+ if (confFill && confValue) {
+ const width = confValue.textContent.trim();
+ setTimeout(() => {
+ confFill.style.width = width + '%';
+ }, 100);
+ }
+});
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..4cbe0a9
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,834 @@
+/* ========================================
+ SeismoSense - Professional Dark Theme
+ ======================================== */
+
+:root {
+ /* Dark Theme Colors */
+ --bg-primary: #0a0e14;
+ --bg-secondary: #111820;
+ --bg-tertiary: #1a222d;
+ --bg-card: #151c26;
+ --bg-input: #0d1218;
+
+ /* Accent Colors */
+ --accent: #00d4aa;
+ --accent-hover: #00f5c4;
+ --accent-muted: #00a88a;
+
+ /* Text Colors */
+ --text-primary: #e6edf3;
+ --text-secondary: #8b949e;
+ --text-muted: #6e7681;
+
+ /* Alert Colors */
+ --green: #22c55e;
+ --green-bg: rgba(34, 197, 94, 0.15);
+ --orange: #f97316;
+ --orange-bg: rgba(249, 115, 22, 0.15);
+ --red: #ef4444;
+ --red-bg: rgba(239, 68, 68, 0.15);
+ --yellow: #eab308;
+ --yellow-bg: rgba(234, 179, 8, 0.15);
+
+ /* UI Elements */
+ --border: #30363d;
+ --border-focus: var(--accent);
+
+ /* Shadows */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
+ --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.5);
+ --shadow-glow: 0 0 30px rgba(0, 212, 170, 0.15);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ min-height: 100vh;
+ line-height: 1.6;
+}
+
+/* Background Pattern */
+body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(ellipse at 20% 20%, rgba(0, 212, 170, 0.08) 0%, transparent 50%),
+ radial-gradient(ellipse at 80% 80%, rgba(0, 212, 170, 0.05) 0%, transparent 50%),
+ repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 50px,
+ rgba(0, 212, 170, 0.02) 50px,
+ rgba(0, 212, 170, 0.02) 51px
+ ),
+ repeating-linear-gradient(
+ 90deg,
+ transparent,
+ transparent 50px,
+ rgba(0, 212, 170, 0.02) 50px,
+ rgba(0, 212, 170, 0.02) 51px
+ );
+ pointer-events: none;
+ z-index: -1;
+}
+
+/* Header */
+.header {
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border);
+ padding: 0 2rem;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ backdrop-filter: blur(10px);
+}
+
+.header-content {
+ max-width: 1400px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 70px;
+}
+
+.logo {
+ display: flex;
+ align-items: center;
+ gap: 0;
+}
+
+.logo-text {
+ font-size: 1.5rem;
+ font-weight: 700;
+ background: linear-gradient(135deg, var(--accent), #00f5c4);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* Navigation Tabs */
+.nav-tabs {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.nav-tab {
+ padding: 0.625rem 1.25rem;
+ background: transparent;
+ border: none;
+ color: var(--text-secondary);
+ font-family: inherit;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.nav-tab:hover {
+ color: var(--text-primary);
+ background: var(--bg-tertiary);
+}
+
+.nav-tab.active {
+ color: var(--accent);
+ background: rgba(0, 212, 170, 0.1);
+}
+
+.nav-tab i {
+ font-size: 0.85rem;
+}
+
+/* Main Content */
+.main-content {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+/* Tab Panels */
+.tab-panel {
+ display: none;
+ animation: fadeIn 0.3s ease;
+}
+
+.tab-panel.active {
+ display: block;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Dashboard Grid */
+.dashboard-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+}
+
+@media (max-width: 1024px) {
+ .dashboard-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Cards */
+.card {
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ padding: 1.5rem;
+ box-shadow: var(--shadow-md);
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 1.5rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.card-title {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.card-title i {
+ color: var(--accent);
+}
+
+/* Form Styles */
+.form-group {
+ margin-bottom: 1.25rem;
+}
+
+.form-label {
+ display: block;
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+}
+
+.form-label .required {
+ color: var(--red);
+}
+
+.form-input {
+ width: 100%;
+ padding: 0.875rem 1rem;
+ background: var(--bg-input);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ color: var(--text-primary);
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.95rem;
+ transition: all 0.2s ease;
+}
+
+.form-input:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.15);
+}
+
+.form-input::placeholder {
+ color: var(--text-muted);
+}
+
+.form-help {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ margin-top: 0.375rem;
+}
+
+/* Button */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.875rem 1.5rem;
+ background: linear-gradient(135deg, var(--accent), var(--accent-muted));
+ color: var(--bg-primary);
+ font-family: inherit;
+ font-size: 0.95rem;
+ font-weight: 600;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ width: 100%;
+ margin-top: 0.5rem;
+}
+
+.btn:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-glow);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.btn i {
+ font-size: 1rem;
+}
+
+/* Button row: two buttons side by side */
+.btn-row {
+ display: flex;
+ gap: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.btn-row .btn {
+ margin-top: 0;
+ flex: 1;
+}
+
+/* Secondary / ghost button (Clear) */
+.btn-secondary {
+ background: transparent;
+ color: var(--text-secondary);
+ border: 1px solid var(--border);
+}
+
+.btn-secondary:hover {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+ border-color: var(--text-secondary);
+ box-shadow: none;
+}
+
+
+/* Spinner */
+.spinner {
+ display: none;
+ width: 20px;
+ height: 20px;
+ border: 2px solid transparent;
+ border-top-color: currentColor;
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* Result Display */
+.result-card {
+ margin-top: 1.5rem;
+ padding: 1.5rem;
+ border-radius: 12px;
+ animation: slideIn 0.4s ease;
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.result-card.green {
+ background: var(--green-bg);
+ border: 1px solid var(--green);
+}
+
+.result-card.orange {
+ background: var(--orange-bg);
+ border: 1px solid var(--orange);
+}
+
+.result-card.red {
+ background: var(--red-bg);
+ border: 1px solid var(--red);
+}
+
+.result-card.yellow {
+ background: var(--yellow-bg);
+ border: 1px solid var(--yellow);
+}
+
+.result-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 1rem;
+}
+
+.result-label {
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.result-value {
+ font-size: 1.5rem;
+ font-weight: 700;
+}
+
+.result-card.green .result-value { color: var(--green); }
+.result-card.orange .result-value { color: var(--orange); }
+.result-card.red .result-value { color: var(--red); }
+.result-card.yellow .result-value { color: var(--yellow); }
+
+.confidence-bar {
+ height: 8px;
+ background: var(--bg-tertiary);
+ border-radius: 4px;
+ overflow: hidden;
+ margin: 0.75rem 0;
+}
+
+.confidence-fill {
+ height: 100%;
+ border-radius: 4px;
+ transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.confidence-fill.green { background: var(--green); }
+.confidence-fill.orange { background: var(--orange); }
+.confidence-fill.red { background: var(--red); }
+.confidence-fill.yellow { background: var(--yellow); }
+
+.confidence-text {
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+}
+
+.confidence-text span {
+ font-family: 'JetBrains Mono', monospace;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+/* Stats Grid */
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.stat-item {
+ background: var(--bg-tertiary);
+ border-radius: 10px;
+ padding: 1rem;
+ text-align: center;
+}
+
+.stat-value {
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: var(--accent);
+ font-family: 'JetBrains Mono', monospace;
+}
+
+.stat-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-top: 0.25rem;
+}
+
+/* Confusion Matrix */
+.conf-matrix-container {
+ text-align: center;
+ padding: 1rem;
+}
+
+.conf-matrix-img {
+ max-width: 100%;
+ height: auto;
+ border-radius: 8px;
+ box-shadow: var(--shadow-md);
+}
+
+/* Alert Level Cards */
+.alert-levels {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+ margin-top: 1.5rem;
+}
+
+.alert-level {
+ padding: 1rem;
+ border-radius: 10px;
+ text-align: center;
+}
+
+.alert-level.green {
+ background: var(--green-bg);
+ border: 1px solid var(--green);
+}
+
+.alert-level.orange {
+ background: var(--orange-bg);
+ border: 1px solid var(--orange);
+}
+
+.alert-level.red {
+ background: var(--red-bg);
+ border: 1px solid var(--red);
+}
+
+.alert-level.yellow {
+ background: var(--yellow-bg);
+ border: 1px solid var(--yellow);
+}
+
+.alert-level-icon {
+ font-size: 1.5rem;
+ margin-bottom: 0.5rem;
+}
+
+.alert-level.green .alert-level-icon { color: var(--green); }
+.alert-level.orange .alert-level-icon { color: var(--orange); }
+.alert-level.red .alert-level-icon { color: var(--red); }
+.alert-level.yellow .alert-level-icon { color: var(--yellow); }
+
+.alert-level-name {
+ font-weight: 600;
+ font-size: 0.9rem;
+}
+
+.alert-level.green .alert-level-name { color: var(--green); }
+.alert-level.orange .alert-level-name { color: var(--orange); }
+.alert-level.red .alert-level-name { color: var(--red); }
+.alert-level.yellow .alert-level-name { color: var(--yellow); }
+
+.alert-level-desc {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ margin-top: 0.25rem;
+}
+
+/* About Section */
+.about-content {
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.about-card {
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ padding: 2rem;
+ margin-bottom: 1.5rem;
+}
+
+.about-title {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--accent);
+ margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.about-text {
+ color: var(--text-secondary);
+ font-size: 0.95rem;
+ line-height: 1.7;
+}
+
+.feature-list {
+ list-style: none;
+ margin-top: 1rem;
+}
+
+.feature-list li {
+ padding: 0.5rem 0;
+ color: var(--text-secondary);
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.feature-list li i {
+ color: var(--accent);
+ font-size: 0.85rem;
+}
+
+.tech-stack {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-top: 1rem;
+}
+
+.tech-badge {
+ padding: 0.375rem 0.75rem;
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+}
+
+/* Footer */
+.footer {
+ background: var(--bg-secondary);
+ border-top: 1px solid var(--border);
+ padding: 0;
+ margin-top: 2rem;
+}
+
+.footer-content {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 1.5rem 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.footer-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.95rem;
+ color: var(--text-secondary);
+}
+
+.footer-brand span:first-child {
+ font-weight: 600;
+ color: var(--accent);
+}
+
+.footer-divider {
+ color: var(--text-muted);
+}
+
+.footer-links {
+ display: flex;
+ gap: 1rem;
+}
+
+.footer-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--text-secondary);
+ text-decoration: none;
+ font-size: 0.9rem;
+ transition: color 0.2s ease;
+}
+
+.footer-link svg {
+ width: 18px;
+ height: 18px;
+}
+
+.footer-link:hover {
+ color: var(--accent);
+}
+
+.footer-copyright {
+ font-size: 0.85rem;
+ color: var(--text-muted);
+}
+
+.footer-link-inline {
+ color: var(--accent);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.footer-link-inline:hover {
+ text-decoration: underline;
+}
+
+/* Error State */
+.error-message {
+ background: var(--red-bg);
+ border: 1px solid var(--red);
+ border-radius: 10px;
+ padding: 1rem;
+ color: var(--red);
+ font-size: 0.9rem;
+ margin-top: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+/* Empty Result State */
+.empty-result {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+ text-align: center;
+}
+
+.empty-icon {
+ width: 80px;
+ height: 80px;
+ background: var(--bg-tertiary);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 1rem;
+}
+
+.empty-icon i {
+ font-size: 2rem;
+ color: var(--accent);
+ opacity: 0.5;
+}
+
+.empty-text {
+ color: var(--text-muted);
+ font-size: 0.9rem;
+}
+
+/* Toast Notifications */
+.toast-container {
+ position: fixed;
+ top: 90px;
+ right: 20px;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.toast {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 1rem 1.25rem;
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ animation: toast-in 0.3s ease;
+ max-width: 350px;
+ box-shadow: var(--shadow-lg);
+}
+
+.toast.toast-out {
+ animation: toast-out 0.3s ease forwards;
+}
+
+@keyframes toast-in {
+ from { opacity: 0; transform: translateX(100%); }
+ to { opacity: 1; transform: translateX(0); }
+}
+
+@keyframes toast-out {
+ from { opacity: 1; transform: translateX(0); }
+ to { opacity: 0; transform: translateX(100%); }
+}
+
+.toast-icon {
+ width: 24px;
+ height: 24px;
+ flex-shrink: 0;
+}
+
+.toast.success .toast-icon { color: var(--green); }
+.toast.error .toast-icon { color: var(--red); }
+.toast.warning .toast-icon { color: var(--yellow); }
+.toast.info .toast-icon { color: var(--accent); }
+
+.toast-message {
+ font-size: 0.9rem;
+ color: var(--text-primary);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .header {
+ padding: 0 1rem;
+ }
+
+ .header-content {
+ flex-direction: column;
+ height: auto;
+ padding: 1rem 0;
+ gap: 1rem;
+ }
+
+ .nav-tabs {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .nav-tab {
+ padding: 0.5rem 0.75rem;
+ font-size: 0.8rem;
+ }
+
+ .nav-tab span {
+ display: none;
+ }
+
+ .main-content {
+ padding: 1rem;
+ }
+
+ .alert-levels {
+ grid-template-columns: 1fr;
+ }
+
+ .footer-content {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .toast-container {
+ left: 1rem;
+ right: 1rem;
+ }
+
+ .toast {
+ max-width: 100%;
+ }
+}
diff --git a/templates/index.html b/templates/index.html
index 82cf5da..0885071 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -5,331 +5,381 @@
SeismoSense – AI Earthquake Alert Classifier
-
-
+
+
+
-
-
-
+
-
+
-
-
-
AI-Powered Earthquake Alert Classifier
+
-
+
-
-