Files
Python-Shannon/views/satellite_types.py
Poidevin, Antoine (ITOP CM) - AF ac6c0e1bdf
All checks were successful
Build & Deploy Shannon / 🏗️ Build & Deploy Shannon (push) Successful in 1m3s
refactor: remove emojis from titles and buttons for a cleaner UI
2026-02-20 10:50:04 +01:00

918 lines
37 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.
"""
Satellite Types — Mission Control Dashboard
=============================================
Each satellite category has its own unique animated scene:
- Navigation → GPS triangulation on a world map
- Communication → Data-flow network between ground stations
- Earth Observation → Top-down terrain scan with image strips
- Weather → Atmospheric cloud/rain/temperature visualisation
- Science → Deep-space telescope view (nebula zoom)
- Defense → Tactical radar sweep with blips
"""
import streamlit as st
import streamlit.components.v1 as components
_SATTYPES_HTML = r"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:transparent;overflow:hidden;font-family:'Segoe UI',system-ui,sans-serif;color:#e2e8f0}
/* ── Tab bar ── */
#tabs{
position:absolute;top:0;left:0;right:0;height:52px;
display:flex;align-items:center;justify-content:center;gap:6px;
background:rgba(10,18,32,0.92);border-bottom:1px solid rgba(79,195,247,0.15);
backdrop-filter:blur(10px);z-index:10;padding:0 10px;
}
.tab{
display:flex;align-items:center;gap:6px;
padding:8px 14px;border-radius:10px;cursor:pointer;
font-size:12px;font-weight:600;letter-spacing:0.3px;
border:1px solid transparent;transition:all 0.3s;
color:#94a3b8;white-space:nowrap;
}
.tab:hover{background:rgba(79,195,247,0.06);color:#cbd5e1}
.tab.active{
background:rgba(79,195,247,0.1);
border-color:rgba(79,195,247,0.3);color:#e2e8f0;
}
.tab .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
/* ── Info panel ── */
#info{
position:absolute;top:64px;right:14px;width:290px;
background:rgba(10,18,32,0.94);border:1px solid rgba(79,195,247,0.2);
border-radius:14px;padding:18px 20px;
backdrop-filter:blur(14px);box-shadow:0 8px 32px rgba(0,0,0,0.5);
font-size:12px;line-height:1.7;z-index:5;
transition:opacity 0.4s;
}
#info h3{color:#4FC3F7;font-size:13px;margin-bottom:10px;text-transform:uppercase;letter-spacing:1px;text-align:center}
.row{display:flex;justify-content:space-between;padding:2px 0}
.row .lbl{color:#64748b}.row .val{color:#cbd5e1;font-weight:600;text-align:right;max-width:160px}
.sep{border:none;border-top:1px solid rgba(79,195,247,0.1);margin:7px 0}
.tag{display:inline-block;background:rgba(79,195,247,0.08);border:1px solid rgba(79,195,247,0.18);
border-radius:6px;padding:1px 7px;margin:2px;font-size:10.5px;color:#4FC3F7}
.fact{background:rgba(79,195,247,0.05);border-radius:8px;padding:8px 10px;margin-top:6px}
.fact b{color:#4FC3F7;font-size:11px}
.fact p{color:#cbd5e1;font-size:11px;margin-top:3px;line-height:1.5}
/* ── Scene label ── */
#sceneLabel{
position:absolute;bottom:14px;left:50%;transform:translateX(-50%);
background:rgba(10,18,32,0.85);border:1px solid rgba(79,195,247,0.15);
border-radius:10px;padding:8px 18px;font-size:11px;color:#94a3b8;
backdrop-filter:blur(8px);text-align:center;z-index:5;
pointer-events:none;transition:opacity 0.3s;
}
#sceneLabel span{color:#e2e8f0;font-weight:700}
</style>
</head>
<body>
<canvas id="c"></canvas>
<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="sceneLabel"><span>Mission Control</span> — choose a category</div>
<script>
// ═══════════════════════════════════════════════════════════════
// DATA
// ═══════════════════════════════════════════════════════════════
const cats=[
{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',
precision:'< 1 m (dual-freq)',lifetime:'1215 yrs',
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.',
sceneHint:'Triangulation — 3 satellites fix your position'},
{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',
precision:'100+ Gbps (HTS)',lifetime:'1520 yrs',
examples:['Starlink','Intelsat','SES','OneWeb'],
fact:'A modern HTS can deliver 500+ Gbps — equivalent to 100 000 HD streams simultaneously.',
sceneHint:'Data flowing between ground stations'},
{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',
precision:'0.330 m resolution',lifetime:'57 yrs',
examples:['Sentinel','Landsat','Planet Doves','Pléiades'],
fact:'Planet Labs\' 200+ Doves image the entire land surface every single day at 3 m resolution.',
sceneHint:'Satellite scanning the terrain below'},
{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',
precision:'Full disk every 10 min',lifetime:'1015 yrs',
examples:['Meteosat','GOES','Himawari','FengYun'],
fact:'GOES-16 produces 3.6 TB of weather imagery per day across 16 spectral bands.',
sceneHint:'Atmospheric monitoring & cloud tracking'},
{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',
precision:'28 Mbps (JWST)',lifetime:'520+ yrs',
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.',
sceneHint:'Deep-space telescope view'},
{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',
precision:'SIGINT + IMINT',lifetime:'715 yrs',
examples:['SBIRS','WGS','Syracuse (FR)','Skynet (UK)'],
fact:'SBIRS infrared satellites detect missile launches within seconds from 36 000 km away by spotting the heat plume.',
sceneHint:'Tactical radar sweep'},
];
// ═══════════════════════════════════════════════════════════════
// CANVAS
// ═══════════════════════════════════════════════════════════════
const cv=document.getElementById('c'),cx=cv.getContext('2d');
let W,H;
function resize(){W=cv.width=window.innerWidth;H=cv.height=window.innerHeight}
window.addEventListener('resize',resize);resize();
let activeCat=null,t=0;
// Stars
const stars=Array.from({length:140},()=>({x:Math.random(),y:Math.random(),r:Math.random()*1+0.3,ph:Math.random()*6.28,sp:0.4+Math.random()*1.2}));
// ═══════════════════════════════════════════════════════════════
// BUILD TABS
// ═══════════════════════════════════════════════════════════════
const tabsEl=document.getElementById('tabs');
cats.forEach(c=>{
const d=document.createElement('div');
d.className='tab';d.dataset.id=c.id;
d.innerHTML='<span class="dot" style="background:'+c.color+'"></span>'+c.icon+' '+c.name;
d.onclick=()=>{
activeCat=activeCat===c.id?null:c.id;
document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('active',t.dataset.id===activeCat));
if(activeCat)showInfo(c);else resetInfo();
updateLabel();
};
tabsEl.appendChild(d);
});
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>';
}
function showInfo(c){
document.getElementById('info').innerHTML=
'<h3>'+c.icon+' '+c.name+'</h3>'+
'<div class="row"><span class="lbl">Orbit</span><span class="val">'+c.orbit+'</span></div>'+
'<div class="row"><span class="lbl">Altitude</span><span class="val">'+c.alt+'</span></div>'+
'<div class="row"><span class="lbl">Band</span><span class="val">'+c.band+'</span></div>'+
'<div class="row"><span class="lbl">Power / Payload</span><span class="val">'+c.power+'</span></div>'+
'<div class="row"><span class="lbl">Performance</span><span class="val">'+c.precision+'</span></div>'+
'<div class="row"><span class="lbl">Lifetime</span><span class="val">'+c.lifetime+'</span></div>'+
'<hr class="sep">'+
'<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>'+
'<hr class="sep">'+
'<div class="fact"><b>Key fact:</b><p>'+c.fact+'</p></div>';
}
function updateLabel(){
var el=document.getElementById('sceneLabel');
if(!activeCat){el.innerHTML='<span>Mission Control</span> — choose a category';return}
var c=cats.find(function(x){return x.id===activeCat});
el.innerHTML='<span>'+c.icon+' '+c.name+'</span> — '+c.sceneHint;
}
// ═══════════════════════════════════════════════════════════════
// SCENE HELPERS
// ═══════════════════════════════════════════════════════════════
var sceneX=0, sceneY=52;
function sceneW(){return W-310}
function sceneH(){return H-52}
function drawBg(){
var g=cx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#060d19');g.addColorStop(1,'#0a1628');
cx.fillStyle=g;cx.fillRect(0,0,W,H);
stars.forEach(function(s){
var a=0.2+Math.sin(t*s.sp+s.ph)*0.3+0.3;
cx.beginPath();cx.arc(s.x*W,s.y*H,s.r,0,6.28);
cx.fillStyle='rgba(255,255,255,'+a+')';cx.fill();
});
}
// ═══════════════════════════════════════════════════════════════
// SCENE: NAVIGATION — GPS triangulation
// ═══════════════════════════════════════════════════════════════
var navReceiverPath=[];
for(var ni=0;ni<200;ni++){
navReceiverPath.push({fx:0.35+Math.sin(ni*0.04)*0.18, fy:0.7+Math.cos(ni*0.06)*0.1});
}
var navIdx=0;
function drawNav(){
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var col='52,211,153';
// Ground / horizon
cx.fillStyle='rgba(10,30,20,0.5)';
cx.fillRect(ox,oy+sh*0.65,sw,sh*0.35);
cx.strokeStyle='rgba(52,211,153,0.15)';cx.lineWidth=1;
cx.beginPath();cx.moveTo(ox,oy+sh*0.65);cx.lineTo(ox+sw,oy+sh*0.65);cx.stroke();
// Grid on ground
cx.strokeStyle='rgba(52,211,153,0.06)';
for(var gi=0;gi<20;gi++){
var gx=ox+(sw/20)*gi;
cx.beginPath();cx.moveTo(gx,oy+sh*0.65);cx.lineTo(gx,oy+sh);cx.stroke();
}
for(var gj=0;gj<6;gj++){
var gy=oy+sh*0.65+((sh*0.35)/6)*gj;
cx.beginPath();cx.moveTo(ox,gy);cx.lineTo(ox+sw,gy);cx.stroke();
}
// 3 satellites positions
var sats=[
{x:ox+sw*0.18,y:oy+sh*0.1},{x:ox+sw*0.52,y:oy+sh*0.06},{x:ox+sw*0.82,y:oy+sh*0.15}
];
// Receiver
navIdx=(navIdx+0.3)%navReceiverPath.length;
var rp=navReceiverPath[Math.floor(navIdx)];
var rx=ox+sw*rp.fx,ry=oy+sh*rp.fy;
// Draw range circles from each sat
sats.forEach(function(s,i){
var dist=Math.sqrt((s.x-rx)*(s.x-rx)+(s.y-ry)*(s.y-ry));
// Fixed-speed expanding circles (maxR = constant, not dist-dependent)
var maxR=280;
var speed=35;
for(var r=0;r<3;r++){
var cr=((t*speed+i*93+r*(maxR/3))%maxR);
var alpha=cr<dist ? 0.22*(1-cr/maxR) : 0.06*(1-cr/maxR);
cx.beginPath();cx.arc(s.x,s.y,cr,0,6.28);
cx.strokeStyle='rgba('+col+','+alpha+')';cx.lineWidth=1.5;cx.stroke();
}
var lineAlpha=0.08+Math.sin(t*2+i)*0.05;
cx.beginPath();cx.moveTo(s.x,s.y);cx.lineTo(rx,ry);
cx.strokeStyle='rgba('+col+','+lineAlpha+')';cx.lineWidth=0.8;
cx.setLineDash([4,6]);cx.stroke();cx.setLineDash([]);
cx.save();
cx.shadowColor='rgba('+col+',0.6)';cx.shadowBlur=12;
cx.beginPath();cx.arc(s.x,s.y,7,0,6.28);
cx.fillStyle='rgba('+col+',0.9)';cx.fill();
cx.restore();
cx.strokeStyle='rgba('+col+',0.5)';cx.lineWidth=2;
cx.beginPath();cx.moveTo(s.x-12,s.y);cx.lineTo(s.x+12,s.y);cx.stroke();
cx.fillStyle='rgba('+col+',0.7)';cx.font='bold 10px system-ui';cx.textAlign='center';
cx.fillText('SV'+(i+1),s.x,s.y-14);
});
// Receiver
cx.save();
cx.shadowColor='rgba(52,211,153,0.8)';cx.shadowBlur=16;
cx.beginPath();cx.arc(rx,ry,6,0,6.28);
cx.fillStyle='#34d399';cx.fill();
cx.restore();
cx.strokeStyle='rgba(52,211,153,0.6)';cx.lineWidth=1;
cx.beginPath();cx.moveTo(rx-12,ry);cx.lineTo(rx+12,ry);cx.moveTo(rx,ry-12);cx.lineTo(rx,ry+12);cx.stroke();
cx.beginPath();cx.arc(rx,ry,10,0,6.28);cx.stroke();
cx.fillStyle='#34d399';cx.font='bold 10px monospace';cx.textAlign='center';
var lat=(rp.fy*90-20).toFixed(2),lon=(rp.fx*360-90).toFixed(2);
cx.fillText(lat+'°N '+lon+'°E',rx,ry+24);
cx.font='9px system-ui';cx.fillStyle='rgba(52,211,153,0.6)';
cx.fillText('Fix: 3D — Accuracy: 0.8 m',rx,ry+36);
cx.textAlign='start';
}
// ═══════════════════════════════════════════════════════════════
// SCENE: COMMUNICATION — Network data flow
// ═══════════════════════════════════════════════════════════════
var comNodes=[];
var comLinks=[];
var comPackets=[];
var comInit=false;
function initCom(){
if(comInit)return;comInit=true;
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var positions=[
{fx:0.08,fy:0.4,label:'New York'},{fx:0.25,fy:0.25,label:'London'},
{fx:0.38,fy:0.35,label:'Paris'},{fx:0.55,fy:0.22,label:'Moscow'},
{fx:0.7,fy:0.45,label:'Dubai'},{fx:0.82,fy:0.3,label:'Tokyo'},
{fx:0.6,fy:0.7,label:'Mumbai'},{fx:0.2,fy:0.65,label:'São Paulo'},
{fx:0.45,fy:0.6,label:'Nairobi'},{fx:0.9,fy:0.6,label:'Sydney'},
];
positions.forEach(function(p){
comNodes.push({x:ox+sw*p.fx,y:oy+sh*p.fy,label:p.label,pulse:Math.random()*6.28});
});
var pairs=[[0,1],[1,2],[2,3],[3,5],[4,5],[4,6],[6,8],[8,7],[7,0],[1,4],[2,8],[5,9],[6,9],[0,7],[3,4]];
pairs.forEach(function(pr){comLinks.push({a:pr[0],b:pr[1]})});
for(var pi=0;pi<20;pi++){
var l=comLinks[Math.floor(Math.random()*comLinks.length)];
comPackets.push({link:l,progress:Math.random(),speed:0.003+Math.random()*0.004,forward:Math.random()>0.5});
}
}
function drawCom(){
initCom();
var col='79,195,247';
comLinks.forEach(function(l){
var a=comNodes[l.a],b=comNodes[l.b];
var mx=(a.x+b.x)/2,my=(a.y+b.y)/2-30;
cx.beginPath();cx.moveTo(a.x,a.y);cx.quadraticCurveTo(mx,my,b.x,b.y);
cx.strokeStyle='rgba('+col+',0.1)';cx.lineWidth=1;cx.stroke();
});
comPackets.forEach(function(p){
p.progress+=p.speed*(p.forward?1:-1);
if(p.progress>1||p.progress<0){
p.forward=!p.forward;
p.progress=Math.max(0,Math.min(1,p.progress));
}
var a=comNodes[p.link.a],b=comNodes[p.link.b];
var mx=(a.x+b.x)/2,my=(a.y+b.y)/2-30;
var tt=p.progress;
var px=(1-tt)*(1-tt)*a.x+2*(1-tt)*tt*mx+tt*tt*b.x;
var py=(1-tt)*(1-tt)*a.y+2*(1-tt)*tt*my+tt*tt*b.y;
cx.save();
cx.shadowColor='rgba('+col+',0.7)';cx.shadowBlur=6;
cx.beginPath();cx.arc(px,py,2.5,0,6.28);
cx.fillStyle='rgba('+col+',0.85)';cx.fill();
cx.restore();
});
comNodes.forEach(function(n){
var pr=8+Math.sin(t*2+n.pulse)*3;
cx.beginPath();cx.arc(n.x,n.y,pr,0,6.28);
cx.strokeStyle='rgba('+col+',0.15)';cx.lineWidth=1;cx.stroke();
cx.save();
cx.shadowColor='rgba('+col+',0.5)';cx.shadowBlur=10;
cx.beginPath();cx.arc(n.x,n.y,5,0,6.28);
cx.fillStyle='rgba('+col+',0.9)';cx.fill();
cx.restore();
cx.fillStyle='rgba('+col+',0.65)';cx.font='9px system-ui';cx.textAlign='center';
cx.fillText(n.label,n.x,n.y+18);
});
cx.fillStyle='rgba(79,195,247,0.4)';cx.font='11px monospace';cx.textAlign='start';
var tp=Math.floor(280+Math.sin(t)*40);
cx.fillText('Aggregate throughput: '+tp+' Gbps',sceneX+16,sceneY+sceneH()-20);
cx.textAlign='start';
}
// ═══════════════════════════════════════════════════════════════
// SCENE: EARTH OBSERVATION — Scanning terrain
// ═══════════════════════════════════════════════════════════════
function drawEO(){
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var col='167,139,250';
// Terrain
var groundY=oy+sh*0.7;
cx.fillStyle='rgba(30,20,50,0.5)';
cx.fillRect(ox,groundY,sw,sh*0.3);
cx.beginPath();cx.moveTo(ox,groundY);
for(var x=0;x<sw;x+=4){
var h=Math.sin(x*0.01)*15+Math.sin(x*0.035+2)*8+Math.cos(x*0.007)*12;
cx.lineTo(ox+x,groundY-h);
}
cx.lineTo(ox+sw,groundY+10);cx.lineTo(ox,groundY+10);cx.closePath();
cx.fillStyle='rgba(60,40,90,0.4)';cx.fill();
cx.strokeStyle='rgba('+col+',0.2)';cx.lineWidth=1;cx.stroke();
cx.strokeStyle='rgba('+col+',0.04)';
for(var gx=0;gx<sw;gx+=30){cx.beginPath();cx.moveTo(ox+gx,groundY-20);cx.lineTo(ox+gx,oy+sh);cx.stroke();}
for(var gy=groundY;gy<oy+sh;gy+=20){cx.beginPath();cx.moveTo(ox,gy);cx.lineTo(ox+sw,gy);cx.stroke();}
var satX=ox+((t*25)%sw);
var satY=oy+sh*0.12;
// Scan beam
var beamW=40;
cx.beginPath();
cx.moveTo(satX,satY+8);
cx.lineTo(satX-beamW,groundY-15);
cx.lineTo(satX+beamW,groundY-15);
cx.closePath();
var beamG=cx.createLinearGradient(satX,satY,satX,groundY);
beamG.addColorStop(0,'rgba('+col+',0.25)');
beamG.addColorStop(1,'rgba('+col+',0.02)');
cx.fillStyle=beamG;cx.fill();
cx.beginPath();
cx.moveTo(satX-beamW,groundY-15);
cx.lineTo(satX+beamW,groundY-15);
cx.strokeStyle='rgba('+col+',0.7)';cx.lineWidth=2;cx.stroke();
// Image strips
var stripY=oy+sh*0.45;
var stripH=sh*0.18;
var scannedW=satX-ox-beamW;
if(scannedW>0){
var bands=['rgba(80,200,120,0.15)','rgba(200,80,80,0.12)','rgba(80,80,200,0.12)'];
bands.forEach(function(b,i){
cx.fillStyle=b;
cx.fillRect(ox,stripY+i*(stripH/3),scannedW,stripH/3);
});
cx.strokeStyle='rgba('+col+',0.2)';cx.lineWidth=0.5;
cx.strokeRect(ox,stripY,scannedW,stripH);
cx.font='9px monospace';cx.textAlign='start';
['NIR','RED','BLUE'].forEach(function(b,i){
cx.fillStyle='rgba('+col+',0.5)';
cx.fillText(b,ox+4,stripY+i*(stripH/3)+12);
});
}
// Satellite body
cx.save();
cx.shadowColor='rgba('+col+',0.7)';cx.shadowBlur=14;
cx.fillStyle='rgba('+col+',0.9)';
cx.fillRect(satX-6,satY-4,12,8);
cx.restore();
cx.fillStyle='rgba('+col+',0.4)';
cx.fillRect(satX-22,satY-2,14,4);
cx.fillRect(satX+8,satY-2,14,4);
cx.beginPath();cx.arc(satX,satY+5,3,0,6.28);
cx.fillStyle='rgba('+col+',0.8)';cx.fill();
cx.fillStyle='rgba('+col+',0.6)';cx.font='bold 10px system-ui';cx.textAlign='center';
cx.fillText('Sentinel-2A — SSO 786 km',satX,satY-14);
cx.fillStyle='rgba('+col+',0.4)';cx.font='10px monospace';cx.textAlign='start';
cx.fillText('GSD: 10 m | Swath: 290 km | Bands: 13',ox+16,oy+sh-20);
cx.textAlign='start';
}
// ═══════════════════════════════════════════════════════════════
// SCENE: WEATHER — Atmospheric monitoring
// ═══════════════════════════════════════════════════════════════
var wxClouds=Array.from({length:18},function(){return{
x:Math.random(),y:0.3+Math.random()*0.4,
r:20+Math.random()*40,sp:0.1+Math.random()*0.3,
opacity:0.15+Math.random()*0.2
}});
var wxRaindrops=Array.from({length:60},function(){return{
x:Math.random(),y:Math.random(),sp:2+Math.random()*3,len:4+Math.random()*8
}});
function drawWx(){
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var col='251,191,36';
var tg=cx.createLinearGradient(ox,oy,ox,oy+sh);
tg.addColorStop(0,'rgba(30,10,60,0.3)');
tg.addColorStop(0.4,'rgba(10,20,50,0.3)');
tg.addColorStop(1,'rgba(10,30,40,0.3)');
cx.fillStyle=tg;cx.fillRect(ox,oy,sw,sh);
// Isobars
cx.strokeStyle='rgba(251,191,36,0.06)';cx.lineWidth=1;
for(var ii=0;ii<8;ii++){
cx.beginPath();
var baseY=oy+sh*0.2+ii*(sh*0.08);
cx.moveTo(ox,baseY);
for(var ix=0;ix<sw;ix+=5){
var iy=baseY+Math.sin((ix+t*20)*0.008+ii)*15;
cx.lineTo(ox+ix,iy);
}
cx.stroke();
if(ii%2===0){
cx.fillStyle='rgba(251,191,36,0.15)';cx.font='8px monospace';
cx.fillText((1013-ii*4)+'hPa',ox+sw-60,baseY-3);
}
}
// Clouds
wxClouds.forEach(function(c){
c.x+=c.sp*0.0003;
if(c.x>1.15)c.x=-0.15;
var cloudX=ox+sw*c.x,cloudY=oy+sh*c.y;
cx.fillStyle='rgba(200,210,230,'+c.opacity+')';
for(var ci=0;ci<4;ci++){
cx.beginPath();
cx.arc(cloudX+ci*c.r*0.4-c.r*0.6, cloudY+Math.sin(ci*1.5)*c.r*0.15, c.r*0.4,0,6.28);
cx.fill();
}
});
// Rain
cx.strokeStyle='rgba(100,180,255,0.3)';cx.lineWidth=1;
wxRaindrops.forEach(function(d){
d.y+=d.sp*0.003;
if(d.y>1)d.y=0.4;
if(d.y>0.5){
var dx=ox+sw*d.x,dy=oy+sh*d.y;
cx.beginPath();cx.moveTo(dx,dy);cx.lineTo(dx-1,dy+d.len);cx.stroke();
}
});
// GEO satellite
var satX=ox+sw*0.5,satY=oy+30;
cx.save();cx.shadowColor='rgba('+col+',0.6)';cx.shadowBlur=10;
cx.beginPath();cx.arc(satX,satY,6,0,6.28);
cx.fillStyle='rgba('+col+',0.9)';cx.fill();cx.restore();
cx.strokeStyle='rgba('+col+',0.4)';cx.lineWidth=2;
cx.beginPath();cx.moveTo(satX-14,satY);cx.lineTo(satX+14,satY);cx.stroke();
// Scan swath
cx.beginPath();
cx.moveTo(satX,satY+8);
cx.lineTo(ox+sw*0.1,oy+sh*0.9);
cx.lineTo(ox+sw*0.9,oy+sh*0.9);
cx.closePath();
var sg=cx.createLinearGradient(satX,satY,satX,oy+sh);
sg.addColorStop(0,'rgba('+col+',0.08)');sg.addColorStop(1,'rgba('+col+',0.01)');
cx.fillStyle=sg;cx.fill();
// Rotating scan line
var scanAngle=(t*0.5)%(Math.PI*2);
var scanEndX=satX+Math.sin(scanAngle)*sw*0.4;
var scanEndY=satY+Math.abs(Math.cos(scanAngle))*sh*0.85;
cx.beginPath();cx.moveTo(satX,satY);cx.lineTo(scanEndX,scanEndY);
cx.strokeStyle='rgba('+col+',0.3)';cx.lineWidth=1.5;cx.stroke();
// Temperature scale
cx.fillStyle='rgba('+col+',0.5)';cx.font='9px monospace';cx.textAlign='start';
var temps=['-60°C','-30°C','0°C','+20°C','+35°C'];
temps.forEach(function(tmp,i){
var ty=oy+sh*0.15+i*(sh*0.17);
cx.fillText(tmp,ox+8,ty);
});
cx.fillStyle='rgba('+col+',0.6)';cx.font='bold 10px system-ui';cx.textAlign='center';
cx.fillText('Meteosat — GEO 36 000 km — Full Disk Imaging',satX,satY-12);
cx.textAlign='start';
cx.fillStyle='rgba('+col+',0.35)';cx.font='10px monospace';
cx.fillText('Update: 10 min | 16 bands | 3.6 TB/day',ox+16,oy+sh-20);
}
// ═══════════════════════════════════════════════════════════════
// SCENE: SCIENCE — Deep space telescope view
// ═══════════════════════════════════════════════════════════════
var sciStars=Array.from({length:300},function(){return{
x:Math.random(),y:Math.random(),
r:Math.random()*1.6+0.2,
hue:Math.random()*60+200,
br:0.3+Math.random()*0.7,
twinkle:Math.random()*6.28
}});
var sciNebula=Array.from({length:12},function(){return{
x:0.3+Math.random()*0.4,y:0.3+Math.random()*0.4,
r:40+Math.random()*80,
hue:Math.random()*360,
alpha:0.03+Math.random()*0.04
}});
function drawSci(){
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var col='244,114,182';
cx.fillStyle='rgba(3,3,12,0.6)';cx.fillRect(ox,oy,sw,sh);
sciNebula.forEach(function(n){
var nx=ox+sw*n.x,ny=oy+sh*n.y;
var grad=cx.createRadialGradient(nx,ny,0,nx,ny,n.r);
grad.addColorStop(0,'hsla('+n.hue+',70%,50%,'+(n.alpha+Math.sin(t*0.5+n.hue)*0.01)+')');
grad.addColorStop(1,'transparent');
cx.fillStyle=grad;
cx.fillRect(nx-n.r,ny-n.r,n.r*2,n.r*2);
});
sciStars.forEach(function(s){
var alpha=s.br*(0.5+Math.sin(t*1.5+s.twinkle)*0.5);
cx.beginPath();
cx.arc(ox+sw*s.x,oy+sh*s.y,s.r,0,6.28);
cx.fillStyle='hsla('+s.hue+',60%,80%,'+alpha+')';
cx.fill();
});
// Telescope reticle
var rX=ox+sw*0.42,rY=oy+sh*0.45;
var rR=sh*0.28;
cx.strokeStyle='rgba('+col+',0.15)';cx.lineWidth=0.8;
cx.beginPath();cx.arc(rX,rY,rR,0,6.28);cx.stroke();
cx.beginPath();cx.arc(rX,rY,rR*0.6,0,6.28);cx.stroke();
cx.beginPath();cx.moveTo(rX-rR,rY);cx.lineTo(rX+rR,rY);cx.stroke();
cx.beginPath();cx.moveTo(rX,rY-rR);cx.lineTo(rX,rY+rR);cx.stroke();
[0,Math.PI/2,Math.PI,3*Math.PI/2].forEach(function(a){
var ix=rX+Math.cos(a)*rR,iy=rY+Math.sin(a)*rR;
cx.beginPath();
cx.moveTo(ix,iy);cx.lineTo(ix+Math.cos(a)*10,iy+Math.sin(a)*10);
cx.strokeStyle='rgba('+col+',0.4)';cx.lineWidth=2;cx.stroke();
});
// Galaxy in reticle
var gpulse=0.7+Math.sin(t*1.2)*0.3;
var galGrad=cx.createRadialGradient(rX,rY,0,rX,rY,28*gpulse);
galGrad.addColorStop(0,'rgba(255,200,100,'+(0.3*gpulse)+')');
galGrad.addColorStop(0.4,'rgba('+col+','+(0.15*gpulse)+')');
galGrad.addColorStop(1,'transparent');
cx.fillStyle=galGrad;
cx.fillRect(rX-40,rY-40,80,80);
// Spiral arms
cx.save();cx.translate(rX,rY);cx.rotate(t*0.1);
cx.strokeStyle='rgba(255,200,140,'+(0.1*gpulse)+')';cx.lineWidth=2;
cx.beginPath();
for(var sa=0;sa<Math.PI*3;sa+=0.1){
var sr=3+sa*5;
cx.lineTo(Math.cos(sa)*sr,Math.sin(sa)*sr);
}
cx.stroke();
cx.beginPath();
for(var sa2=0;sa2<Math.PI*3;sa2+=0.1){
var sr2=3+sa2*5;
cx.lineTo(-Math.cos(sa2)*sr2,-Math.sin(sa2)*sr2);
}
cx.stroke();
cx.restore();
// Data readout
cx.fillStyle='rgba('+col+',0.5)';cx.font='9px monospace';cx.textAlign='start';
var lines=[
'Target: SMACS 0723 — z = 7.66',
'RA: 07h 23m 19.5s Dec: -73° 27\' 15"',
'Exposure: NIRCam F200W — 12.5 hrs',
'Signal: '+(14+Math.sin(t)*2).toFixed(1)+' e\u207B/s/px',
];
lines.forEach(function(l,i){
cx.fillText(l,ox+16,oy+sh-60+i*14);
});
cx.fillStyle='rgba('+col+',0.6)';cx.font='bold 10px system-ui';cx.textAlign='center';
cx.fillText('JWST — NIRCam Deep Field — L2 Lagrange Point',ox+sw*0.42,oy+20);
cx.textAlign='start';
}
// ═══════════════════════════════════════════════════════════════
// SCENE: DEFENSE — Tactical radar sweep
// ═══════════════════════════════════════════════════════════════
var defBlips=Array.from({length:12},function(){return{
angle:Math.random()*6.28,
dist:0.2+Math.random()*0.7,
type:['friendly','hostile','unknown'][Math.floor(Math.random()*3)],
label:['F-16','MiG-29','UAV','Ship','Sub','SAM','AWACS','Tanker','C2','Helo','Drone','Cargo'][Math.floor(Math.random()*12)],
blinkPhase:Math.random()*6.28
}});
function drawDef(){
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var col='251,146,60';
var rcx2=ox+sw*0.42,rcy2=oy+sh*0.52;
var rr=Math.min(sw,sh)*0.38;
cx.fillStyle='rgba(5,15,5,0.4)';
cx.beginPath();cx.arc(rcx2,rcy2,rr+10,0,6.28);cx.fill();
for(var ri=1;ri<=4;ri++){
cx.beginPath();cx.arc(rcx2,rcy2,rr*ri/4,0,6.28);
cx.strokeStyle='rgba('+col+','+(0.08+ri*0.02)+')';cx.lineWidth=0.8;cx.stroke();
cx.fillStyle='rgba('+col+',0.3)';cx.font='8px monospace';
cx.fillText(ri*100+'km',rcx2+rr*ri/4+4,rcy2-3);
}
cx.strokeStyle='rgba('+col+',0.07)';cx.lineWidth=0.5;
cx.beginPath();cx.moveTo(rcx2-rr,rcy2);cx.lineTo(rcx2+rr,rcy2);cx.stroke();
cx.beginPath();cx.moveTo(rcx2,rcy2-rr);cx.lineTo(rcx2,rcy2+rr);cx.stroke();
cx.beginPath();cx.moveTo(rcx2-rr*0.707,rcy2-rr*0.707);cx.lineTo(rcx2+rr*0.707,rcy2+rr*0.707);cx.stroke();
cx.beginPath();cx.moveTo(rcx2+rr*0.707,rcy2-rr*0.707);cx.lineTo(rcx2-rr*0.707,rcy2+rr*0.707);cx.stroke();
// Sweep
var sweepAngle=(t*0.8)%(Math.PI*2);
cx.save();
cx.translate(rcx2,rcy2);
cx.rotate(sweepAngle);
for(var si=0;si<40;si++){
var sa=-si*0.02;
var salpha=0.25*(1-si/40);
cx.beginPath();
cx.moveTo(0,0);
cx.lineTo(Math.cos(sa)*rr,Math.sin(sa)*rr);
cx.strokeStyle='rgba('+col+','+salpha+')';cx.lineWidth=1;cx.stroke();
}
cx.beginPath();cx.moveTo(0,0);cx.lineTo(rr,0);
cx.strokeStyle='rgba('+col+',0.7)';cx.lineWidth=2;cx.stroke();
cx.restore();
// Blips
defBlips.forEach(function(b){
var bx=rcx2+Math.cos(b.angle)*b.dist*rr;
var by=rcy2+Math.sin(b.angle)*b.dist*rr;
var angleDiff=(sweepAngle-b.angle+Math.PI*4)%(Math.PI*2);
var vis=angleDiff<Math.PI*1.5 ? Math.max(0,1-angleDiff/(Math.PI*1.5)) : 0;
if(vis<0.05)return;
var colors={friendly:'rgba(52,211,153,',hostile:'rgba(239,68,68,',unknown:'rgba(251,191,36,'};
var bc=colors[b.type];
cx.save();
cx.globalAlpha=vis;
cx.shadowColor=bc+'0.8)';cx.shadowBlur=8;
cx.beginPath();cx.arc(bx,by,3.5,0,6.28);
cx.fillStyle=bc+'0.9)';cx.fill();
cx.restore();
if(vis>0.4){
cx.globalAlpha=vis*0.7;
cx.fillStyle=bc+'0.7)';cx.font='8px monospace';cx.textAlign='center';
cx.fillText(b.label,bx,by-10);
cx.globalAlpha=1;
}
});
cx.globalAlpha=1;
// Legend
cx.font='9px system-ui';cx.textAlign='start';
var legendY=oy+sh-55;
[['● Friendly','rgba(52,211,153,0.7)'],['● Hostile','rgba(239,68,68,0.7)'],['● Unknown','rgba(251,191,36,0.7)']].forEach(function(item,i){
cx.fillStyle=item[1];cx.fillText(item[0],ox+16,legendY+i*14);
});
cx.fillStyle='rgba('+col+',0.5)';cx.font='bold 10px system-ui';cx.textAlign='center';
cx.fillText('SBIRS / EW — Surveillance & Early Warning',rcx2,oy+20);
cx.font='9px monospace';cx.fillStyle='rgba('+col+',0.35)';
cx.fillText('Sweep rate: 7.6 RPM | Range: 400 km | Tracks: '+defBlips.length,rcx2,oy+sh-20);
cx.textAlign='start';
}
// ═══════════════════════════════════════════════════════════════
// IDLE SCENE — no category selected
// ═══════════════════════════════════════════════════════════════
function drawIdle(){
var sw=sceneW(),sh=sceneH(),ox=sceneX,oy=sceneY;
var cx0=ox+sw*0.42,cy0=oy+sh*0.5;
var hexR=Math.min(sw,sh)*0.22;
cats.forEach(function(ic,i){
var angle=i*(Math.PI*2/cats.length)-Math.PI/2+t*0.15;
var ix=cx0+Math.cos(angle)*hexR;
var iy=cy0+Math.sin(angle)*hexR+Math.sin(t*1.5+i)*8;
cx.beginPath();cx.arc(cx0,cy0,hexR,0,6.28);
cx.strokeStyle='rgba(79,195,247,0.04)';cx.lineWidth=0.8;cx.stroke();
cx.save();
cx.shadowColor='rgba('+ic.rgb+',0.4)';cx.shadowBlur=18;
cx.font='28px system-ui';cx.textAlign='center';cx.textBaseline='middle';
cx.fillText(ic.icon,ix,iy);
cx.restore();
cx.fillStyle=ic.color;cx.font='bold 10px system-ui';cx.textAlign='center';cx.textBaseline='top';
cx.fillText(ic.name,ix,iy+22);
cx.textBaseline='alphabetic';
});
cx.fillStyle='#64748b';cx.font='13px system-ui';cx.textAlign='center';
cx.fillText('Select a mission type to explore',cx0,cy0+4);
cx.textAlign='start';
}
// ═══════════════════════════════════════════════════════════════
// MAIN LOOP
// ═══════════════════════════════════════════════════════════════
var scenes={nav:drawNav,com:drawCom,eo:drawEO,wx:drawWx,sci:drawSci,def:drawDef};
function frame(){
t+=0.016;
cx.clearRect(0,0,W,H);
drawBg();
if(activeCat && scenes[activeCat]){
scenes[activeCat]();
} else {
drawIdle();
}
requestAnimationFrame(frame);
}
frame();
</script>
</body>
</html>
"""
def render():
"""Render the satellite missions / types dashboard page."""
st.markdown("## Satellite Missions — Types & Applications")
st.markdown(
"Explore **six categories** of satellite missions through unique animated scenes. "
"Click a tab to switch between navigation, communication, observation, weather, "
"science and defense — each with its own visual story."
)
st.divider()
components.html(_SATTYPES_HTML, height=720, scrolling=False)
# ── Educational content below ──
st.divider()
st.markdown("### Mission Comparison")
st.markdown("""
| Category | Orbit | Altitude | Key Band | Examples |
|:---|:---:|:---:|:---:|:---|
| **Navigation** | MEO | 20 200 km | L-band | GPS, Galileo, GLONASS, BeiDou |
| **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 |
| **Weather** | GEO / LEO | 800 36 000 km | L / Ka | Meteosat, GOES, Himawari |
| **Science** | Various | Variable | S / X / Ka | JWST, Hubble, Gaia |
| **Defense** | LEO / GEO / HEO | Variable | UHF / EHF | SBIRS, WGS, Syracuse |
""")
col1, col2 = st.columns(2)
with col1:
with st.expander("Navigation Satellites"):
st.markdown("""
**How GNSS works:**
Each satellite broadcasts its precise position and exact time
(from onboard atomic clocks). A receiver picks up
signals from $\\geq 4$ satellites and solves the position equations:
$$d_i = c \\cdot (t_{\\text{rx}} - t_{\\text{tx},i})$$
Where $d_i$ is the pseudorange to satellite $i$, $c$ is the speed of
light, and $t_{\\text{rx}}$ is the receiver clock time.
With 4+ satellites the receiver solves for $(x, y, z, \\Delta t)$ —
3D position plus its own clock error.
**Key systems:**
- **GPS** (USA) — 31 sats, L1 / L2 / L5
- **Galileo** (EU) — 30 sats, E1 / E5a / E5b / E6
- **GLONASS** (Russia) — 24 sats
- **BeiDou** (China) — 35+ sats
""")
with st.expander("Communication Satellites"):
st.markdown("""
**Evolution:**
1. **1960s** — Early Bird: 240 voice channels
2. **1980s** — Large GEO: thousands of transponders
3. **2000s** — HTS: spot beams, frequency reuse
4. **2020s** — Mega-constellations (Starlink 6 000+ LEO)
**Shannon connection:**
$$C = B \\cdot \\log_2\\!\\left(1 + \\frac{C}{N}\\right)$$
A GEO satellite at 36 000 km suffers ~50 dB more path loss than
LEO at 550 km, but compensates with larger antennas, higher power
and advanced modulation (DVB-S2X, 256-APSK).
""")
with st.expander("Weather Satellites"):
st.markdown("""
**Two approaches:**
1. **GEO** (Meteosat, GOES, Himawari) — continuous monitoring,
full disk every 1015 min, 16+ spectral bands.
2. **LEO polar** (MetOp, NOAA) — higher resolution but 2 passes/day,
microwave sounders penetrate clouds.
**Data volume:** GOES-16 generates ~3.6 TB/day of imagery.
""")
with col2:
with st.expander("Earth Observation Satellites"):
st.markdown("""
**Imaging technologies:**
| Type | Resolution | Use |
|:---|:---:|:---|
| **Optical** | 0.3 1 m | Urban mapping, defence |
| **Multispectral** | 5 30 m | Agriculture (NDVI) |
| **Hyperspectral** | 30 m, 200+ bands | Mineral detection |
| **SAR** | 1 10 m | All-weather imaging |
| **InSAR** | mm displacement | Ground subsidence |
**Sun-Synchronous Orbit (SSO):** ~98° inclination ensures
consistent solar illumination for temporal comparison.
""")
with st.expander("Science & Exploration"):
st.markdown("""
| Mission | Location | Purpose |
|:---|:---:|:---|
| **JWST** | L2 (1.5 M km) | IR astronomy |
| **Hubble** | LEO (540 km) | Optical / UV |
| **Gaia** | L2 | 3D map of 1.8 B stars |
| **Chandra** | HEO | X-ray astronomy |
**Deep space challenge:** Voyager 1 at 24 billion km transmits
at ~160 **bits**/s with 23 W through NASA's 70 m DSN antennas.
""")
with st.expander("Defense & Intelligence"):
st.markdown("""
**Categories:**
- **MILCOM** — WGS, AEHF, Syracuse (secure links, EHF)
- **SIGINT** — intercept electromagnetic emissions
- **IMINT** — high-res optical/radar reconnaissance
- **Early Warning** — SBIRS IR missile detection from GEO
- **ELINT** — radar system characterisation
**Resilience:** frequency hopping, spread spectrum,
radiation hardening, satellite crosslinks.
""")