- {bgmUrl &&
}
-
- {/* Absolute Background Visualizer */}
-
-
- {/* Top Left: Logo / Mission Info Overlay */}
-
-
-
-
-
-
-
- {t.appTitle}
-
-
- {t.appSub}
-
-
-
-
-
- {/* Top Right: Preset Selector & Lang Toggle */}
-
-
- {
- const p = PRESETS.find((x) => x.id === e.target.value);
- if (p) changePreset(p);
- }}
- className="bg-transparent text-white/90 text-xs font-bold uppercase tracking-widest outline-none px-3 py-1 cursor-pointer appearance-none"
- >
- {PRESETS.map((p) => (
-
- {p.name}
-
- ))}
-
-
-
{
- setAudioOn((v) => !v);
- }}
- className="p-2 bg-black/30 backdrop-blur-md border border-white/10 rounded-lg text-white/70 hover:bg-white/10 hover:text-white transition-all shadow-lg flex items-center justify-center"
- title="Toggle Global Audio (Mutes/Unmutes BGM)"
- >
- {audioOn ? (
-
- ) : (
-
- )}
-
-
setLang((l) => (l === "en" ? "zh" : "en"))}
- className="p-2 bg-black/30 backdrop-blur-md border border-white/10 rounded-lg text-white/70 hover:bg-white/10 hover:text-white transition-all shadow-lg flex items-center justify-center"
- title="Toggle Language"
- >
-
-
-
-
- {/* Control Panel (Floating Glass Panel Left) */}
-
-
-
-
- setCameraMode((m) => (m === "dynamic" ? "follow" : "dynamic"))
- }
- className="px-2 py-1 bg-white/5 border border-white/10 rounded-md text-white/60 hover:bg-white/10 hover:text-white transition-all flex items-center justify-center gap-1.5"
- title="Toggle Camera Mode"
- >
-
-
- {cameraMode === "dynamic" ? t.cameraDynamic : t.cameraFollow}
-
-
-
-
- {/* Rocket Info */}
-
-
- {t.vehicleParams}
-
-
-
{t.dryMass}
-
- {formatNumber(totalDryMass / 1000, 1)} t
-
-
{t.fuelMass}
-
- {formatNumber(totalFuelMass / 1000, 1)} t
-
-
{t.maxThrust}
-
- {formatNumber(maxPossibleThrust / 1000000, 1)} MN
-
-
{t.isp}
-
- {rocket.stages[0].isp} s
-
-
{t.engines}
-
- {rocket.stages[0].engines}
-
-
-
-
-
-
{
- toggleSimulation();
- }}
- disabled={isFinished}
- className={`flex-1 flex items-center justify-center gap-2 py-3.5 rounded-xl font-bold tracking-widest text-xs transition-all border ${
- isFinished
- ? "bg-red-500/20 text-red-500 border-red-500/30 opacity-70 cursor-not-allowed"
- : state.isRunning
- ? "bg-amber-500/20 text-amber-400 border-amber-500/30 hover:bg-amber-500/30"
- : "bg-emerald-500/20 text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/30"
- }`}
- >
- {isFinished ? (
-
-
- {t.ended}
-
- ) : state.isRunning ? (
- <>
-
- {t.pause}
- >
- ) : (
- <>
-
- {t.launch}
- >
- )}
-
-
-
-
-
-
-
{
- const file = e.target.files?.[0];
- if (file) {
- const url = URL.createObjectURL(file);
- setBgmUrl(url);
- setAudioOn(true); // Automatically turn on global audio so they can hear it
- }
- }}
- />
-
-
-
- {/* Tooltip explaining BGM relation to the global speaker control */}
-
- Load BGM (Controlled by global audio button)
-
-
-
-
-
-
-
- {t.throttle}
-
-
- {Math.round(state.throttle * 100)}%
-
-
-
-
setThrottle(parseFloat(e.target.value))}
- className="w-full h-2 bg-white/10 rounded-full appearance-none cursor-pointer accent-white"
- />
-
-
-
-
-
- {t.twr}
-
- 1 ? "text-emerald-400 drop-shadow-md" : "text-white/80"
- }
- >
- {formatNumber(tWR, 2)}
-
-
-
-
- {t.mach}
-
- 1 ? "text-sky-400 drop-shadow-md" : "text-white/80"
- }
- >
- {formatNumber(mach, 1)}
-
-
-
-
- {t.gforce}
-
- 3 ? "text-rose-400 drop-shadow-md" : "text-white/80"
- }
- >
- {formatNumber(gForce, 1)}
-
-
-
-
- {t.maxT}
-
-
- {formatNumber(
- rocket.stages[state.currentStage].maxThrust / 1000000,
- 1,
- )}
- MN
-
-
-
-
-
- {/* Dashboards (Floating Glass Panel Right) */}
-
-
- {/* Timeline (Top Center) */}
-
- {["liftoff", "maxq", "stageSep1", "karman", "orbit"].map(
- (evName, idx, arr) => {
- const ev = state.events.find((e) => e.name === evName);
- const isReached = !!ev;
- return (
-
-
- {/* Light */}
-
- {/* Label */}
-
- {t[evName as keyof typeof t]}
-
- {/* Time */}
-
- {isReached ? formatTime(ev.time) : "--:--"}
-
-
- {idx < arr.length - 1 && (
-
- )}
-
- );
- },
- )}
-
-
- {/* Bottom Center: Broadcast Telemetry Style HUD */}
-
- {/* Speed */}
-
-
- {/* Center Stage Info */}
-
-
- {formatTime(state.time)}
-
-
-
- {/* Throttle Bar */}
-
-
- {t.hudThrottle}
-
- {Math.round(state.throttle * 100)}%
-
-
-
-
-
- {/* Fuel Bar */}
-
-
-
- {t.hudProp} (S{state.currentStage + 1})
-
-
- {Math.round(
- (state.fuel / rocket.stages[state.currentStage].fuelMass) *
- 100,
- )}
- %
-
-
-
-
-
-
-
- {/* Altitude */}
-
-
- {/* Navball */}
-
-
-
- );
-}
diff --git a/docs/火箭发送模拟/src/components/DashboardChart.tsx b/docs/火箭发送模拟/src/components/DashboardChart.tsx
deleted file mode 100644
index c48c542..0000000
--- a/docs/火箭发送模拟/src/components/DashboardChart.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import {
- LineChart,
- Line,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- ResponsiveContainer,
-} from "recharts";
-import { DataPoint } from "../types";
-
-interface ChartProps {
- data: DataPoint[];
- dataKey: keyof DataPoint;
- color: string;
- unit: string;
- title: string;
-}
-
-export function DashboardChart({
- data,
- dataKey,
- color,
- unit,
- title,
-}: ChartProps) {
- // Simple formatter for Y Axis
- const formatYAxis = (tickItem: number) => {
- if (tickItem > 1000) {
- return `${(tickItem / 1000).toFixed(1)}k`;
- }
- return tickItem.toFixed(0);
- };
-
- return (
-
-
- {title}
-
-
-
-
-
- `${t.toFixed(0)}s`}
- stroke="rgba(255,255,255,0.4)"
- fontSize={10}
- tickMargin={8}
- tickLine={false}
- axisLine={false}
- />
-
- [
- `${value.toFixed(1)} ${unit}`,
- title,
- ]}
- labelFormatter={(label: number) => `T+ ${label.toFixed(1)}s`}
- />
-
-
-
-
-
- );
-}
diff --git a/docs/火箭发送模拟/src/components/FlightVisualizer.tsx b/docs/火箭发送模拟/src/components/FlightVisualizer.tsx
deleted file mode 100644
index a9ac789..0000000
--- a/docs/火箭发送模拟/src/components/FlightVisualizer.tsx
+++ /dev/null
@@ -1,428 +0,0 @@
-import { ReactNode } from "react";
-import { RocketPreset, SimulationState } from "../types";
-
-interface FlightVisualizerProps {
- rocket: RocketPreset;
- state: SimulationState;
- cameraMode: "dynamic" | "follow";
-}
-
-export function FlightVisualizer({
- rocket,
- state,
- cameraMode,
-}: FlightVisualizerProps) {
- // Atmosphere gradient based on altitude: fades to black at 80km
- const getAtmosphereOpacity = () => {
- return Math.max(0, 1 - state.altitude / 80000);
- };
-
- // Map physical coords to SVG viewbox
- const aspect =
- typeof window !== "undefined" ? window.innerWidth / window.innerHeight : 2;
- const minVHeight = cameraMode === "dynamic" ? 2500 : 500;
- const viewHeight =
- cameraMode === "dynamic"
- ? Math.max(minVHeight, state.altitude * 0.8 + 500)
- : minVHeight;
- const viewWidth = viewHeight * aspect;
-
- const physX = state.downrange || 0;
- const physY = state.altitude || 0;
-
- // Center camera on rocket
- const minY =
- cameraMode === "dynamic"
- ? -physY - viewHeight * 0.35
- : -physY - viewHeight * 0.6;
- const minX = physX - viewWidth * 0.5;
-
- const viewBox = `${minX} ${minY} ${viewWidth} ${viewHeight}`;
-
- const trailPoints = (state.history || [])
- .map((p) => `${p.downrange},${-p.altitude}`)
- .join(" ");
-
- // visualScale controls the visual size of the rocket.
- const visualScale =
- cameraMode === "dynamic"
- ? Math.max(1, Math.pow(viewHeight / minVHeight, 0.95)) * 1.5
- : 3.5;
-
- const getStageOffset = (stageIndex: number) => {
- if (state.currentStage <= stageIndex) return 0;
- const sepEventName = `stageSep${stageIndex + 1}`;
- const sepEvent = state.events.find((e) => e.name === sepEventName);
- if (!sepEvent) return 0;
- const dt = state.time - sepEvent.time;
- if (dt < 0) return 0;
- // Quadratic falling back effect
- return (dt * 15 + 0.5 * 10 * Math.pow(Math.min(dt, 20), 2)) * visualScale;
- };
-
- // Custom render for each rocket type
- const renderRocketSvg = (id: string, scale: number) => {
- let activeEngineY = 0;
- let nodes: ReactNode = null;
-
- if (id === "saturnv") {
- const w = 10 * scale;
- const l = 111 * scale;
-
- const s3Bottom = l * 0.3;
- const s2Bottom = l * 0.7;
- const s1Bottom = l;
-
- if (state.currentStage === 0) activeEngineY = s1Bottom;
- else if (state.currentStage === 1) activeEngineY = s2Bottom;
- else activeEngineY = s3Bottom;
-
- const offset1 = getStageOffset(0);
- const offset2 = getStageOffset(1);
-
- nodes = (
- <>
- {/* Stage 3 + Apollo */}
-
- {/* Atmosphere background */}
-
-
- {/* Stars - Deep layer */}
-
- {/* Stars - Mid layer */}
-
- {/* Stars - Close layer */}
-
-
- {/* Screen Effects (Cinematic Vignette) */}
-
-
-
- {/* Ground */}
-
-
-
- {/* Launch Pad Base */}
-
-
-
- {/* Trajectory Trail */}
- {trailPoints.length > 0 && (
-
- )}
-
- {/* The Rocket Container */}
-
- {/* Engine Flame */}
- {state.throttle > 0 && state.fuel > 0 && (
-
- {/* Outer Glow */}
-
- {/* Inner Core */}
-
- {/* Mach Diamonds (visible at higher speeds/altitudes) */}
- {state.throttle > 0.5 &&
- state.altitude > 8000 &&
- state.altitude < 60000 && (
-
-
-
-
-
- )}
-
- )}
-
- {/* Render Detailed Rocket Specific to Model with Active Engine aligned to (0,0) */}
- {nodes}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/docs/火箭发送模拟/src/components/Navball.tsx b/docs/火箭发送模拟/src/components/Navball.tsx
deleted file mode 100644
index b501f06..0000000
--- a/docs/火箭发送模拟/src/components/Navball.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from "react";
-
-interface NavballProps {
- pitch: number; // 90 is up, 0 is horizontal, -90 is down
-}
-
-export function Navball({ pitch }: NavballProps) {
- // For SpaceX broadcast style, we show a rocket silhouette that rotates based on pitch.
- // pitch 90 = pointing straight up = 0 deg rotation
- // pitch 0 = pointing horizontal = 90 deg rotation
- const rotation = 90 - pitch;
-
- return (
-
-
- {/* Horizontal reference line */}
-
- {/* Vertical reference line */}
-
-
- {/* Rocket outline rotating */}
-
- {/* Simple rocket silhouette */}
-
- {/* Rocket body */}
-
- {/* Fins */}
-
-
- {/* Engine bell */}
-
-
-
-
-
- PITCH {Math.round(pitch)}°
-
-
- );
-}
diff --git a/docs/火箭发送模拟/src/components/TelemetryGauge.tsx b/docs/火箭发送模拟/src/components/TelemetryGauge.tsx
deleted file mode 100644
index d5aaa41..0000000
--- a/docs/火箭发送模拟/src/components/TelemetryGauge.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from "react";
-
-interface TelemetryGaugeProps {
- label: string;
- value: number;
- unit: string;
- maxValue: number;
-}
-
-export function TelemetryGauge({
- label,
- value,
- unit,
- maxValue,
-}: TelemetryGaugeProps) {
- const radius = 32;
- const stroke = 3;
- const normalizedRadius = radius - stroke * 2;
- const circumference = normalizedRadius * 2 * Math.PI;
- const fillRatio = Math.min(Math.max(value / maxValue, 0), 1);
- const strokeDashoffset = circumference - fillRatio * circumference;
-
- return (
-