From 5efe0736ac81def1706ccd6eb2862eb8df2484c4 Mon Sep 17 00:00:00 2001 From: scott Date: Tue, 7 Apr 2026 22:06:30 -0700 Subject: [PATCH] Initial project setup: Rust/WASM solar system simulator with SvelteKit frontend - Rust workspace with 4 crates: orbital-mechanics, mass-driver-core, mass-driver-wasm, mass-driver-backend - Keplerian orbital mechanics engine with JPL elements for 14 bodies (Sun, 8 planets, Pluto, Ceres, Europa, Titan, Ganymede) - Kepler equation solver and orbital position computation compiled to WASM - SvelteKit frontend with Tailwind CSS, Canvas2D renderer showing animated solar system - Orbit ellipses, planet dots with labels, Sun glow, grid, scale bar, pan/zoom controls - Time controls (play/pause, 5 speed levels, date picker) driving live simulation - 2D/3D view toggle (3D placeholder for Threlte integration) Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 26 + Cargo.toml | 13 + crates/mass-driver-backend/Cargo.toml | 16 + crates/mass-driver-backend/src/main.rs | 3 + crates/mass-driver-core/Cargo.toml | 13 + crates/mass-driver-core/src/lib.rs | 2 + crates/mass-driver-core/src/route.rs | 24 + crates/mass-driver-core/src/station.rs | 44 + .../.vite/deps/_metadata.json | 8 + .../mass-driver-wasm/.vite/deps/package.json | 3 + crates/mass-driver-wasm/Cargo.toml | 20 + crates/mass-driver-wasm/src/api.rs | 59 + crates/mass-driver-wasm/src/lib.rs | 3 + crates/orbital-mechanics/Cargo.toml | 8 + crates/orbital-mechanics/src/bodies.rs | 413 +++ crates/orbital-mechanics/src/constants.rs | 26 + crates/orbital-mechanics/src/kepler.rs | 56 + crates/orbital-mechanics/src/lib.rs | 6 + crates/orbital-mechanics/src/orbits.rs | 192 ++ frontend/.npmrc | 1 + frontend/package-lock.json | 2826 +++++++++++++++++ frontend/package.json | 37 + frontend/src/app.css | 22 + frontend/src/app.d.ts | 13 + frontend/src/app.html | 12 + frontend/src/lib/assets/favicon.svg | 1 + frontend/src/lib/index.ts | 1 + .../src/lib/render/canvas2d/SolarSystem2D.ts | 284 ++ frontend/src/lib/stores/simulation.svelte.ts | 84 + frontend/src/lib/utils/colors.ts | 7 + frontend/src/lib/utils/format.ts | 17 + frontend/src/lib/wasm/bridge.ts | 35 + frontend/src/lib/wasm/loader.ts | 25 + frontend/src/lib/wasm/types.ts | 5 + .../components/Canvas2DView.svelte | 66 + .../components/TimeControls.svelte | 72 + .../(simulator)/components/ViewToggle.svelte | 26 + .../(simulator)/components/Viewport.svelte | 16 + frontend/src/routes/+layout.svelte | 13 + frontend/src/routes/+page.svelte | 64 + frontend/src/routes/+page.ts | 1 + frontend/static/robots.txt | 3 + frontend/svelte.config.js | 17 + frontend/tsconfig.json | 20 + frontend/vite.config.ts | 23 + 45 files changed, 4626 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 crates/mass-driver-backend/Cargo.toml create mode 100644 crates/mass-driver-backend/src/main.rs create mode 100644 crates/mass-driver-core/Cargo.toml create mode 100644 crates/mass-driver-core/src/lib.rs create mode 100644 crates/mass-driver-core/src/route.rs create mode 100644 crates/mass-driver-core/src/station.rs create mode 100644 crates/mass-driver-wasm/.vite/deps/_metadata.json create mode 100644 crates/mass-driver-wasm/.vite/deps/package.json create mode 100644 crates/mass-driver-wasm/Cargo.toml create mode 100644 crates/mass-driver-wasm/src/api.rs create mode 100644 crates/mass-driver-wasm/src/lib.rs create mode 100644 crates/orbital-mechanics/Cargo.toml create mode 100644 crates/orbital-mechanics/src/bodies.rs create mode 100644 crates/orbital-mechanics/src/constants.rs create mode 100644 crates/orbital-mechanics/src/kepler.rs create mode 100644 crates/orbital-mechanics/src/lib.rs create mode 100644 crates/orbital-mechanics/src/orbits.rs create mode 100644 frontend/.npmrc create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/src/app.css create mode 100644 frontend/src/app.d.ts create mode 100644 frontend/src/app.html create mode 100644 frontend/src/lib/assets/favicon.svg create mode 100644 frontend/src/lib/index.ts create mode 100644 frontend/src/lib/render/canvas2d/SolarSystem2D.ts create mode 100644 frontend/src/lib/stores/simulation.svelte.ts create mode 100644 frontend/src/lib/utils/colors.ts create mode 100644 frontend/src/lib/utils/format.ts create mode 100644 frontend/src/lib/wasm/bridge.ts create mode 100644 frontend/src/lib/wasm/loader.ts create mode 100644 frontend/src/lib/wasm/types.ts create mode 100644 frontend/src/routes/(simulator)/components/Canvas2DView.svelte create mode 100644 frontend/src/routes/(simulator)/components/TimeControls.svelte create mode 100644 frontend/src/routes/(simulator)/components/ViewToggle.svelte create mode 100644 frontend/src/routes/(simulator)/components/Viewport.svelte create mode 100644 frontend/src/routes/+layout.svelte create mode 100644 frontend/src/routes/+page.svelte create mode 100644 frontend/src/routes/+page.ts create mode 100644 frontend/static/robots.txt create mode 100644 frontend/svelte.config.js create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a7521ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Rust +target/ +Cargo.lock + +# Node +node_modules/ +frontend/node_modules/ +frontend/.svelte-kit/ +frontend/build/ + +# WASM build output +crates/mass-driver-wasm/pkg/ + +# Environment +.env +.env.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6bf7f5a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +resolver = "2" +members = [ + "crates/orbital-mechanics", + "crates/mass-driver-core", + "crates/mass-driver-wasm", + "crates/mass-driver-backend", +] + +[workspace.dependencies] +nalgebra = "0.33" +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/crates/mass-driver-backend/Cargo.toml b/crates/mass-driver-backend/Cargo.toml new file mode 100644 index 0000000..931336a --- /dev/null +++ b/crates/mass-driver-backend/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mass-driver-backend" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbital-mechanics = { path = "../orbital-mechanics" } +mass-driver-core = { path = "../mass-driver-core" } +axum = "0.8" +tokio = { version = "1", features = ["full"] } +sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] } +serde = { workspace = true } +serde_json = { workspace = true } +tower-http = { version = "0.6", features = ["cors", "compression-gzip"] } +tracing = "0.1" +tracing-subscriber = "0.3" diff --git a/crates/mass-driver-backend/src/main.rs b/crates/mass-driver-backend/src/main.rs new file mode 100644 index 0000000..aa3cc8e --- /dev/null +++ b/crates/mass-driver-backend/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("mass-driver backend — not yet implemented"); +} diff --git a/crates/mass-driver-core/Cargo.toml b/crates/mass-driver-core/Cargo.toml new file mode 100644 index 0000000..a099c45 --- /dev/null +++ b/crates/mass-driver-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mass-driver-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbital-mechanics = { path = "../orbital-mechanics" } +nalgebra = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +petgraph = "0.8" +ordered-float = "5" +rmp-serde = "1" diff --git a/crates/mass-driver-core/src/lib.rs b/crates/mass-driver-core/src/lib.rs new file mode 100644 index 0000000..a96457e --- /dev/null +++ b/crates/mass-driver-core/src/lib.rs @@ -0,0 +1,2 @@ +pub mod station; +pub mod route; diff --git a/crates/mass-driver-core/src/route.rs b/crates/mass-driver-core/src/route.rs new file mode 100644 index 0000000..80273bd --- /dev/null +++ b/crates/mass-driver-core/src/route.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +/// A single leg of a route between two stations. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RouteLeg { + pub from_station: usize, + pub to_station: usize, + /// Departure week index (0 = first week of simulation) + pub departure_week: u32, + /// Arrival week index + pub arrival_week: u32, + /// Weeks spent waiting at departure station before this leg + pub wait_weeks: u32, +} + +/// The result of a route computation. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RouteResult { + pub legs: Vec, + /// Total elapsed time in weeks from departure to final arrival + pub total_time_weeks: u32, + /// The week index when the package departs the origin + pub departure_week: u32, +} diff --git a/crates/mass-driver-core/src/station.rs b/crates/mass-driver-core/src/station.rs new file mode 100644 index 0000000..37e6f65 --- /dev/null +++ b/crates/mass-driver-core/src/station.rs @@ -0,0 +1,44 @@ +use serde::{Deserialize, Serialize}; + +/// How a station is placed in the solar system. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum StationPlacement { + /// Station in heliocentric orbit + SolarOrbit { + a: f64, // Semi-major axis (AU) + e: f64, // Eccentricity + i: f64, // Inclination (degrees) + omega: f64, // Longitude of ascending node (degrees) + w: f64, // Argument of perihelion (degrees) + m0: f64, // Mean anomaly at epoch (degrees) + }, + /// Station in orbit around a planet + PlanetaryOrbit { + parent_body_id: usize, + altitude_km: f64, + inclination: f64, + }, + /// Station at a Lagrange point + LagrangePoint { + primary_body_id: usize, // e.g., Sun + secondary_body_id: usize, // e.g., Earth + point: LagrangePointId, + }, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum LagrangePointId { + L1, + L2, + L3, + L4, + L5, +} + +/// A mass driver relay station. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Station { + pub id: usize, + pub name: String, + pub placement: StationPlacement, +} diff --git a/crates/mass-driver-wasm/.vite/deps/_metadata.json b/crates/mass-driver-wasm/.vite/deps/_metadata.json new file mode 100644 index 0000000..171fb32 --- /dev/null +++ b/crates/mass-driver-wasm/.vite/deps/_metadata.json @@ -0,0 +1,8 @@ +{ + "hash": "bef44088", + "configHash": "3d8fe0fc", + "lockfileHash": "e3b0c442", + "browserHash": "7d9faa18", + "optimized": {}, + "chunks": {} +} \ No newline at end of file diff --git a/crates/mass-driver-wasm/.vite/deps/package.json b/crates/mass-driver-wasm/.vite/deps/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/crates/mass-driver-wasm/.vite/deps/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/crates/mass-driver-wasm/Cargo.toml b/crates/mass-driver-wasm/Cargo.toml new file mode 100644 index 0000000..7e92f51 --- /dev/null +++ b/crates/mass-driver-wasm/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mass-driver-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +orbital-mechanics = { path = "../orbital-mechanics" } +mass-driver-core = { path = "../mass-driver-core" } +wasm-bindgen = "0.2" +serde-wasm-bindgen = "0.6" +serde = { workspace = true } +serde_json = { workspace = true } +js-sys = "0.3" +web-sys = { version = "0.3", features = ["console"] } + +[dev-dependencies] +wasm-bindgen-test = "0.3" diff --git a/crates/mass-driver-wasm/src/api.rs b/crates/mass-driver-wasm/src/api.rs new file mode 100644 index 0000000..e7a7b18 --- /dev/null +++ b/crates/mass-driver-wasm/src/api.rs @@ -0,0 +1,59 @@ +use orbital_mechanics::bodies; +use orbital_mechanics::orbits; +use wasm_bindgen::prelude::*; + +/// Initialize the WASM module. Called once on page load. +#[wasm_bindgen] +pub fn init() -> Result<(), JsValue> { + web_sys::console::log_1(&"mass-driver WASM initialized".into()); + Ok(()) +} + +/// Get the number of celestial bodies in the simulation. +#[wasm_bindgen] +pub fn get_body_count() -> usize { + bodies::all_bodies().len() +} + +/// Get positions of all celestial bodies at a given Julian Date. +/// +/// Returns a Float64Array of [x0, y0, z0, x1, y1, z1, ...] in AU. +#[wasm_bindgen] +pub fn get_body_positions_at_epoch(jd: f64) -> Vec { + let all = bodies::all_bodies(); + orbits::all_positions_at_epoch(&all, jd) +} + +/// Get body names as a JSON array of strings. +#[wasm_bindgen] +pub fn get_body_names() -> String { + let all = bodies::all_bodies(); + let names: Vec<&str> = all.iter().map(|b| b.name).collect(); + serde_json::to_string(&names).unwrap() +} + +/// Get body colors as a flat array [r0, g0, b0, r1, g1, b1, ...]. +#[wasm_bindgen] +pub fn get_body_colors() -> Vec { + let all = bodies::all_bodies(); + all.iter().flat_map(|b| b.color.iter().copied()).collect() +} + +/// Get body radii in km as a Float64Array. +#[wasm_bindgen] +pub fn get_body_radii() -> Vec { + let all = bodies::all_bodies(); + all.iter().map(|b| b.radius_km).collect() +} + +/// Get orbit points for a given body, sampled around its full orbit. +/// Returns a Float64Array of [x0, y0, z0, x1, y1, z1, ...] in AU. +/// `samples` is the number of points around the orbit. +#[wasm_bindgen] +pub fn get_orbit_points(body_id: usize, jd: f64, samples: usize) -> Vec { + let all = bodies::all_bodies(); + if body_id >= all.len() || body_id == bodies::id::SUN { + return vec![]; + } + orbits::orbit_points(&all, body_id, jd, samples) +} diff --git a/crates/mass-driver-wasm/src/lib.rs b/crates/mass-driver-wasm/src/lib.rs new file mode 100644 index 0000000..299bbf7 --- /dev/null +++ b/crates/mass-driver-wasm/src/lib.rs @@ -0,0 +1,3 @@ +mod api; + +pub use api::*; diff --git a/crates/orbital-mechanics/Cargo.toml b/crates/orbital-mechanics/Cargo.toml new file mode 100644 index 0000000..46ff28c --- /dev/null +++ b/crates/orbital-mechanics/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "orbital-mechanics" +version = "0.1.0" +edition = "2021" + +[dependencies] +nalgebra = { workspace = true } +serde = { workspace = true } diff --git a/crates/orbital-mechanics/src/bodies.rs b/crates/orbital-mechanics/src/bodies.rs new file mode 100644 index 0000000..6f24a1a --- /dev/null +++ b/crates/orbital-mechanics/src/bodies.rs @@ -0,0 +1,413 @@ +use serde::{Deserialize, Serialize}; + +/// Keplerian orbital elements at J2000.0 epoch. +/// +/// For planets: heliocentric, ecliptic coordinates. +/// Values from JPL "Approximate Positions of the Planets" +/// (https://ssd.jpl.nasa.gov/planets/approx_pos.html) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KeplerianElements { + /// Semi-major axis (AU) + pub a: f64, + /// Eccentricity + pub e: f64, + /// Inclination (degrees) + pub i: f64, + /// Mean longitude (degrees) + pub l: f64, + /// Longitude of perihelion (degrees) + pub w_bar: f64, + /// Longitude of ascending node (degrees) + pub omega: f64, +} + +/// Rates of change of Keplerian elements per Julian century. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KeplerianRates { + pub a: f64, + pub e: f64, + pub i: f64, + pub l: f64, + pub w_bar: f64, + pub omega: f64, +} + +/// A celestial body with orbital elements and physical properties. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CelestialBody { + pub name: &'static str, + pub id: usize, + /// Gravitational parameter (km³/s²) + pub mu: f64, + /// Mean radius (km) + pub radius_km: f64, + /// Keplerian elements at J2000.0 + pub elements: KeplerianElements, + /// Rates of change per century + pub rates: KeplerianRates, + /// Display color as [r, g, b] + pub color: [u8; 3], +} + +/// Enumeration of body IDs for quick lookup. +pub mod id { + pub const SUN: usize = 0; + pub const MERCURY: usize = 1; + pub const VENUS: usize = 2; + pub const EARTH: usize = 3; + pub const MARS: usize = 4; + pub const JUPITER: usize = 5; + pub const SATURN: usize = 6; + pub const URANUS: usize = 7; + pub const NEPTUNE: usize = 8; + pub const PLUTO: usize = 9; + pub const CERES: usize = 10; + pub const EUROPA: usize = 11; + pub const TITAN: usize = 12; + pub const GANYMEDE: usize = 13; +} + +/// Returns all celestial bodies with JPL Keplerian elements. +/// +/// Planet elements from JPL "Approximate Positions of the Planets" +/// valid for 3000 BC to 3000 AD. +pub fn all_bodies() -> Vec { + vec![ + // Sun (placeholder — no orbit, always at origin) + CelestialBody { + name: "Sun", + id: id::SUN, + mu: 1.327_124_400_41e11, + radius_km: 695_700.0, + elements: KeplerianElements { + a: 0.0, e: 0.0, i: 0.0, l: 0.0, w_bar: 0.0, omega: 0.0, + }, + rates: KeplerianRates { + a: 0.0, e: 0.0, i: 0.0, l: 0.0, w_bar: 0.0, omega: 0.0, + }, + color: [255, 204, 0], + }, + // Mercury + CelestialBody { + name: "Mercury", + id: id::MERCURY, + mu: 2.2032e4, + radius_km: 2_439.7, + elements: KeplerianElements { + a: 0.387_099_27, + e: 0.205_301_04, + i: 7.004_986_8, + l: 252.250_32, + w_bar: 77.457_796, + omega: 48.330_893, + }, + rates: KeplerianRates { + a: 0.000_000_37, + e: 0.000_010_02, + i: -0.005_946_30, + l: 149_472.674_11, + w_bar: 0.160_476_24, + omega: -0.125_340_2, + }, + color: [183, 168, 153], + }, + // Venus + CelestialBody { + name: "Venus", + id: id::VENUS, + mu: 3.24859e5, + radius_km: 6_051.8, + elements: KeplerianElements { + a: 0.723_332_01, + e: 0.006_773_23, + i: 3.394_662_0, + l: 181.979_73, + w_bar: 131.602_467, + omega: 76.679_920, + }, + rates: KeplerianRates { + a: 0.000_000_60, + e: -0.000_048_98, + i: -0.007_889_0, + l: 58_517.815_39, + w_bar: 0.002_688_29, + omega: -0.278_008_0, + }, + color: [231, 196, 150], + }, + // Earth + CelestialBody { + name: "Earth", + id: id::EARTH, + mu: 3.986e5, + radius_km: 6_371.0, + elements: KeplerianElements { + a: 1.000_002_61, + e: 0.016_711_23, + i: -0.000_015_31, + l: 100.464_572, + w_bar: 102.937_682, + omega: 0.0, + }, + rates: KeplerianRates { + a: 0.000_005_62, + e: -0.000_043_92, + i: -0.012_946_68, + l: 35_999.372_47, + w_bar: 0.323_281_26, + omega: 0.0, + }, + color: [100, 149, 237], + }, + // Mars + CelestialBody { + name: "Mars", + id: id::MARS, + mu: 4.2828e4, + radius_km: 3_389.5, + elements: KeplerianElements { + a: 1.523_679_40, + e: 0.093_400_62, + i: 1.849_726_0, + l: -4.553_432, + w_bar: -23.943_629_5, + omega: 49.559_539, + }, + rates: KeplerianRates { + a: 0.000_018_47, + e: 0.000_079_48, + i: -0.006_813_52, + l: 19_140.303_00, + w_bar: 0.445_265_40, + omega: -0.295_257_0, + }, + color: [193, 68, 14], + }, + // Jupiter + CelestialBody { + name: "Jupiter", + id: id::JUPITER, + mu: 1.26687e8, + radius_km: 69_911.0, + elements: KeplerianElements { + a: 5.202_603_0, + e: 0.048_498_26, + i: 1.303_270, + l: 34.396_441, + w_bar: 14.728_479, + omega: 100.473_909, + }, + rates: KeplerianRates { + a: 0.000_016_05, + e: 0.000_163_94, + i: -0.001_983_72, + l: 3_034.746_12, + w_bar: 0.216_519_4, + omega: 0.205_831_6, + }, + color: [216, 165, 108], + }, + // Saturn + CelestialBody { + name: "Saturn", + id: id::SATURN, + mu: 3.7931e7, + radius_km: 58_232.0, + elements: KeplerianElements { + a: 9.554_909_6, + e: 0.055_086_22, + i: 2.488_878, + l: 49.954_244, + w_bar: 92.598_78, + omega: 113.662_424, + }, + rates: KeplerianRates { + a: -0.000_025_06, + e: -0.000_050_32, + i: 0.004_530_33, + l: 1_222.113_94, + w_bar: -0.411_897_0, + omega: -0.288_668_0, + }, + color: [210, 180, 140], + }, + // Uranus + CelestialBody { + name: "Uranus", + id: id::URANUS, + mu: 5.7940e6, + radius_km: 25_362.0, + elements: KeplerianElements { + a: 19.218_446_2, + e: 0.047_317_65, + i: 0.773_196, + l: 313.238_105, + w_bar: 170.954_276, + omega: 74.016_925, + }, + rates: KeplerianRates { + a: -0.000_198_58, + e: -0.000_004_07, + i: -0.002_446_1, + l: 428.481_40, + w_bar: 0.408_030_0, + omega: 0.046_863_9, + }, + color: [172, 229, 238], + }, + // Neptune + CelestialBody { + name: "Neptune", + id: id::NEPTUNE, + mu: 6.8351e6, + radius_km: 24_622.0, + elements: KeplerianElements { + a: 30.110_386_9, + e: 0.008_590_48, + i: 1.769_952, + l: -55.120_029, + w_bar: 44.964_762, + omega: 131.784_057, + }, + rates: KeplerianRates { + a: 0.000_006_08, + e: 0.000_006_30, + i: 0.000_062_6, + l: 218.457_64, + w_bar: -0.327_55, + omega: -0.006_066_2, + }, + color: [63, 84, 186], + }, + // Pluto (approximate elements) + CelestialBody { + name: "Pluto", + id: id::PLUTO, + mu: 8.71e2, + radius_km: 1_188.3, + elements: KeplerianElements { + a: 39.481_686_77, + e: 0.248_905_87, + i: 17.140_175, + l: 238.928_06, + w_bar: 224.068_76, + omega: 110.303_47, + }, + rates: KeplerianRates { + a: -0.007_608_12, + e: 0.000_060_65, + i: 0.003_681_9, + l: 145.205_80, + w_bar: -0.045_07, + omega: -0.018_16, + }, + color: [190, 180, 170], + }, + // Ceres (approximate) + CelestialBody { + name: "Ceres", + id: id::CERES, + mu: 63.13, + radius_km: 469.73, + elements: KeplerianElements { + a: 2.769_1, + e: 0.075_8, + i: 10.594, + l: 153.94, + w_bar: 73.597 + 80.329, + omega: 80.329, + }, + rates: KeplerianRates { + a: 0.0, + e: 0.0, + i: 0.0, + l: 78_362.73, // ~4.6 yr period + w_bar: 0.0, + omega: 0.0, + }, + color: [148, 137, 121], + }, + // Europa (orbits Jupiter — elements are Jovicentric) + // For visualization, we compute Jupiter's position and add Europa's offset + CelestialBody { + name: "Europa", + id: id::EUROPA, + mu: 3_202.7, + radius_km: 1_560.8, + elements: KeplerianElements { + a: 0.004_486, // ~671,100 km in AU + e: 0.009_4, + i: 0.47, + l: 171.016, + w_bar: 0.0, + omega: 0.0, + }, + rates: KeplerianRates { + a: 0.0, + e: 0.0, + i: 0.0, + l: 101_375.3, // ~3.55 day period + w_bar: 0.0, + omega: 0.0, + }, + color: [200, 190, 160], + }, + // Titan (orbits Saturn) + CelestialBody { + name: "Titan", + id: id::TITAN, + mu: 8_978.1, + radius_km: 2_574.7, + elements: KeplerianElements { + a: 0.008_168, // ~1,221,870 km in AU + e: 0.028_8, + i: 0.34, + l: 120.0, + w_bar: 0.0, + omega: 0.0, + }, + rates: KeplerianRates { + a: 0.0, + e: 0.0, + i: 0.0, + l: 22_577.0, // ~15.95 day period + w_bar: 0.0, + omega: 0.0, + }, + color: [230, 190, 100], + }, + // Ganymede (orbits Jupiter) + CelestialBody { + name: "Ganymede", + id: id::GANYMEDE, + mu: 9_887.8, + radius_km: 2_634.1, + elements: KeplerianElements { + a: 0.007_155, // ~1,070,400 km in AU + e: 0.001_3, + i: 0.20, + l: 317.54, + w_bar: 0.0, + omega: 0.0, + }, + rates: KeplerianRates { + a: 0.0, + e: 0.0, + i: 0.0, + l: 50_317.6, // ~7.15 day period + w_bar: 0.0, + omega: 0.0, + }, + color: [160, 145, 130], + }, + ] +} + +/// Returns the parent body ID for moons (None for planets/Sun). +pub fn parent_body(body_id: usize) -> Option { + match body_id { + id::EUROPA | id::GANYMEDE => Some(id::JUPITER), + id::TITAN => Some(id::SATURN), + _ => None, + } +} diff --git a/crates/orbital-mechanics/src/constants.rs b/crates/orbital-mechanics/src/constants.rs new file mode 100644 index 0000000..5c5da9c --- /dev/null +++ b/crates/orbital-mechanics/src/constants.rs @@ -0,0 +1,26 @@ +/// Gravitational parameter of the Sun (km³/s²) +pub const MU_SUN: f64 = 1.327_124_400_41e11; + +/// One Astronomical Unit in kilometers +pub const AU_KM: f64 = 1.495_978_707e8; + +/// Seconds per day +pub const SECONDS_PER_DAY: f64 = 86_400.0; + +/// Days per Julian century +pub const DAYS_PER_CENTURY: f64 = 36_525.0; + +/// J2000.0 epoch in Julian Date +pub const J2000_JD: f64 = 2_451_545.0; + +/// Days per week +pub const DAYS_PER_WEEK: f64 = 7.0; + +/// Pi +pub const PI: f64 = std::f64::consts::PI; + +/// Two Pi +pub const TAU: f64 = std::f64::consts::TAU; + +/// Degrees to radians +pub const DEG_TO_RAD: f64 = PI / 180.0; diff --git a/crates/orbital-mechanics/src/kepler.rs b/crates/orbital-mechanics/src/kepler.rs new file mode 100644 index 0000000..d4c85a6 --- /dev/null +++ b/crates/orbital-mechanics/src/kepler.rs @@ -0,0 +1,56 @@ +use crate::constants::TAU; + +/// Solve Kepler's equation M = E - e*sin(E) for eccentric anomaly E. +/// +/// Uses Newton-Raphson iteration. Converges in 3-6 iterations for +/// eccentricities < 0.9. All angles in radians. +pub fn solve_kepler(mean_anomaly: f64, eccentricity: f64) -> f64 { + let m = mean_anomaly % TAU; + let e = eccentricity; + + // Initial guess (Markley's starting value for better convergence) + let mut ea = if e < 0.8 { + m + e * m.sin() + } else { + std::f64::consts::PI + }; + + // Newton-Raphson iteration + for _ in 0..50 { + let delta = (ea - e * ea.sin() - m) / (1.0 - e * ea.cos()); + ea -= delta; + if delta.abs() < 1e-12 { + break; + } + } + + ea +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_circular_orbit() { + // For e=0, E should equal M + let m = 1.0; + let e = solve_kepler(m, 0.0); + assert!((e - m).abs() < 1e-12); + } + + #[test] + fn test_known_values() { + // For M=π, any eccentricity, E should be π + let e = solve_kepler(std::f64::consts::PI, 0.5); + assert!((e - std::f64::consts::PI).abs() < 1e-10); + } + + #[test] + fn test_high_eccentricity() { + let ea = solve_kepler(1.0, 0.9); + // Verify it satisfies Kepler's equation + let m_check = ea - 0.9 * ea.sin(); + assert!((m_check - 1.0).abs() < 1e-10); + } +} diff --git a/crates/orbital-mechanics/src/lib.rs b/crates/orbital-mechanics/src/lib.rs new file mode 100644 index 0000000..4647b08 --- /dev/null +++ b/crates/orbital-mechanics/src/lib.rs @@ -0,0 +1,6 @@ +pub mod bodies; +pub mod constants; +pub mod kepler; +pub mod orbits; + +pub use nalgebra::Vector3; diff --git a/crates/orbital-mechanics/src/orbits.rs b/crates/orbital-mechanics/src/orbits.rs new file mode 100644 index 0000000..2b23b73 --- /dev/null +++ b/crates/orbital-mechanics/src/orbits.rs @@ -0,0 +1,192 @@ +use crate::bodies::{self, CelestialBody}; +use crate::constants::*; +use crate::kepler::solve_kepler; +use nalgebra::Vector3; + +/// Compute the heliocentric ecliptic position of a body at a given Julian Date. +/// +/// Returns position in AU in the ecliptic coordinate frame: +/// - X toward vernal equinox +/// - Y in ecliptic plane, 90° from X +/// - Z toward ecliptic north pole +/// +/// For moons, returns the heliocentric position (parent position + moon offset). +pub fn position_at_epoch(bodies: &[CelestialBody], body_id: usize, jd: f64) -> Vector3 { + let body = &bodies[body_id]; + + // Sun is at origin + if body_id == bodies::id::SUN { + return Vector3::zeros(); + } + + // Centuries since J2000.0 + let t = (jd - J2000_JD) / DAYS_PER_CENTURY; + + // Compute current elements + let a = body.elements.a + body.rates.a * t; + let e = body.elements.e + body.rates.e * t; + let i = (body.elements.i + body.rates.i * t) * DEG_TO_RAD; + let l = (body.elements.l + body.rates.l * t) * DEG_TO_RAD; + let w_bar = (body.elements.w_bar + body.rates.w_bar * t) * DEG_TO_RAD; + let omega = (body.elements.omega + body.rates.omega * t) * DEG_TO_RAD; + + // Argument of perihelion + let w = w_bar - omega; + + // Mean anomaly + let m = l - w_bar; + + // Solve Kepler's equation for eccentric anomaly + let ea = solve_kepler(m, e); + + // True anomaly + let nu = 2.0 * ((1.0 + e).sqrt() * (ea / 2.0).sin()) + .atan2((1.0 - e).sqrt() * (ea / 2.0).cos()); + + // Distance from focus + let r = a * (1.0 - e * ea.cos()); + + // Position in orbital plane + let x_orb = r * nu.cos(); + let y_orb = r * nu.sin(); + + // Rotate to ecliptic coordinates + let cos_w = w.cos(); + let sin_w = w.sin(); + let cos_o = omega.cos(); + let sin_o = omega.sin(); + let cos_i = i.cos(); + let sin_i = i.sin(); + + let x_ecl = (cos_w * cos_o - sin_w * sin_o * cos_i) * x_orb + + (-sin_w * cos_o - cos_w * sin_o * cos_i) * y_orb; + let y_ecl = (cos_w * sin_o + sin_w * cos_o * cos_i) * x_orb + + (-sin_w * sin_o + cos_w * cos_o * cos_i) * y_orb; + let z_ecl = (sin_w * sin_i) * x_orb + (cos_w * sin_i) * y_orb; + + let pos = Vector3::new(x_ecl, y_ecl, z_ecl); + + // For moons, add parent body position + if let Some(parent_id) = bodies::parent_body(body_id) { + let parent_pos = position_at_epoch(bodies, parent_id, jd); + parent_pos + pos + } else { + pos + } +} + +/// Compute positions of all bodies at a given epoch. +/// Returns a flat Vec of [x, y, z, x, y, z, ...] in AU. +pub fn all_positions_at_epoch(bodies: &[CelestialBody], jd: f64) -> Vec { + let mut result = Vec::with_capacity(bodies.len() * 3); + for i in 0..bodies.len() { + let pos = position_at_epoch(bodies, i, jd); + result.push(pos.x); + result.push(pos.y); + result.push(pos.z); + } + result +} + +/// Sample points around a body's orbit for drawing orbit ellipses. +/// For planets, samples one full heliocentric orbit. +/// For moons, samples one full orbit around the parent and adds parent position. +/// Returns flat [x, y, z, x, y, z, ...] in AU. +pub fn orbit_points( + bodies: &[CelestialBody], + body_id: usize, + jd: f64, + samples: usize, +) -> Vec { + let body = &bodies[body_id]; + let t = (jd - J2000_JD) / DAYS_PER_CENTURY; + + let a = body.elements.a + body.rates.a * t; + let e = body.elements.e + body.rates.e * t; + let i = (body.elements.i + body.rates.i * t) * DEG_TO_RAD; + let w_bar = (body.elements.w_bar + body.rates.w_bar * t) * DEG_TO_RAD; + let omega = (body.elements.omega + body.rates.omega * t) * DEG_TO_RAD; + let w = w_bar - omega; + + let cos_w = w.cos(); + let sin_w = w.sin(); + let cos_o = omega.cos(); + let sin_o = omega.sin(); + let cos_i = i.cos(); + let sin_i = i.sin(); + + // Parent offset for moons + let parent_pos = if let Some(parent_id) = bodies::parent_body(body_id) { + position_at_epoch(bodies, parent_id, jd) + } else { + Vector3::zeros() + }; + + let mut result = Vec::with_capacity(samples * 3); + for s in 0..samples { + let nu = TAU * (s as f64) / (samples as f64); + let r = a * (1.0 - e * e) / (1.0 + e * nu.cos()); + + let x_orb = r * nu.cos(); + let y_orb = r * nu.sin(); + + let x = (cos_w * cos_o - sin_w * sin_o * cos_i) * x_orb + + (-sin_w * cos_o - cos_w * sin_o * cos_i) * y_orb + + parent_pos.x; + let y = (cos_w * sin_o + sin_w * cos_o * cos_i) * x_orb + + (-sin_w * sin_o + cos_w * cos_o * cos_i) * y_orb + + parent_pos.y; + let z = (sin_w * sin_i) * x_orb + (cos_w * sin_i) * y_orb + parent_pos.z; + + result.push(x); + result.push(y); + result.push(z); + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bodies::all_bodies; + + #[test] + fn test_sun_at_origin() { + let bodies = all_bodies(); + let pos = position_at_epoch(&bodies, bodies::id::SUN, J2000_JD); + assert!(pos.norm() < 1e-15); + } + + #[test] + fn test_earth_distance_from_sun() { + let bodies = all_bodies(); + let pos = position_at_epoch(&bodies, bodies::id::EARTH, J2000_JD); + let distance_au = pos.norm(); + // Earth should be ~1 AU from the Sun + assert!( + (distance_au - 1.0).abs() < 0.02, + "Earth distance: {} AU", + distance_au + ); + } + + #[test] + fn test_jupiter_distance() { + let bodies = all_bodies(); + let pos = position_at_epoch(&bodies, bodies::id::JUPITER, J2000_JD); + let distance_au = pos.norm(); + // Jupiter should be ~5.2 AU + assert!( + (distance_au - 5.2).abs() < 0.3, + "Jupiter distance: {} AU", + distance_au + ); + } + + #[test] + fn test_all_positions_length() { + let bodies = all_bodies(); + let positions = all_positions_at_epoch(&bodies, J2000_JD); + assert_eq!(positions.len(), bodies.len() * 3); + } +} diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..5ebe4d6 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2826 @@ +{ + "name": "frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.1", + "dependencies": { + "@threlte/core": "^8.5.8", + "@threlte/extras": "^9.14.5", + "@threlte/flex": "^2.2.2", + "astronomy-engine": "^2.1.19", + "mass-driver-wasm": "file:../crates/mass-driver-wasm/pkg", + "postprocessing": "^6.39.0", + "three": "^0.183.2", + "vite-plugin-top-level-await": "^1.6.0", + "vite-plugin-wasm": "^3.6.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.50.2", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tailwindcss/vite": "^4.2.2", + "@types/three": "^0.183.1", + "svelte": "^5.54.0", + "svelte-check": "^4.4.2", + "tailwindcss": "^4.2.2", + "typescript": "^5.9.3", + "vite": "^7.3.1" + } + }, + "../crates/mass-driver-wasm/pkg": { + "name": "mass-driver-wasm", + "version": "0.1.0" + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz", + "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.57.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.57.0.tgz", + "integrity": "sha512-TMiqCTy9ZW4KBHvmTgeWU/hF6jcFpeMgR+9ekE06uhhGnbUZ7wpIY6l1Uk4ThRzlWYJnCVfzmtVNaHaDjaSiSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.6.4", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "obug": "^2.1.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@swc/core": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.24.tgz", + "integrity": "sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.26" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.24", + "@swc/core-darwin-x64": "1.15.24", + "@swc/core-linux-arm-gnueabihf": "1.15.24", + "@swc/core-linux-arm64-gnu": "1.15.24", + "@swc/core-linux-arm64-musl": "1.15.24", + "@swc/core-linux-ppc64-gnu": "1.15.24", + "@swc/core-linux-s390x-gnu": "1.15.24", + "@swc/core-linux-x64-gnu": "1.15.24", + "@swc/core-linux-x64-musl": "1.15.24", + "@swc/core-win32-arm64-msvc": "1.15.24", + "@swc/core-win32-ia32-msvc": "1.15.24", + "@swc/core-win32-x64-msvc": "1.15.24" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz", + "integrity": "sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz", + "integrity": "sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz", + "integrity": "sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz", + "integrity": "sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz", + "integrity": "sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz", + "integrity": "sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz", + "integrity": "sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz", + "integrity": "sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz", + "integrity": "sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz", + "integrity": "sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz", + "integrity": "sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz", + "integrity": "sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@swc/wasm": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.24.tgz", + "integrity": "sha512-vFjzOE8dhJcfeTbM4+HO9Qy58IINV0ysqStAgw81uds+KqCeUDM9huN+SZ5lWZ6U+5nf8VcZoEw5N81xMtAidg==", + "license": "Apache-2.0" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@threejs-kit/instanced-sprite-mesh": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@threejs-kit/instanced-sprite-mesh/-/instanced-sprite-mesh-2.5.1.tgz", + "integrity": "sha512-pmt1ALRhbHhCJQTj2FuthH6PeLIeaM4hOuS2JO3kWSwlnvx/9xuUkjFR3JOi/myMqsH7pSsLIROSaBxDfttjeA==", + "dependencies": { + "diet-sprite": "^0.0.1", + "earcut": "^2.2.4", + "maath": "^0.10.7", + "three-instanced-uniforms-mesh": "^0.52.4", + "troika-three-utils": "^0.52.4" + }, + "peerDependencies": { + "three": ">=0.170.0" + } + }, + "node_modules/@threlte/core": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@threlte/core/-/core-8.5.8.tgz", + "integrity": "sha512-wtPbmzEHRbSusLC1cuxGrKvs1hhlRos/ytJjda0sk6iXNQoRGNh7j5v4e7qmMM7O0D9LZML/xOWcRVHVJLU9Cg==", + "license": "MIT", + "peerDependencies": { + "svelte": ">=5", + "three": ">=0.160" + } + }, + "node_modules/@threlte/extras": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@threlte/extras/-/extras-9.14.5.tgz", + "integrity": "sha512-87AGxxcjMKEVqVbi0T8e3Q+CwMUzP1hMfC6z5lGy6v3aaMzUewHZNbSBQ3+MeNmuUjDz1wRajVX/tw6kwrVshw==", + "license": "MIT", + "dependencies": { + "@threejs-kit/instanced-sprite-mesh": "^2.5.1", + "camera-controls": "^3.1.2", + "three-mesh-bvh": "^0.9.1", + "three-perf": "^1.0.11", + "three-viewport-gizmo": "^2.2.0", + "troika-three-text": "^0.52.4" + }, + "peerDependencies": { + "svelte": ">=5", + "three": ">=0.160" + } + }, + "node_modules/@threlte/flex": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@threlte/flex/-/flex-2.2.2.tgz", + "integrity": "sha512-3juob3BZSiaq1Ufds6LDMQCozGuZdjGR6uFSnVFu6fnB42SkYwcqOl7xBcqRs831NdkcTKPRoamnGmo9hBJ50w==", + "license": "MIT", + "dependencies": { + "mitt": "^3.0.1", + "yoga-layout": "^3.2.1" + }, + "peerDependencies": { + "svelte": ">=5", + "three": ">=0.160" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.183.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz", + "integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~1.0.1" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", + "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/astronomy-engine": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/astronomy-engine/-/astronomy-engine-2.1.19.tgz", + "integrity": "sha512-8yWKNf7UeNbH458h3sAJ6ZgAjE5jTXp/mNNRFoC20j2SHwZIjAQeEsBB2Q3uCFRaTCCJRv33K2XhkhZQMXoX6w==", + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/camera-controls": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz", + "integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==", + "license": "MIT", + "engines": { + "node": ">=22.0.0", + "npm": ">=10.5.1" + }, + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.7.0.tgz", + "integrity": "sha512-qCvc8m7cImp1QDCsiY+C2EdSBWSj7Ucfoq87scSdYboDiIKdvMtFbH1U2VReBls6WMhMaUOoK3ZJEDNG/7zm3w==", + "license": "MIT" + }, + "node_modules/diet-sprite": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/diet-sprite/-/diet-sprite-0.0.1.tgz", + "integrity": "sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A==", + "license": "ISC" + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz", + "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@typescript-eslint/types": "^8.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/maath": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", + "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.134.0", + "three": ">=0.134.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mass-driver-wasm": { + "resolved": "../crates/mass-driver-wasm/pkg", + "link": true + }, + "node_modules/meshoptimizer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz", + "integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==", + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postprocessing": { + "version": "6.39.0", + "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.0.tgz", + "integrity": "sha512-/G6JY8hs426lcto/pBZlnFSkyEo1fHsh4gy7FPJtq1SaSUOzJgDW6f6f1K/+aMOYzK/eQEefyOb3++jPPIUeDA==", + "license": "Zlib", + "peerDependencies": { + "three": ">= 0.168.0 < 0.184.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.55.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.2.tgz", + "integrity": "sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.6.4", + "esm-env": "^1.2.1", + "esrap": "^2.2.4", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.6.tgz", + "integrity": "sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/three": { + "version": "0.183.2", + "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz", + "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", + "license": "MIT" + }, + "node_modules/three-instanced-uniforms-mesh": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/three-instanced-uniforms-mesh/-/three-instanced-uniforms-mesh-0.52.4.tgz", + "integrity": "sha512-YwDBy05hfKZQtU+Rp0KyDf9yH4GxfhxMbVt9OYruxdgLfPwmDG5oAbGoW0DrKtKZSM3BfFcCiejiOHCjFBTeng==", + "license": "MIT", + "dependencies": { + "troika-three-utils": "^0.52.4" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/three-mesh-bvh": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.9.9.tgz", + "integrity": "sha512-FJKitcjvbALmeQRK+Sc+nLGorCpkrZBrbgJZFzhdyWboak37DZikn46hvQkNqSbJPm227ahYmS6k3N/GXaAyXw==", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/three-perf": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/three-perf/-/three-perf-1.0.11.tgz", + "integrity": "sha512-OgBpZjwL+csQKGKZjpkH/QHdbGFMxqngMbSEJeSnVNfXDYd6On7WHNv/GhUZH4YxIpNMwMahBWrNnsJvnbSJHQ==", + "license": "MIT", + "dependencies": { + "troika-three-text": "^0.52.0", + "tweakpane": "^3.1.10" + }, + "peerDependencies": { + "three": ">=0.170" + } + }, + "node_modules/three-viewport-gizmo": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/three-viewport-gizmo/-/three-viewport-gizmo-2.2.0.tgz", + "integrity": "sha512-Jo9Liur1rUmdKk75FZumLU/+hbF+RtJHi1qsKZpntjKlCYScK6tjbYoqvJ9M+IJphrlQJF5oReFW7Sambh0N4Q==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.162.0 <1.0.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/troika-three-text": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", + "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.52.4", + "troika-worker-utils": "^0.52.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz", + "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", + "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", + "license": "MIT" + }, + "node_modules/tweakpane": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-3.1.10.tgz", + "integrity": "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/cocopon" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz", + "integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==", + "license": "MIT", + "dependencies": { + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.12.14", + "@swc/wasm": "^1.12.14", + "uuid": "10.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-wasm": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.6.0.tgz", + "integrity": "sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==", + "license": "MIT", + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", + "license": "MIT" + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..e21a6bb --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,37 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.50.2", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tailwindcss/vite": "^4.2.2", + "@types/three": "^0.183.1", + "svelte": "^5.54.0", + "svelte-check": "^4.4.2", + "tailwindcss": "^4.2.2", + "typescript": "^5.9.3", + "vite": "^7.3.1" + }, + "dependencies": { + "mass-driver-wasm": "file:../crates/mass-driver-wasm/pkg", + "@threlte/core": "^8.5.8", + "@threlte/extras": "^9.14.5", + "@threlte/flex": "^2.2.2", + "astronomy-engine": "^2.1.19", + "postprocessing": "^6.39.0", + "three": "^0.183.2", + "vite-plugin-top-level-await": "^1.6.0", + "vite-plugin-wasm": "^3.6.0" + } +} diff --git a/frontend/src/app.css b/frontend/src/app.css new file mode 100644 index 0000000..cea43c5 --- /dev/null +++ b/frontend/src/app.css @@ -0,0 +1,22 @@ +@import 'tailwindcss'; + +:root { + --bg-primary: #0a0a0f; + --bg-secondary: #12121a; + --bg-panel: #1a1a2e; + --text-primary: #e0e0e8; + --text-secondary: #8888a0; + --accent-blue: #4a9eff; + --accent-orange: #ff6a33; + --accent-green: #33ff88; +} + +html, body { + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: 'Inter', system-ui, -apple-system, sans-serif; +} diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/frontend/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/frontend/src/app.html b/frontend/src/app.html new file mode 100644 index 0000000..6a2bb58 --- /dev/null +++ b/frontend/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/frontend/src/lib/assets/favicon.svg b/frontend/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/frontend/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/frontend/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/frontend/src/lib/render/canvas2d/SolarSystem2D.ts b/frontend/src/lib/render/canvas2d/SolarSystem2D.ts new file mode 100644 index 0000000..6aed18b --- /dev/null +++ b/frontend/src/lib/render/canvas2d/SolarSystem2D.ts @@ -0,0 +1,284 @@ +import type { BodyInfo } from '$lib/wasm/types'; +import { rgbToCss } from '$lib/utils/colors'; + +const AU_TO_PIXELS_BASE = 80; // pixels per AU at zoom level 1 + +interface Camera2D { + x: number; // center offset in AU + y: number; + zoom: number; +} + +export class SolarSystem2DRenderer { + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + private camera: Camera2D = { x: 0, y: 0, zoom: 1 }; + private bodyInfos: BodyInfo[] = []; + private positions: Float64Array = new Float64Array(0); + private orbitPoints: Map = new Map(); + private isDragging = false; + private lastMouse = { x: 0, y: 0 }; + private animFrameId: number = 0; + + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.ctx = canvas.getContext('2d')!; + this.setupEventListeners(); + } + + private setupEventListeners() { + this.canvas.addEventListener('wheel', (e) => { + e.preventDefault(); + const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + this.camera.zoom *= zoomFactor; + this.camera.zoom = Math.max(0.05, Math.min(50, this.camera.zoom)); + }); + + this.canvas.addEventListener('mousedown', (e) => { + this.isDragging = true; + this.lastMouse = { x: e.clientX, y: e.clientY }; + }); + + this.canvas.addEventListener('mousemove', (e) => { + if (!this.isDragging) return; + const scale = this.getScale(); + this.camera.x -= (e.clientX - this.lastMouse.x) / scale; + this.camera.y -= (e.clientY - this.lastMouse.y) / scale; + this.lastMouse = { x: e.clientX, y: e.clientY }; + }); + + this.canvas.addEventListener('mouseup', () => { this.isDragging = false; }); + this.canvas.addEventListener('mouseleave', () => { this.isDragging = false; }); + } + + private getScale(): number { + return AU_TO_PIXELS_BASE * this.camera.zoom; + } + + private auToScreen(xAU: number, yAU: number): [number, number] { + const scale = this.getScale(); + const rect = this.canvas.getBoundingClientRect(); + const cx = rect.width / 2; + const cy = rect.height / 2; + return [ + cx + (xAU - this.camera.x) * scale, + cy - (yAU - this.camera.y) * scale, // flip Y for screen coords + ]; + } + + updateBodies(infos: BodyInfo[], positions: Float64Array) { + this.bodyInfos = infos; + this.positions = positions; + } + + updateOrbit(bodyId: number, points: Float64Array) { + this.orbitPoints.set(bodyId, points); + } + + render() { + const { canvas, ctx } = this; + const dpr = window.devicePixelRatio || 1; + const rect = canvas.getBoundingClientRect(); + + canvas.width = rect.width * dpr; + canvas.height = rect.height * dpr; + ctx.scale(dpr, dpr); + + // Clear + ctx.fillStyle = '#0a0a0f'; + ctx.fillRect(0, 0, rect.width, rect.height); + + // Draw grid + this.drawGrid(rect.width, rect.height); + + // Draw orbit ellipses + for (const [bodyId, points] of this.orbitPoints) { + if (bodyId < this.bodyInfos.length) { + this.drawOrbit(points, this.bodyInfos[bodyId]); + } + } + + // Draw bodies on top + if (this.positions.length > 0) { + for (let i = 0; i < this.bodyInfos.length; i++) { + const info = this.bodyInfos[i]; + const x = this.positions[i * 3]; + const y = this.positions[i * 3 + 1]; + + if (i === 0) { + this.drawSun(x, y, info); + } else { + this.drawBody(x, y, info); + } + } + } + + // Scale indicator + this.drawScaleBar(rect.width, rect.height); + } + + private drawGrid(w: number, h: number) { + const ctx = this.ctx; + const scale = this.getScale(); + + // Determine grid spacing in AU + let gridAU = 1; + const pixelsPerGrid = gridAU * scale; + if (pixelsPerGrid < 30) gridAU = 5; + if (pixelsPerGrid > 200) gridAU = 0.5; + if (pixelsPerGrid > 400) gridAU = 0.1; + + ctx.strokeStyle = 'rgba(255, 255, 255, 0.04)'; + ctx.lineWidth = 0.5; + + // Vertical lines + const startXAU = Math.floor((this.camera.x - w / 2 / scale) / gridAU) * gridAU; + const endXAU = Math.ceil((this.camera.x + w / 2 / scale) / gridAU) * gridAU; + for (let xAU = startXAU; xAU <= endXAU; xAU += gridAU) { + const [sx] = this.auToScreen(xAU, 0); + ctx.beginPath(); + ctx.moveTo(sx, 0); + ctx.lineTo(sx, h); + ctx.stroke(); + } + + // Horizontal lines + const startYAU = Math.floor((this.camera.y - h / 2 / scale) / gridAU) * gridAU; + const endYAU = Math.ceil((this.camera.y + h / 2 / scale) / gridAU) * gridAU; + for (let yAU = startYAU; yAU <= endYAU; yAU += gridAU) { + const [, sy] = this.auToScreen(0, yAU); + ctx.beginPath(); + ctx.moveTo(0, sy); + ctx.lineTo(w, sy); + ctx.stroke(); + } + + // Concentric orbit reference circles (centered on Sun) + ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)'; + const [sunX, sunY] = this.auToScreen(0, 0); + for (let r = 1; r <= 40; r++) { + const radiusPx = r * scale; + if (radiusPx < 10 || radiusPx > 5000) continue; + ctx.beginPath(); + ctx.arc(sunX, sunY, radiusPx, 0, Math.PI * 2); + ctx.stroke(); + } + } + + private drawOrbit(points: Float64Array, info: BodyInfo) { + if (points.length < 6) return; + const ctx = this.ctx; + const [r, g, b] = info.color; + + ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, 0.2)`; + ctx.lineWidth = 0.8; + ctx.beginPath(); + + const [sx0, sy0] = this.auToScreen(points[0], points[1]); + ctx.moveTo(sx0, sy0); + + for (let i = 3; i < points.length; i += 3) { + const [sx, sy] = this.auToScreen(points[i], points[i + 1]); + ctx.lineTo(sx, sy); + } + + ctx.closePath(); + ctx.stroke(); + } + + private drawSun(xAU: number, yAU: number, info: BodyInfo) { + const ctx = this.ctx; + const [sx, sy] = this.auToScreen(xAU, yAU); + const [r, g, b] = info.color; + + // Glow + const gradient = ctx.createRadialGradient(sx, sy, 0, sx, sy, 30); + gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.8)`); + gradient.addColorStop(0.3, `rgba(${r}, ${g}, ${b}, 0.3)`); + gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc(sx, sy, 30, 0, Math.PI * 2); + ctx.fill(); + + // Core + ctx.fillStyle = rgbToCss(r, g, b); + ctx.beginPath(); + ctx.arc(sx, sy, 6, 0, Math.PI * 2); + ctx.fill(); + + // Label + ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; + ctx.font = '10px monospace'; + ctx.fillText(info.name, sx + 10, sy + 4); + } + + private drawBody(xAU: number, yAU: number, info: BodyInfo) { + const ctx = this.ctx; + const [sx, sy] = this.auToScreen(xAU, yAU); + const [r, g, b] = info.color; + + // Size based on radius (with minimum for visibility) + let size = Math.max(2, Math.log10(info.radius_km / 1000) * 2); + // Moons are smaller + if (info.radius_km < 3000) size = 1.5; + + // Body dot + ctx.fillStyle = rgbToCss(r, g, b); + ctx.beginPath(); + ctx.arc(sx, sy, size, 0, Math.PI * 2); + ctx.fill(); + + // Subtle glow + ctx.fillStyle = `rgba(${r}, ${g}, ${b}, 0.15)`; + ctx.beginPath(); + ctx.arc(sx, sy, size * 3, 0, Math.PI * 2); + ctx.fill(); + + // Label + ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.font = '9px monospace'; + ctx.fillText(info.name, sx + size + 4, sy + 3); + } + + private drawScaleBar(w: number, h: number) { + const ctx = this.ctx; + const scale = this.getScale(); + + // Find a nice round AU value for the bar + let barAU = 1; + let barPx = barAU * scale; + if (barPx > 200) { barAU = 0.5; barPx = barAU * scale; } + if (barPx > 200) { barAU = 0.1; barPx = barAU * scale; } + if (barPx < 30) { barAU = 5; barPx = barAU * scale; } + if (barPx < 30) { barAU = 10; barPx = barAU * scale; } + + const x = 20; + const y = h - 20; + + ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + barPx, y); + ctx.stroke(); + + // End caps + ctx.beginPath(); + ctx.moveTo(x, y - 4); + ctx.lineTo(x, y + 4); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(x + barPx, y - 4); + ctx.lineTo(x + barPx, y + 4); + ctx.stroke(); + + ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; + ctx.font = '10px monospace'; + ctx.fillText(`${barAU} AU`, x + barPx / 2 - 15, y - 8); + } + + destroy() { + if (this.animFrameId) cancelAnimationFrame(this.animFrameId); + } +} diff --git a/frontend/src/lib/stores/simulation.svelte.ts b/frontend/src/lib/stores/simulation.svelte.ts new file mode 100644 index 0000000..eedf0cd --- /dev/null +++ b/frontend/src/lib/stores/simulation.svelte.ts @@ -0,0 +1,84 @@ +import type { BodyInfo } from '$lib/wasm/types'; + +/// J2000.0 epoch = 2000-01-01 12:00 TT +const J2000_JD = 2451545.0; + +export type ViewMode = '2d' | '3d'; + +class SimulationState { + // Time + currentJD = $state(J2000_JD); + playbackSpeed = $state(1.0); // weeks per second of real time + isPlaying = $state(false); + + // Bodies + bodyInfos = $state([]); + bodyPositions = $state(new Float64Array(0)); + + // View + viewMode = $state('2d'); + + // WASM ready + wasmReady = $state(false); + + // Derived + get currentDateStr(): string { + return jdToCalendarDate(this.currentJD); + } + + get currentWeekIndex(): number { + return Math.floor((this.currentJD - J2000_JD) / 7); + } + + advanceTime(dtSeconds: number) { + if (!this.isPlaying) return; + // playbackSpeed is in weeks/second, 1 week = 7 days + this.currentJD += this.playbackSpeed * 7 * dtSeconds; + } + + setDate(year: number, month: number, day: number) { + this.currentJD = calendarToJD(year, month, day); + } + + togglePlay() { + this.isPlaying = !this.isPlaying; + } +} + +export const simulation = new SimulationState(); + +// Julian Date <-> Calendar conversions +function jdToCalendarDate(jd: number): string { + // Algorithm from Meeus, "Astronomical Algorithms" + const z = Math.floor(jd + 0.5); + const f = jd + 0.5 - z; + let a: number; + if (z < 2299161) { + a = z; + } else { + const alpha = Math.floor((z - 1867216.25) / 36524.25); + a = z + 1 + alpha - Math.floor(alpha / 4); + } + const b = a + 1524; + const c = Math.floor((b - 122.1) / 365.25); + const d = Math.floor(365.25 * c); + const e = Math.floor((b - d) / 30.6001); + + const day = b - d - Math.floor(30.6001 * e) + f; + const month = e < 14 ? e - 1 : e - 13; + const year = month > 2 ? c - 4716 : c - 4715; + + return `${year}-${String(month).padStart(2, '0')}-${String(Math.floor(day)).padStart(2, '0')}`; +} + +function calendarToJD(year: number, month: number, day: number): number { + let y = year; + let m = month; + if (m <= 2) { + y -= 1; + m += 12; + } + const a = Math.floor(y / 100); + const b = 2 - a + Math.floor(a / 4); + return Math.floor(365.25 * (y + 4716)) + Math.floor(30.6001 * (m + 1)) + day + b - 1524.5; +} diff --git a/frontend/src/lib/utils/colors.ts b/frontend/src/lib/utils/colors.ts new file mode 100644 index 0000000..abe7ca2 --- /dev/null +++ b/frontend/src/lib/utils/colors.ts @@ -0,0 +1,7 @@ +export function rgbToHex(r: number, g: number, b: number): string { + return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`; +} + +export function rgbToCss(r: number, g: number, b: number): string { + return `rgb(${r}, ${g}, ${b})`; +} diff --git a/frontend/src/lib/utils/format.ts b/frontend/src/lib/utils/format.ts new file mode 100644 index 0000000..afefb0e --- /dev/null +++ b/frontend/src/lib/utils/format.ts @@ -0,0 +1,17 @@ +export function formatAU(au: number): string { + if (Math.abs(au) < 0.01) { + return `${(au * 149_597_870.7).toFixed(0)} km`; + } + return `${au.toFixed(3)} AU`; +} + +export function formatWeeks(weeks: number): string { + if (weeks < 4) return `${weeks.toFixed(1)} weeks`; + if (weeks < 52) return `${(weeks / 4.345).toFixed(1)} months`; + return `${(weeks / 52.177).toFixed(1)} years`; +} + +export function formatVelocity(kms: number): string { + if (kms < 1) return `${(kms * 1000).toFixed(0)} m/s`; + return `${kms.toFixed(1)} km/s`; +} diff --git a/frontend/src/lib/wasm/bridge.ts b/frontend/src/lib/wasm/bridge.ts new file mode 100644 index 0000000..3066122 --- /dev/null +++ b/frontend/src/lib/wasm/bridge.ts @@ -0,0 +1,35 @@ +import { getWasm } from './loader'; +import type { BodyInfo } from './types'; + +export function getBodyPositions(jd: number): Float64Array { + const wasm = getWasm(); + if (!wasm) return new Float64Array(0); + return wasm.get_body_positions_at_epoch(jd); +} + +export function getBodyCount(): number { + const wasm = getWasm(); + if (!wasm) return 0; + return wasm.get_body_count(); +} + +export function getBodyInfos(): BodyInfo[] { + const wasm = getWasm(); + if (!wasm) return []; + + const names: string[] = JSON.parse(wasm.get_body_names()); + const colors = wasm.get_body_colors(); + const radii = wasm.get_body_radii(); + + return names.map((name, i) => ({ + name, + color: [colors[i * 3], colors[i * 3 + 1], colors[i * 3 + 2]] as [number, number, number], + radius_km: radii[i], + })); +} + +export function getOrbitPoints(bodyId: number, jd: number, samples: number = 180): Float64Array { + const wasm = getWasm(); + if (!wasm) return new Float64Array(0); + return wasm.get_orbit_points(bodyId, jd, samples); +} diff --git a/frontend/src/lib/wasm/loader.ts b/frontend/src/lib/wasm/loader.ts new file mode 100644 index 0000000..ed33ea4 --- /dev/null +++ b/frontend/src/lib/wasm/loader.ts @@ -0,0 +1,25 @@ +import type * as WasmTypes from 'mass-driver-wasm'; + +type WasmExports = typeof WasmTypes; + +let wasmModule: WasmExports | null = null; +let initPromise: Promise | null = null; + +export async function initWasm(): Promise { + if (wasmModule) return wasmModule; + if (initPromise) return initPromise; + + initPromise = (async () => { + const mod = await import('mass-driver-wasm'); + await mod.default(); + mod.init(); + wasmModule = mod; + return mod; + })(); + + return initPromise; +} + +export function getWasm(): WasmExports | null { + return wasmModule; +} diff --git a/frontend/src/lib/wasm/types.ts b/frontend/src/lib/wasm/types.ts new file mode 100644 index 0000000..3c2da09 --- /dev/null +++ b/frontend/src/lib/wasm/types.ts @@ -0,0 +1,5 @@ +export interface BodyInfo { + name: string; + color: [number, number, number]; + radius_km: number; +} diff --git a/frontend/src/routes/(simulator)/components/Canvas2DView.svelte b/frontend/src/routes/(simulator)/components/Canvas2DView.svelte new file mode 100644 index 0000000..1f13740 --- /dev/null +++ b/frontend/src/routes/(simulator)/components/Canvas2DView.svelte @@ -0,0 +1,66 @@ + + + diff --git a/frontend/src/routes/(simulator)/components/TimeControls.svelte b/frontend/src/routes/(simulator)/components/TimeControls.svelte new file mode 100644 index 0000000..5514d08 --- /dev/null +++ b/frontend/src/routes/(simulator)/components/TimeControls.svelte @@ -0,0 +1,72 @@ + + +
+ + + + +
+ {#each speedOptions as opt} + + {/each} +
+ + +
+ + +
+ Date: + +
+ + + + Week {simulation.currentWeekIndex.toLocaleString()} + +
diff --git a/frontend/src/routes/(simulator)/components/ViewToggle.svelte b/frontend/src/routes/(simulator)/components/ViewToggle.svelte new file mode 100644 index 0000000..8d832f8 --- /dev/null +++ b/frontend/src/routes/(simulator)/components/ViewToggle.svelte @@ -0,0 +1,26 @@ + + +
+ + +
diff --git a/frontend/src/routes/(simulator)/components/Viewport.svelte b/frontend/src/routes/(simulator)/components/Viewport.svelte new file mode 100644 index 0000000..cb48ddd --- /dev/null +++ b/frontend/src/routes/(simulator)/components/Viewport.svelte @@ -0,0 +1,16 @@ + + +
+ {#if simulation.viewMode === '2d'} + + {:else} +
+

3D view coming soon...

+
+ {/if} +
diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte new file mode 100644 index 0000000..7ca22ab --- /dev/null +++ b/frontend/src/routes/+layout.svelte @@ -0,0 +1,13 @@ + + + + + Mass Driver — Interplanetary Relay Network + + +{@render children()} diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte new file mode 100644 index 0000000..766dc70 --- /dev/null +++ b/frontend/src/routes/+page.svelte @@ -0,0 +1,64 @@ + + +
+ +
+
+

+ MASS DRIVER +

+ Interplanetary Relay Network +
+ +
+ + +
+ {#if wasmError} +
+
+

Failed to load simulation engine

+

{wasmError}

+
+
+ {:else if !simulation.wasmReady} +
+
+
+

Initializing simulation engine...

+
+
+ {:else} + + {/if} +
+ + + +
diff --git a/frontend/src/routes/+page.ts b/frontend/src/routes/+page.ts new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/frontend/src/routes/+page.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/frontend/static/robots.txt b/frontend/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/frontend/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js new file mode 100644 index 0000000..0c3412e --- /dev/null +++ b/frontend/svelte.config.js @@ -0,0 +1,17 @@ +import adapter from '@sveltejs/adapter-auto'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + compilerOptions: { + // Force runes mode for the project, except for libraries. Can be removed in svelte 6. + runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true) + }, + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..718a830 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,23 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; +import wasm from 'vite-plugin-wasm'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + plugins: [ + wasm(), + tailwindcss(), + sveltekit(), + ], + build: { + target: 'esnext', + }, + optimizeDeps: { + exclude: ['mass-driver-wasm'], + }, + server: { + fs: { + allow: ['..'], + }, + }, +});