refactor: remove emojis from titles and buttons for a cleaner UI
All checks were successful
Build & Deploy Shannon / 🏗️ Build & Deploy Shannon (push) Successful in 1m3s

This commit is contained in:
Poidevin, Antoine (ITOP CM) - AF
2026-02-20 10:50:04 +01:00
parent 6a4ccc3376
commit ac6c0e1bdf
9 changed files with 112 additions and 113 deletions

16
app.py
View File

@@ -17,7 +17,7 @@ import streamlit as st
st.set_page_config( st.set_page_config(
page_title="Shannon's Equation for Dummies", page_title="Shannon's Equation for Dummies",
page_icon="📡", page_icon="antenna_bars",
layout="wide", layout="wide",
initial_sidebar_state="expanded", initial_sidebar_state="expanded",
) )
@@ -104,7 +104,7 @@ st.markdown("""
# ────────────────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────────────────
with st.sidebar: with st.sidebar:
st.markdown("## 📡 Shannon for Dummies") st.markdown("## Shannon for Dummies")
st.caption("Educational Application — AP Feb 2021") st.caption("Educational Application — AP Feb 2021")
st.divider() st.divider()
@@ -112,12 +112,12 @@ with st.sidebar:
"Navigation", "Navigation",
options=["animation", "orbits", "sat_types", "theory", "real_world", "contributions"], options=["animation", "orbits", "sat_types", "theory", "real_world", "contributions"],
format_func=lambda x: { format_func=lambda x: {
"animation": "📡 Satellite Link Animation", "animation": "Satellite Link Animation",
"orbits": "🌍 GEO / MEO / LEO Orbits", "orbits": "GEO / MEO / LEO Orbits",
"sat_types": "🛰️ Satellite Missions & Types", "sat_types": "Satellite Missions & Types",
"theory": "🧮 Theoretical Exploration", "theory": "Theoretical Exploration",
"real_world": "🛰️ Real World Link Budget", "real_world": "Real World Link Budget",
"contributions": "💬 Community Contributions", "contributions": "Community Contributions",
}[x], }[x],
label_visibility="collapsed", label_visibility="collapsed",
) )

View File

@@ -101,7 +101,7 @@ THEORY_HELP = {
"- Try multiple values in all the fields one by one\n" "- Try multiple values in all the fields one by one\n"
"- Explore the graphs and try to understand the underlying physics\n" "- Explore the graphs and try to understand the underlying physics\n"
"- The units are as explicit as possible to facilitate the exploration\n" "- The units are as explicit as possible to facilitate the exploration\n"
"- Click on the icons to get information about each parameter" "- Click on the info icons to get information about each parameter"
), ),
} }
@@ -134,7 +134,7 @@ REAL_WORLD_HELP = {
"The position of the ground station affects link availability due to weather statistics " "The position of the ground station affects link availability due to weather statistics "
"(tropical regions have very heavy rains attenuating signals at high frequencies). " "(tropical regions have very heavy rains attenuating signals at high frequencies). "
"It also impacts the elevation angle and overall path length.\n\n" "It also impacts the elevation angle and overall path length.\n\n"
"🔗 [Find coordinates](https://www.gps-coordinates.net)" "[Find coordinates](https://www.gps-coordinates.net)"
), ),
"availability": ( "availability": (
"**Link Availability [%]**\n\n" "**Link Availability [%]**\n\n"

View File

@@ -11,17 +11,17 @@ from core.database import write_contribution, search_contributions, delete_contr
def render(): def render():
"""Render the Contributions page.""" """Render the Contributions page."""
st.markdown("# 💬 Community Contributions") st.markdown("# Community Contributions")
st.markdown( st.markdown(
"Share your observations about Shannon's theorem, satellite communications, " "Share your observations about Shannon's theorem, satellite communications, "
"or suggest improvements. Contributions are stored locally and shared with all users." "or suggest improvements. Contributions are stored locally and shared with all users."
) )
tab_read, tab_write = st.tabs(["📖 Read Contributions", "✍️ Write Contribution"]) tab_read, tab_write = st.tabs(["Read Contributions", "Write Contribution"])
# ── Read Contributions ── # ── Read Contributions ──
with tab_read: with tab_read:
st.markdown("### 🔍 Search Contributions") st.markdown("### Search Contributions")
db_choice = st.radio( db_choice = st.radio(
"Database", "Database",
@@ -39,7 +39,7 @@ def render():
kw_filter = st.text_input("Filter by Keywords", key="filter_kw") kw_filter = st.text_input("Filter by Keywords", key="filter_kw")
content_filter = st.text_input("Filter by Content", key="filter_content") content_filter = st.text_input("Filter by Content", key="filter_content")
if st.button("🔍 Search", type="primary", key="btn_search"): if st.button("Search", type="primary", key="btn_search"):
results = search_contributions( results = search_contributions(
db_name, db_name,
name_filter=name_filter, name_filter=name_filter,
@@ -60,10 +60,10 @@ def render():
): ):
st.markdown(contrib["text"]) st.markdown(contrib["text"])
if contrib["keywords"]: if contrib["keywords"]:
st.caption(f"🏷️ Keywords: {contrib['keywords']}") st.caption(f"Keywords: {contrib['keywords']}")
# Delete functionality # Delete functionality
with st.popover("🗑️ Delete"): with st.popover("Delete"):
st.warning("This action cannot be undone.") st.warning("This action cannot be undone.")
del_password = st.text_input( del_password = st.text_input(
"Enter contribution password", "Enter contribution password",
@@ -72,7 +72,7 @@ def render():
) )
if st.button("Confirm Delete", key=f"del_btn_{contrib['num']}"): if st.button("Confirm Delete", key=f"del_btn_{contrib['num']}"):
if delete_contribution(db_name, contrib["num"], del_password): if delete_contribution(db_name, contrib["num"], del_password):
st.success(f"Contribution #{contrib['num']} deleted.") st.success(f"Contribution #{contrib['num']} deleted.")
# Refresh results # Refresh results
st.session_state["contrib_results"] = search_contributions( st.session_state["contrib_results"] = search_contributions(
db_name, db_name,
@@ -83,13 +83,13 @@ def render():
) )
st.rerun() st.rerun()
else: else:
st.error("Incorrect password or contribution not found.") st.error("Incorrect password or contribution not found.")
elif "contrib_results" in st.session_state: elif "contrib_results" in st.session_state:
st.info("No contributions found matching your filters.") st.info("No contributions found matching your filters.")
# ── Write Contribution ── # ── Write Contribution ──
with tab_write: with tab_write:
st.markdown("### ✍️ New Contribution") st.markdown("### New Contribution")
db_choice_w = st.radio( db_choice_w = st.radio(
"Database", "Database",
@@ -109,17 +109,16 @@ def render():
type="password", type="password",
) )
submitted = st.form_submit_button("📤 Submit", type="primary") submitted = st.form_submit_button("Submit", type="primary")
if submitted: if submitted:
if not name or not title or not text: if not name or not title or not text:
st.error("Please fill in all required fields (marked with *).") st.error("Please fill in all required fields (marked with *).")
else: else:
new_id = write_contribution(db_name_w, name, title, keywords, text, password) new_id = write_contribution(db_name_w, name, title, keywords, text, password)
st.success(f"Thank you! Your contribution has been stored with ID #{new_id}.") st.success(f"Thank you. Your contribution has been stored with ID #{new_id}.")
st.balloons()
with st.expander("Help"): with st.expander("Help"):
st.markdown( st.markdown(
"Write your contribution as a free text. Contributions should be:\n\n" "Write your contribution as a free text. Contributions should be:\n\n"
"- Candid observations about the technical subject\n" "- Candid observations about the technical subject\n"

View File

@@ -66,7 +66,7 @@ _ORBITS_HTML = """
<canvas id="c"></canvas> <canvas id="c"></canvas>
<div id="info"> <div id="info">
<h3>🛰️ Orbit Comparison</h3> <h3>Orbit Comparison</h3>
<div id="infoContent"> <div id="infoContent">
<p style="color:#94a3b8; text-align:center;"> <p style="color:#94a3b8; text-align:center;">
Hover over a satellite to see its details. Hover over a satellite to see its details.
@@ -226,8 +226,8 @@ function showInfo(o) {
<div class="stat"><span class="label">FSPL</span><span class="value">${o.fsplDb}</span></div> <div class="stat"><span class="label">FSPL</span><span class="value">${o.fsplDb}</span></div>
<div class="stat"><span class="label">Coverage</span><span class="value">${o.coverage}</span></div> <div class="stat"><span class="label">Coverage</span><span class="value">${o.coverage}</span></div>
<hr> <hr>
<div class="stat"><span class="label">Pros</span><span class="value" style="text-align:right; max-width:160px">${o.pros}</span></div> <div class="stat"><span class="label">Pros</span><span class="value" style="text-align:right; max-width:160px">${o.pros}</span></div>
<div class="stat"><span class="label">⚠️ Cons</span><span class="value" style="text-align:right; max-width:160px">${o.cons}</span></div> <div class="stat"><span class="label">Cons</span><span class="value" style="text-align:right; max-width:160px">${o.cons}</span></div>
<hr> <hr>
<div class="stat"><span class="label">Examples</span><span class="value" style="color:#4FC3F7">${o.examples}</span></div> <div class="stat"><span class="label">Examples</span><span class="value" style="color:#4FC3F7">${o.examples}</span></div>
`; `;
@@ -489,7 +489,7 @@ draw();
def render(): def render():
"""Render the GEO/MEO/LEO orbit comparison animation.""" """Render the GEO/MEO/LEO orbit comparison animation."""
st.markdown("## 🌍 Satellite Orbits — GEO vs MEO vs LEO") st.markdown("## Satellite Orbits — GEO vs MEO vs LEO")
st.markdown( st.markdown(
"Compare the three main satellite orbit types. " "Compare the three main satellite orbit types. "
"Hover over satellites or legend items to explore their characteristics." "Hover over satellites or legend items to explore their characteristics."
@@ -500,50 +500,50 @@ def render():
# ── Educational content below ── # ── Educational content below ──
st.divider() st.divider()
st.markdown("### 📊 Orbit Comparison at a Glance") st.markdown("### Orbit Comparison at a Glance")
col1, col2, col3 = st.columns(3) col1, col2, col3 = st.columns(3)
with col1: with col1:
st.markdown(""" st.markdown("""
#### 🟢 LEO — Low Earth Orbit #### LEO — Low Earth Orbit
**160 2 000 km** **160 2 000 km**
- Latency: **4 20 ms** (RTT) - Latency: **4 20 ms** (RTT)
- 📉 FSPL: ~155 dB (Ku-band) - FSPL: ~155 dB (Ku-band)
- 🔄 Period: ~90 120 min - Period: ~90 120 min
- 📡 Small footprint → **large constellations** needed (hundreds to thousands) - Small footprint → **large constellations** needed (hundreds to thousands)
- 🤝 Requires **handover** between satellites - Requires **handover** between satellites
- 🛰️ *Starlink (550 km), OneWeb (1 200 km), Iridium (780 km)* - *Starlink (550 km), OneWeb (1 200 km), Iridium (780 km)*
""") """)
with col2: with col2:
st.markdown(""" st.markdown("""
#### 🟡 MEO — Medium Earth Orbit #### MEO — Medium Earth Orbit
**2 000 35 786 km** **2 000 35 786 km**
- Latency: **40 80 ms** (RTT) - Latency: **40 80 ms** (RTT)
- 📉 FSPL: ~186 dB (Ku-band) - FSPL: ~186 dB (Ku-band)
- 🔄 Period: ~2 12 h - Period: ~2 12 h
- 📡 Medium footprint → **medium constellations** (20 50 sats) - Medium footprint → **medium constellations** (20 50 sats)
- 🌍 Good balance between coverage and latency - Good balance between coverage and latency
- 🛰️ *GPS (20 200 km), Galileo, O3b/SES (8 000 km)* - *GPS (20 200 km), Galileo, O3b/SES (8 000 km)*
""") """)
with col3: with col3:
st.markdown(""" st.markdown("""
#### 🔴 GEO — Geostationary Orbit #### GEO — Geostationary Orbit
**35 786 km (fixed)** **35 786 km (fixed)**
- Latency: **240 280 ms** (RTT) - Latency: **240 280 ms** (RTT)
- 📉 FSPL: ~205 dB (Ku-band) - FSPL: ~205 dB (Ku-band)
- 🔄 Period: 23 h 56 min (= 1 sidereal day) - Period: 23 h 56 min (= 1 sidereal day)
- 📡 Huge footprint → **3 sats = global** coverage - Huge footprint → **3 sats = global** coverage
- 📌 **Fixed position** in the sky — no tracking needed - **Fixed position** in the sky — no tracking needed
- 🛰️ *Intelsat, SES, Eutelsat, ViaSat* - *Intelsat, SES, Eutelsat, ViaSat*
""") """)
with st.expander("🔬 Key Trade-offs in Detail"): with st.expander("Key Trade-offs in Detail"):
st.markdown(r""" st.markdown(r"""
| Parameter | LEO | MEO | GEO | | Parameter | LEO | MEO | GEO |
|:---|:---:|:---:|:---:| |:---|:---:|:---:|:---:|
@@ -569,7 +569,7 @@ for the same bandwidth and transmit power. LEO compensates with lower FSPL
but requires more satellites and complex handover. but requires more satellites and complex handover.
""") """)
with st.expander("🛰️ Notable Constellations"): with st.expander("Notable Constellations"):
st.markdown(""" st.markdown("""
| Constellation | Orbit | Altitude | # Satellites | Use Case | | Constellation | Orbit | Altitude | # Satellites | Use Case |
|:---|:---:|:---:|:---:|:---| |:---|:---:|:---:|:---:|:---|

View File

@@ -159,21 +159,21 @@ def render():
with col_img: with col_img:
st.image("Satellite.png", width=200) st.image("Satellite.png", width=200)
with col_title: with col_title:
st.markdown("# 🛰️ Shannon & Friends in the Real World") st.markdown("# Shannon & Friends in the Real World")
st.markdown("From theory to satellite communication link budget.") st.markdown("From theory to satellite communication link budget.")
wiki_cols = st.columns(4) wiki_cols = st.columns(4)
wiki_cols[0].link_button("📖 Harry Nyquist", "https://en.wikipedia.org/wiki/Harry_Nyquist") wiki_cols[0].link_button("Harry Nyquist", "https://en.wikipedia.org/wiki/Harry_Nyquist")
wiki_cols[1].link_button("📖 Richard Hamming", "https://en.wikipedia.org/wiki/Richard_Hamming") wiki_cols[1].link_button("Richard Hamming", "https://en.wikipedia.org/wiki/Richard_Hamming")
wiki_cols[2].link_button("📖 Andrew Viterbi", "https://en.wikipedia.org/wiki/Andrew_Viterbi") wiki_cols[2].link_button("Andrew Viterbi", "https://en.wikipedia.org/wiki/Andrew_Viterbi")
wiki_cols[3].link_button("📖 Claude Berrou", "https://en.wikipedia.org/wiki/Claude_Berrou") wiki_cols[3].link_button("Claude Berrou", "https://en.wikipedia.org/wiki/Claude_Berrou")
st.divider() st.divider()
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
# SECTION 1: Satellite Link # SECTION 1: Satellite Link
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
st.markdown("## 📡 Satellite Link") st.markdown("## Satellite Link")
col1, col2, col3 = st.columns(3) col1, col2, col3 = st.columns(3)
with col1: with col1:
@@ -213,7 +213,7 @@ def render():
try: try:
sat_cir_list = [float(v.strip()) for v in sat_cir_input.split(",")] sat_cir_list = [float(v.strip()) for v in sat_cir_input.split(",")]
except ValueError: except ValueError:
st.error("Invalid C/I values.") st.error("Invalid C/I values.")
return return
# Compute satellite link # Compute satellite link
@@ -224,11 +224,11 @@ def render():
sat_alt, sat_lat, sat_lon, gs_lat, gs_lon, availability, sat_alt, sat_lat, sat_lon, gs_lat, gs_lon, availability,
) )
except Exception as e: except Exception as e:
st.error(f"Satellite link computation error: {e}") st.error(f"Satellite link computation error: {e}")
return return
# Display satellite link results # Display satellite link results
st.markdown("#### 📊 Satellite Link Results") st.markdown("#### Satellite Link Results")
r1, r2, r3 = st.columns(3) r1, r2, r3 = st.columns(3)
r1.metric("Output Power", fmt_power(sat["sig_power"]), help=REAL_WORLD_HELP["output_power"]) r1.metric("Output Power", fmt_power(sat["sig_power"]), help=REAL_WORLD_HELP["output_power"])
r2.metric("Antenna Gain", fmt_gain(sat["sat_gain_linear"]), help=REAL_WORLD_HELP["sat_gain"]) r2.metric("Antenna Gain", fmt_gain(sat["sat_gain_linear"]), help=REAL_WORLD_HELP["sat_gain"])
@@ -245,14 +245,14 @@ def render():
st.metric("Power Flux Density", fmt_pfd(sat["pfd_linear"]), help=REAL_WORLD_HELP["pfd"]) st.metric("Power Flux Density", fmt_pfd(sat["pfd_linear"]), help=REAL_WORLD_HELP["pfd"])
if sat["elevation"] <= 0: if sat["elevation"] <= 0:
st.warning("⚠️ Satellite is below the horizon (negative elevation). Results may not be meaningful.") st.warning("Satellite is below the horizon (negative elevation). Results may not be meaningful.")
st.divider() st.divider()
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
# SECTION 2: Radio Front End # SECTION 2: Radio Front End
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
st.markdown("## 📻 Radio Front End") st.markdown("## Radio Front End")
col_r1, col_r2 = st.columns(2) col_r1, col_r2 = st.columns(2)
with col_r1: with col_r1:
@@ -268,10 +268,10 @@ def render():
cpe_ant_d, cpe_t_clear, cpe_ant_d, cpe_t_clear,
) )
except Exception as e: except Exception as e:
st.error(f"Receiver computation error: {e}") st.error(f"Receiver computation error: {e}")
return return
st.markdown("#### 📊 Receiver Results") st.markdown("#### Receiver Results")
rx1, rx2 = st.columns(2) rx1, rx2 = st.columns(2)
rx1.metric("Antenna Area · G/T", f"{rcv['cpe_ae']:.2f} m² · {rcv['cpe_g_t']:.1f} dB/K", rx1.metric("Antenna Area · G/T", f"{rcv['cpe_ae']:.2f} m² · {rcv['cpe_g_t']:.1f} dB/K",
help=REAL_WORLD_HELP["cpe_gain"]) help=REAL_WORLD_HELP["cpe_gain"])
@@ -290,7 +290,7 @@ def render():
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
# SECTION 3: Baseband Unit # SECTION 3: Baseband Unit
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
st.markdown("## 💻 Baseband Unit") st.markdown("## Baseband Unit")
col_b1, col_b2, col_b3 = st.columns(3) col_b1, col_b2, col_b3 = st.columns(3)
with col_b1: with col_b1:
@@ -310,7 +310,7 @@ def render():
try: try:
cnr_imp_list = [float(v.strip()) for v in cir_input.split(",")] cnr_imp_list = [float(v.strip()) for v in cir_input.split(",")]
except ValueError: except ValueError:
st.error("Invalid C/I values.") st.error("Invalid C/I values.")
return return
try: try:
@@ -319,10 +319,10 @@ def render():
sat["sat_cir"], bandwidth, rolloff, overheads, cnr_imp_list, penalties, sat["sat_cir"], bandwidth, rolloff, overheads, cnr_imp_list, penalties,
) )
except Exception as e: except Exception as e:
st.error(f"Baseband computation error: {e}") st.error(f"Baseband computation error: {e}")
return return
st.markdown("#### 📊 Baseband Results") st.markdown("#### Baseband Results")
b1, b2, b3 = st.columns(3) b1, b2, b3 = st.columns(3)
b1.metric("SNR in Available BW", f"{bb['cnr_bw']:.1f} dB in {bandwidth:.1f} MHz", b1.metric("SNR in Available BW", f"{bb['cnr_bw']:.1f} dB in {bandwidth:.1f} MHz",
@@ -336,7 +336,7 @@ def render():
b4, b5 = st.columns(2) b4, b5 = st.columns(2)
with b4: with b4:
st.markdown("##### 🎯 Theoretical") st.markdown("##### Theoretical")
st.metric( st.metric(
"Theoretical BR", "Theoretical BR",
f"{fmt_br(bb['br_nyq'])} · {bb['br_nyq_norm']:.0%}", f"{fmt_br(bb['br_nyq'])} · {bb['br_nyq_norm']:.0%}",
@@ -344,7 +344,7 @@ def render():
) )
st.caption(f"Spectral Eff: {bb['spe_nyq']:.2f} bps/Hz · {bb['bits_per_symbol']:.2f} b/Symbol") st.caption(f"Spectral Eff: {bb['spe_nyq']:.2f} bps/Hz · {bb['bits_per_symbol']:.2f} b/Symbol")
with b5: with b5:
st.markdown("##### 🏭 Practical") st.markdown("##### Practical")
st.metric( st.metric(
"Physical Layer BR", "Physical Layer BR",
f"{fmt_br(bb['br_rcv'])} · {bb['br_rcv_norm']:.0%}", f"{fmt_br(bb['br_rcv'])} · {bb['br_rcv_norm']:.0%}",
@@ -360,12 +360,12 @@ def render():
st.divider() st.divider()
# ── Graphs ── # ── Graphs ──
st.markdown("### 📈 Interactive Graphs") st.markdown("### Interactive Graphs")
tab_bw, tab_pow, tab_map = st.tabs([ tab_bw, tab_pow, tab_map = st.tabs([
"📶 BW Sensitivity", "BW Sensitivity",
"Power Sensitivity", "Power Sensitivity",
"🗺️ BR Factor Map", "BR Factor Map",
]) ])
cnr_imp = combine_cnr(*cnr_imp_list) cnr_imp = combine_cnr(*cnr_imp_list)

View File

@@ -78,7 +78,7 @@ body{background:transparent;overflow:hidden;font-family:'Segoe UI',system-ui,san
<canvas id="c"></canvas> <canvas id="c"></canvas>
<div id="tabs"></div> <div id="tabs"></div>
<div id="info"><h3>🛰️ Satellite Missions</h3><p style="color:#64748b;text-align:center;padding:12px 0">Select a mission type above<br>to explore its animation.</p></div> <div id="info"><h3>Satellite Missions</h3><p style="color:#64748b;text-align:center;padding:12px 0">Select a mission type above<br>to explore its animation.</p></div>
<div id="sceneLabel"><span>Mission Control</span> — choose a category</div> <div id="sceneLabel"><span>Mission Control</span> — choose a category</div>
<script> <script>
@@ -86,37 +86,37 @@ body{background:transparent;overflow:hidden;font-family:'Segoe UI',system-ui,san
// DATA // DATA
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
const cats=[ const cats=[
{id:'nav',icon:'🧭',name:'Navigation',color:'#34d399',rgb:'52,211,153', {id:'nav',icon:'NAV',name:'Navigation',color:'#34d399',rgb:'52,211,153',
orbit:'MEO',alt:'20 200 km',band:'L-band (1.21.6 GHz)',power:'~50 W/signal', orbit:'MEO',alt:'20 200 km',band:'L-band (1.21.6 GHz)',power:'~50 W/signal',
precision:'< 1 m (dual-freq)',lifetime:'1215 yrs', precision:'< 1 m (dual-freq)',lifetime:'1215 yrs',
examples:['GPS (USA)','Galileo (EU)','GLONASS (RU)','BeiDou (CN)'], examples:['GPS (USA)','Galileo (EU)','GLONASS (RU)','BeiDou (CN)'],
fact:'Each GPS satellite carries 4 atomic clocks accurate to ~1 ns. Relativity corrections of 38 μs/day are applied.', fact:'Each GPS satellite carries 4 atomic clocks accurate to ~1 ns. Relativity corrections of 38 μs/day are applied.',
sceneHint:'Triangulation — 3 satellites fix your position'}, sceneHint:'Triangulation — 3 satellites fix your position'},
{id:'com',icon:'📡',name:'Communication',color:'#4FC3F7',rgb:'79,195,247', {id:'com',icon:'COM',name:'Communication',color:'#4FC3F7',rgb:'79,195,247',
orbit:'GEO + LEO',alt:'55036 000 km',band:'C / Ku / Ka-band',power:'220 kW', orbit:'GEO + LEO',alt:'55036 000 km',band:'C / Ku / Ka-band',power:'220 kW',
precision:'100+ Gbps (HTS)',lifetime:'1520 yrs', precision:'100+ Gbps (HTS)',lifetime:'1520 yrs',
examples:['Starlink','Intelsat','SES','OneWeb'], examples:['Starlink','Intelsat','SES','OneWeb'],
fact:'A modern HTS can deliver 500+ Gbps — equivalent to 100 000 HD streams simultaneously.', fact:'A modern HTS can deliver 500+ Gbps — equivalent to 100 000 HD streams simultaneously.',
sceneHint:'Data flowing between ground stations'}, sceneHint:'Data flowing between ground stations'},
{id:'eo',icon:'📸',name:'Earth Observation',color:'#a78bfa',rgb:'167,139,250', {id:'eo',icon:'EO',name:'Earth Observation',color:'#a78bfa',rgb:'167,139,250',
orbit:'LEO (SSO)',alt:'500800 km',band:'X-band (downlink)',power:'SAR + optical', orbit:'LEO (SSO)',alt:'500800 km',band:'X-band (downlink)',power:'SAR + optical',
precision:'0.330 m resolution',lifetime:'57 yrs', precision:'0.330 m resolution',lifetime:'57 yrs',
examples:['Sentinel','Landsat','Planet Doves','Pléiades'], examples:['Sentinel','Landsat','Planet Doves','Pléiades'],
fact:'Planet Labs\' 200+ Doves image the entire land surface every single day at 3 m resolution.', fact:'Planet Labs\' 200+ Doves image the entire land surface every single day at 3 m resolution.',
sceneHint:'Satellite scanning the terrain below'}, sceneHint:'Satellite scanning the terrain below'},
{id:'wx',icon:'🌦️',name:'Weather',color:'#fbbf24',rgb:'251,191,36', {id:'wx',icon:'WX',name:'Weather',color:'#fbbf24',rgb:'251,191,36',
orbit:'GEO + LEO',alt:'80036 000 km',band:'L / S / Ka-band',power:'Imager + Sounder', orbit:'GEO + LEO',alt:'80036 000 km',band:'L / S / Ka-band',power:'Imager + Sounder',
precision:'Full disk every 10 min',lifetime:'1015 yrs', precision:'Full disk every 10 min',lifetime:'1015 yrs',
examples:['Meteosat','GOES','Himawari','FengYun'], examples:['Meteosat','GOES','Himawari','FengYun'],
fact:'GOES-16 produces 3.6 TB of weather imagery per day across 16 spectral bands.', fact:'GOES-16 produces 3.6 TB of weather imagery per day across 16 spectral bands.',
sceneHint:'Atmospheric monitoring & cloud tracking'}, sceneHint:'Atmospheric monitoring & cloud tracking'},
{id:'sci',icon:'🔭',name:'Science',color:'#f472b6',rgb:'244,114,182', {id:'sci',icon:'SCI',name:'Science',color:'#f472b6',rgb:'244,114,182',
orbit:'Various (L2, LEO, HEO)',alt:'540 km 1.5M km',band:'S / X / Ka-band',power:'Telescope + Spectrometer', orbit:'Various (L2, LEO, HEO)',alt:'540 km 1.5M km',band:'S / X / Ka-band',power:'Telescope + Spectrometer',
precision:'28 Mbps (JWST)',lifetime:'520+ yrs', precision:'28 Mbps (JWST)',lifetime:'520+ yrs',
examples:['JWST','Hubble','Gaia','SOHO','Chandra'], examples:['JWST','Hubble','Gaia','SOHO','Chandra'],
fact:'JWST\'s 6.5 m gold mirror operates at 233 °C to see infrared light from the first galaxies, 13.5 billion years ago.', fact:'JWST\'s 6.5 m gold mirror operates at 233 °C to see infrared light from the first galaxies, 13.5 billion years ago.',
sceneHint:'Deep-space telescope view'}, sceneHint:'Deep-space telescope view'},
{id:'def',icon:'🛡️',name:'Defense',color:'#fb923c',rgb:'251,146,60', {id:'def',icon:'DEF',name:'Defense',color:'#fb923c',rgb:'251,146,60',
orbit:'LEO / GEO / HEO',alt:'Variable',band:'UHF / SHF / EHF',power:'Anti-jam, encrypted', orbit:'LEO / GEO / HEO',alt:'Variable',band:'UHF / SHF / EHF',power:'Anti-jam, encrypted',
precision:'SIGINT + IMINT',lifetime:'715 yrs', precision:'SIGINT + IMINT',lifetime:'715 yrs',
examples:['SBIRS','WGS','Syracuse (FR)','Skynet (UK)'], examples:['SBIRS','WGS','Syracuse (FR)','Skynet (UK)'],
@@ -155,7 +155,7 @@ cats.forEach(c=>{
}); });
function resetInfo(){ function resetInfo(){
document.getElementById('info').innerHTML='<h3>🛰️ Satellite Missions</h3><p style="color:#64748b;text-align:center;padding:12px 0">Select a mission type above<br>to explore its animation.</p>'; document.getElementById('info').innerHTML='<h3>Satellite Missions</h3><p style="color:#64748b;text-align:center;padding:12px 0">Select a mission type above<br>to explore its animation.</p>';
} }
function showInfo(c){ function showInfo(c){
document.getElementById('info').innerHTML= document.getElementById('info').innerHTML=
@@ -170,7 +170,7 @@ function showInfo(c){
'<div style="color:#64748b;font-size:10.5px;margin-bottom:4px">Notable systems:</div>'+ '<div style="color:#64748b;font-size:10.5px;margin-bottom:4px">Notable systems:</div>'+
'<div>'+c.examples.map(function(e){return '<span class="tag">'+e+'</span>'}).join('')+'</div>'+ '<div>'+c.examples.map(function(e){return '<span class="tag">'+e+'</span>'}).join('')+'</div>'+
'<hr class="sep">'+ '<hr class="sep">'+
'<div class="fact"><b>💡 Did you know?</b><p>'+c.fact+'</p></div>'; '<div class="fact"><b>Key fact:</b><p>'+c.fact+'</p></div>';
} }
function updateLabel(){ function updateLabel(){
var el=document.getElementById('sceneLabel'); var el=document.getElementById('sceneLabel');
@@ -791,7 +791,7 @@ frame();
def render(): def render():
"""Render the satellite missions / types dashboard page.""" """Render the satellite missions / types dashboard page."""
st.markdown("## 🛰️ Satellite Missions — Types & Applications") st.markdown("## Satellite Missions — Types & Applications")
st.markdown( st.markdown(
"Explore **six categories** of satellite missions through unique animated scenes. " "Explore **six categories** of satellite missions through unique animated scenes. "
"Click a tab to switch between navigation, communication, observation, weather, " "Click a tab to switch between navigation, communication, observation, weather, "
@@ -803,23 +803,23 @@ def render():
# ── Educational content below ── # ── Educational content below ──
st.divider() st.divider()
st.markdown("### 📋 Mission Comparison") st.markdown("### Mission Comparison")
st.markdown(""" st.markdown("""
| Category | Orbit | Altitude | Key Band | Examples | | Category | Orbit | Altitude | Key Band | Examples |
|:---|:---:|:---:|:---:|:---| |:---|:---:|:---:|:---:|:---|
| 🧭 **Navigation** | MEO | 20 200 km | L-band | GPS, Galileo, GLONASS, BeiDou | | **Navigation** | MEO | 20 200 km | L-band | GPS, Galileo, GLONASS, BeiDou |
| 📡 **Communication** | GEO / LEO | 550 36 000 km | C / Ku / Ka | Starlink, Intelsat, SES | | **Communication** | GEO / LEO | 550 36 000 km | C / Ku / Ka | Starlink, Intelsat, SES |
| 📸 **Earth Observation** | LEO (SSO) | 500 800 km | X-band | Sentinel, Landsat, Planet | | **Earth Observation** | LEO (SSO) | 500 800 km | X-band | Sentinel, Landsat, Planet |
| 🌦️ **Weather** | GEO / LEO | 800 36 000 km | L / Ka | Meteosat, GOES, Himawari | | **Weather** | GEO / LEO | 800 36 000 km | L / Ka | Meteosat, GOES, Himawari |
| 🔭 **Science** | Various | Variable | S / X / Ka | JWST, Hubble, Gaia | | **Science** | Various | Variable | S / X / Ka | JWST, Hubble, Gaia |
| 🛡️ **Defense** | LEO / GEO / HEO | Variable | UHF / EHF | SBIRS, WGS, Syracuse | | **Defense** | LEO / GEO / HEO | Variable | UHF / EHF | SBIRS, WGS, Syracuse |
""") """)
col1, col2 = st.columns(2) col1, col2 = st.columns(2)
with col1: with col1:
with st.expander("🧭 Navigation Satellites"): with st.expander("Navigation Satellites"):
st.markdown(""" st.markdown("""
**How GNSS works:** **How GNSS works:**
@@ -842,7 +842,7 @@ With 4+ satellites the receiver solves for $(x, y, z, \\Delta t)$ —
- **BeiDou** (China) — 35+ sats - **BeiDou** (China) — 35+ sats
""") """)
with st.expander("📡 Communication Satellites"): with st.expander("Communication Satellites"):
st.markdown(""" st.markdown("""
**Evolution:** **Evolution:**
@@ -860,7 +860,7 @@ LEO at 550 km, but compensates with larger antennas, higher power
and advanced modulation (DVB-S2X, 256-APSK). and advanced modulation (DVB-S2X, 256-APSK).
""") """)
with st.expander("🌦️ Weather Satellites"): with st.expander("Weather Satellites"):
st.markdown(""" st.markdown("""
**Two approaches:** **Two approaches:**
@@ -873,7 +873,7 @@ and advanced modulation (DVB-S2X, 256-APSK).
""") """)
with col2: with col2:
with st.expander("📸 Earth Observation Satellites"): with st.expander("Earth Observation Satellites"):
st.markdown(""" st.markdown("""
**Imaging technologies:** **Imaging technologies:**
@@ -889,7 +889,7 @@ and advanced modulation (DVB-S2X, 256-APSK).
consistent solar illumination for temporal comparison. consistent solar illumination for temporal comparison.
""") """)
with st.expander("🔭 Science & Exploration"): with st.expander("Science & Exploration"):
st.markdown(""" st.markdown("""
| Mission | Location | Purpose | | Mission | Location | Purpose |
|:---|:---:|:---| |:---|:---:|:---|
@@ -902,7 +902,7 @@ consistent solar illumination for temporal comparison.
at ~160 **bits**/s with 23 W through NASA's 70 m DSN antennas. at ~160 **bits**/s with 23 W through NASA's 70 m DSN antennas.
""") """)
with st.expander("🛡️ Defense & Intelligence"): with st.expander("Defense & Intelligence"):
st.markdown(""" st.markdown("""
**Categories:** **Categories:**

View File

@@ -146,17 +146,17 @@ def render():
with col_img: with col_img:
st.image("Shannon.png", width=200) st.image("Shannon.png", width=200)
with col_title: with col_title:
st.markdown("# 📡 Shannon's Equation for Dummies") st.markdown("# Shannon's Equation for Dummies")
st.markdown( st.markdown(
"Exploration of Claude Shannon's channel capacity theorem — " "Exploration of Claude Shannon's channel capacity theorem — "
"the fundamental limit of digital communications." "the fundamental limit of digital communications."
) )
st.link_button("📖 Wiki: Claude Shannon", "https://en.wikipedia.org/wiki/Claude_Shannon") st.link_button("Wiki: Claude Shannon", "https://en.wikipedia.org/wiki/Claude_Shannon")
st.divider() st.divider()
# ── Input Parameters ── # ── Input Parameters ──
st.markdown("### ⚙️ Input Parameters") st.markdown("### Input Parameters")
col_in1, col_in2 = st.columns(2) col_in1, col_in2 = st.columns(2)
with col_in1: with col_in1:
@@ -177,15 +177,15 @@ def render():
cnr_values = [float(v.strip()) for v in cnr_input.split(",")] cnr_values = [float(v.strip()) for v in cnr_input.split(",")]
cnr_nyq = combine_cnr(*cnr_values) cnr_nyq = combine_cnr(*cnr_values)
except (ValueError, ZeroDivisionError): except (ValueError, ZeroDivisionError):
st.error("Invalid C/N values. Use comma-separated numbers (e.g., '12' or '12, 15').") st.error("Invalid C/N values. Use comma-separated numbers (e.g., '12' or '12, 15').")
return return
# ── Computed Results ── # ── Computed Results ──
cnr_linear, br_inf, c_n0, br_bw = shannon_points(bw_input, cnr_nyq) cnr_linear, br_inf, c_n0, br_bw = shannon_points(bw_input, cnr_nyq)
br_unit = c_n0 # Spectral efficiency = 1 br_unit = c_n0 # Spectral efficiency = 1
st.markdown("### 📊 Results") st.markdown("### Results")
st.info(THEORY_HELP["c_n0"], icon="") if st.checkbox("Show C/N₀ explanation", value=False) else None st.info(THEORY_HELP["c_n0"], icon=":material/info:") if st.checkbox("Show C/N₀ explanation", value=False) else None
m1, m2, m3, m4 = st.columns(4) m1, m2, m3, m4 = st.columns(4)
m1.metric("C/N₀", f"{c_n0:.1f} MHz", help=THEORY_HELP["c_n0"]) m1.metric("C/N₀", f"{c_n0:.1f} MHz", help=THEORY_HELP["c_n0"])
@@ -202,7 +202,7 @@ def render():
st.divider() st.divider()
# ── Sensitivity Analysis ── # ── Sensitivity Analysis ──
st.markdown("### 🔬 Sensitivity Analysis") st.markdown("### Sensitivity Analysis")
col_s1, col_s2, col_s3 = st.columns(3) col_s1, col_s2, col_s3 = st.columns(3)
with col_s1: with col_s1:
@@ -229,12 +229,12 @@ def render():
st.divider() st.divider()
# ── Graphs ── # ── Graphs ──
st.markdown("### 📈 Interactive Graphs") st.markdown("### Interactive Graphs")
tab_bw, tab_pow, tab_map = st.tabs([ tab_bw, tab_pow, tab_map = st.tabs([
"📶 Bandwidth Sensitivity", "Bandwidth Sensitivity",
"Power Sensitivity", "Power Sensitivity",
"🗺️ BR Factor Map", "BR Factor Map",
]) ])
with tab_bw: with tab_bw:
@@ -256,14 +256,14 @@ def render():
) )
# ── Help Section ── # ── Help Section ──
with st.expander("📘 Background Information"): with st.expander("Background Information"):
help_topic = st.selectbox( help_topic = st.selectbox(
"Choose a topic:", "Choose a topic:",
options=["shannon", "advanced", "help"], options=["shannon", "advanced", "help"],
format_func=lambda x: { format_func=lambda x: {
"shannon": "🧠 Shannon's Equation", "shannon": "Shannon's Equation",
"advanced": "🔧 Advanced (AWGN Model)", "advanced": "Advanced (AWGN Model)",
"help": "How to use this tool", "help": "How to use this tool",
}[x], }[x],
) )
st.markdown(THEORY_HELP[help_topic]) st.markdown(THEORY_HELP[help_topic])