MIF_E31232465/splash_page.py

406 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
splash_page.py
──────────────
Splash screen modern, full-viewport centered, tampil sekali per sesi.
Menggunakan st.components.v1.html (iframe isolated).
🕐 PENGATURAN DURASI:
_SPLASH_DURATION = 10 ← ganti angka ini (satuan: detik)
Rekomendasi: 48 detik
📐 PENGATURAN TINGGI IFRAME:
components.html(..., height=700 ...) ← sesuaikan jika konten terpotong
"""
import time
import streamlit as st
import streamlit.components.v1 as components
_APP_NAME = "SentiTrack"
# ┌─────────────────────────────────────────┐
# │ ⏱ GANTI ANGKA INI UNTUK UBAH DURASI │
# │ Satuan: detik | Rekomendasi: 48 │
_SPLASH_DURATION = 10
# └─────────────────────────────────────────┘
def _build_splash_html(duration: int) -> str:
return f"""<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
<style>
*, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}
/* ── Full viewport centering — bekerja di semua resolusi ── */
html, body {{
width: 100%; height: 100%;
overflow: hidden;
}}
body {{
font-family: 'Plus Jakarta Sans', sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
/* background ditangani oleh .bg */
}}
/* ── Background gradient ── */
.bg {{
position: fixed; inset: 0; z-index: 0;
background: linear-gradient(160deg, #eef4ff 0%, #f8faff 40%, #f0f7ff 70%, #eef4ff 100%);
background-size: 300% 300%;
animation: gradMove {duration * 3}s ease infinite;
}}
@keyframes gradMove {{
0% {{ background-position: 0% 50%; }}
50% {{ background-position: 100% 50%; }}
100% {{ background-position: 0% 50%; }}
}}
/* ── Pola titik ── */
.dots {{
position: fixed; inset: 0; z-index: 1; pointer-events: none;
background-image: radial-gradient(rgba(59,130,246,0.07) 1.5px, transparent 1.5px);
background-size: 26px 26px;
}}
/* ── Orb cahaya latar ── */
.orb {{
position: fixed; border-radius: 50%; z-index: 1; pointer-events: none;
will-change: transform;
}}
.orb-1 {{
width: 480px; height: 480px;
top: -180px; left: -120px;
background: radial-gradient(circle, rgba(99,102,241,0.09) 0%, transparent 70%);
animation: floatA 9s ease-in-out infinite;
}}
.orb-2 {{
width: 380px; height: 380px;
bottom: -120px; right: -80px;
background: radial-gradient(circle, rgba(59,130,246,0.08) 0%, transparent 70%);
animation: floatB 11s ease-in-out infinite;
}}
@keyframes floatA {{ 0%,100% {{ transform: translate(0,0); }} 50% {{ transform: translate(24px,-32px); }} }}
@keyframes floatB {{ 0%,100% {{ transform: translate(0,0); }} 50% {{ transform: translate(-18px,26px); }} }}
/* ── Card utama ── */
.card {{
position: relative; z-index: 10;
background: rgba(255,255,255,0.9);
backdrop-filter: blur(18px) saturate(1.4);
-webkit-backdrop-filter: blur(18px) saturate(1.4);
border: 1.5px solid rgba(226,232,240,0.85);
border-radius: 24px;
padding: 2.5rem 2.75rem 2rem;
/*
* ──────────────────────────────────────────────────────
* 📐 LEBAR CARD
* Ubah max-width untuk memperlebar / mempersempit card.
* Rekomendasi: 460px 580px
* ──────────────────────────────────────────────────────
*/
width: calc(100% - 2.5rem);
max-width: 520px;
box-shadow:
0 0 0 1px rgba(255,255,255,0.5) inset,
0 8px 40px rgba(59,130,246,0.09),
0 2px 12px rgba(15,23,42,0.06);
animation: fadeUp 0.65s cubic-bezier(0.22,1,0.36,1) forwards;
opacity: 0;
}}
@keyframes fadeUp {{
from {{ opacity: 0; transform: translateY(22px); }}
to {{ opacity: 1; transform: translateY(0); }}
}}
/* ── Header: logo + nama app ── */
.header {{
display: flex; align-items: center; gap: 1rem;
margin-bottom: 1.5rem;
}}
.logo {{
width: 54px; height: 54px; flex-shrink: 0;
background: linear-gradient(135deg, #3b6cf7 0%, #6366f1 100%);
border-radius: 16px;
display: flex; align-items: center; justify-content: center;
font-size: 1.5rem;
box-shadow: 0 8px 24px rgba(59,108,247,0.28);
animation: pulse 2.5s ease-in-out infinite;
}}
@keyframes pulse {{
0%,100% {{ box-shadow: 0 8px 24px rgba(59,108,247,0.28), 0 0 0 0 rgba(59,108,247,0.18); }}
50% {{ box-shadow: 0 10px 30px rgba(59,108,247,0.38), 0 0 0 10px rgba(59,108,247,0); }}
}}
.title {{
font-size: 1.75rem; font-weight: 800; letter-spacing: -0.03em;
background: linear-gradient(135deg, #1e3a8a 20%, #3b6cf7 60%, #6366f1 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text; line-height: 1.1;
}}
.subtitle {{
font-size: 0.65rem; color: #64748b; font-weight: 600;
letter-spacing: 0.01em; margin-top: 4px; line-height: 1.45;
max-width: 320px;
}}
/* ── Divider ── */
.divider {{
height: 1px;
background: linear-gradient(90deg, transparent, #e2e8f0 30%, #e2e8f0 70%, transparent);
margin: 1.25rem 0;
}}
/* ── Topik penelitian ── */
.topic {{
background: linear-gradient(135deg, #eff6ff, #eef2ff);
border: 1.5px solid #c7d2fe;
border-radius: 14px;
padding: 0.9rem 1.1rem;
margin-bottom: 1.25rem;
}}
.topic-label {{
font-size: 0.6rem; font-weight: 800; color: #4f46e5;
text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.3rem;
}}
.topic-value {{
font-size: 0.88rem; font-weight: 700; color: #1e3a8a; line-height: 1.5;
}}
.topic-sub {{
font-size: 0.68rem; color: #6366f1; font-weight: 600; margin-top: 0.25rem;
}}
/* ── Info grid 2×2 ── */
.info-grid {{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.65rem;
margin-bottom: 1.25rem;
}}
.info-item {{
background: #f8fafc;
border: 1px solid #e8edf5;
border-radius: 12px;
padding: 0.65rem 0.8rem;
display: flex; align-items: flex-start; gap: 0.6rem;
transition: border-color 0.2s;
}}
.info-item:hover {{ border-color: #c7d2fe; }}
.info-icon {{ font-size: 1rem; flex-shrink: 0; margin-top: 1px; }}
.info-text-label {{
font-size: 0.58rem; font-weight: 700; color: #94a3b8;
text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 2px;
}}
.info-text-value {{
font-size: 0.76rem; font-weight: 700; color: #0f172a; line-height: 1.35;
}}
/* ── Identitas mahasiswa ── */
.identity {{
display: flex; align-items: center; justify-content: space-between;
background: #f0fdf4;
border: 1px solid #86efac;
border-radius: 12px;
padding: 0.65rem 0.9rem;
margin-bottom: 1.25rem;
}}
.identity-left {{
font-size: 0.72rem; color: #15803d; font-weight: 600; line-height: 1.6;
}}
.identity-right {{
font-size: 0.66rem; color: #16a34a; font-weight: 700;
background: white; border: 1px solid #86efac; border-radius: 8px;
padding: 0.2rem 0.6rem; white-space: nowrap;
}}
/* ── Progress bar ── */
.progress-wrap {{
height: 4px; background: #e2e8f0; border-radius: 99px; overflow: hidden;
margin-bottom: 0.6rem;
}}
.progress-bar {{
height: 100%;
background: linear-gradient(90deg, #3b6cf7, #6366f1, #818cf8);
border-radius: 99px;
/*
* ──────────────────────────────────────────────────────
* ⏱ PROGRESS BAR DURATION
* Nilai ini otomatis mengikuti _SPLASH_DURATION di atas.
* Tidak perlu diubah manual.
* ──────────────────────────────────────────────────────
*/
animation: progress {duration}s linear forwards;
width: 0%;
}}
@keyframes progress {{ from {{ width: 0%; }} to {{ width: 100%; }} }}
.progress-footer {{
display: flex; justify-content: space-between; align-items: center;
}}
.progress-label {{
font-size: 0.68rem; color: #94a3b8; font-weight: 500; letter-spacing: 0.03em;
}}
.progress-source {{
font-size: 0.63rem; color: #cbd5e1; font-weight: 500;
}}
/* ── Shimmer dots loading indicator ── */
.dots-loader {{
display: flex; align-items: center; gap: 4px; margin-top: 0.2rem;
}}
.dots-loader span {{
width: 5px; height: 5px; border-radius: 50%; background: #c7d2fe;
animation: bounce 1.4s ease-in-out infinite;
}}
.dots-loader span:nth-child(2) {{ animation-delay: 0.18s; }}
.dots-loader span:nth-child(3) {{ animation-delay: 0.36s; }}
@keyframes bounce {{
0%, 80%, 100% {{ transform: scale(0.75); opacity: 0.5; }}
40% {{ transform: scale(1.1); opacity: 1; }}
}}
</style>
</head>
<body>
<div class="bg"></div>
<div class="dots"></div>
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="card">
<!-- ── Header ── -->
<div class="header">
<div class="logo">📊</div>
<div>
<div class="title">{_APP_NAME}</div>
<div class="subtitle">Dashboard Monitoring Sentimen Netizen terhadap Kebijakan Pembatasan Gratis Ongkir</div>
</div>
</div>
<div class="divider"></div>
<!-- ── Topik penelitian ── -->
<div class="topic">
<div class="topic-label">📌 Topik Penelitian</div>
<div class="topic-value">Perancangan dan Implementasi Dashboard Sentimen Netizen X terhadap Kebijakan Pembatasan Gratis Ongkir dengan Naive Bayes</div>
<div class="topic-sub">Komdigi · Analisis Opini Publik di Platform X (Twitter)</div>
</div>
<!-- ── Info grid ── -->
<div class="info-grid">
<div class="info-item">
<div class="info-icon">🤖</div>
<div>
<div class="info-text-label">Model</div>
<div class="info-text-value">Naive Bayes</div>
</div>
</div>
<div class="info-item">
<div class="info-icon">📡</div>
<div>
<div class="info-text-label">Sumber Data</div>
<div class="info-text-value">X Data Historis via Tweet Harvest &amp; Realtime</div>
</div>
</div>
<div class="info-item">
<div class="info-icon">🔤</div>
<div>
<div class="info-text-label">Preprocessing</div>
<div class="info-text-value">Case Folding · Cleaning · Normalisasi · Stopword · Stemming</div>
</div>
</div>
<div class="info-item">
<div class="info-icon">📊</div>
<div>
<div class="info-text-label">Output</div>
<div class="info-text-value">Positif · Netral · Negatif</div>
</div>
</div>
</div>
<!-- ── Identitas mahasiswa ── -->
<div class="identity">
<div class="identity-left">
<strong style="color:#0f172a;">Nurizzati Adistya Putri</strong> · E31232465<br/>
Politeknik Negeri Jember · Manajemen Informatika
</div>
<div class="identity-right">Tugas Akhir 2026</div>
</div>
<!-- ── Progress loading ── -->
<div class="progress-wrap">
<div class="progress-bar"></div>
</div>
<div class="progress-footer">
<div>
<div class="progress-label">Memuat dashboard…</div>
<div class="dots-loader">
<span></span><span></span><span></span>
</div>
</div>
<div class="progress-source">Data: X (Twitter)</div>
</div>
</div>
</body>
</html>"""
def maybe_show_splash() -> bool:
"""
Tampilkan splash jika belum tampil di sesi ini.
Kembalikan True jika splash baru saja ditampilkan, False jika dilewati.
"""
if st.session_state.get("splash_done", False):
return False
# Sembunyikan semua elemen Streamlit selama splash
st.markdown("""
<style>
#MainMenu, footer, header,
[data-testid="stHeader"],
[data-testid="stSidebar"],
[data-testid="stToolbar"],
[data-testid="collapsedControl"],
[data-testid="stStatusWidget"],
[data-testid="stDecoration"],
.stDeployButton {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
.main > div { padding: 0 !important; margin: 0 !important; }
[data-testid="stMain"],
[data-testid="stAppViewContainer"],
.stApp, html, body {
background: #f8faff !important;
overflow: hidden !important;
}
iframe { border: none !important; display: block !important; }
</style>
""", unsafe_allow_html=True)
# ┌───────────────────────────────────────────────────────┐
# │ 📐 TINGGI IFRAME (px) │
# │ Naikkan jika card terpotong di layar kecil. │
# │ Rekomendasi: 650750 │
components.html(_build_splash_html(_SPLASH_DURATION), height=700, scrolling=False)
# └───────────────────────────────────────────────────────┘
# Tunggu sesuai durasi splash sebelum lanjut ke halaman utama
time.sleep(_SPLASH_DURATION + 0.3)
st.session_state["splash_done"] = True
st.rerun()
return True