# @zendir/ui

## 0.7.0

### Minor Changes

- 339957c: New `@zendir/ui/react/icons` entry point: the full Lucide icon set (ISC license), plus six custom space-status glyphs Lucide has no equivalent for (`SatelliteTransmit`/`SatelliteReceive`/`SatelliteOff`, `AntennaTransmit`/`AntennaReceive`/`AntennaOff`). Named exports throughout — bundlers tree-shake to only the icons actually imported.

  New `Logo` component (`@zendir/ui/react/core`): the Zendir brand mark, primary wordmark lockup, and sub-brand lockups for Docs, Studio, Editor, and API. Ink color is a prop, so one asset covers every surface.

  Both are additive — no existing exports changed.

- c4b2bc0: Export the card, chart and visualization components from `@zendir/ui/react`.

  The top-level React barrel re-exported some sub-barrels but never wired in
  `cards` or `visualizations`, and cherry-picked from `charts` rather than
  re-exporting it. Around sixty components and their prop/data types were
  therefore public one level down while being unreachable from the entry point
  consumers actually import — among them the chart family (`LineChart`,
  `AreaChart`, `BarChart`, `ScatterChart`, `PieChart`, `GaugeChart`, the 3D and
  mission-analysis charts), the cards (`SpacecraftCard`, `TelemetryCard`,
  `OrbitCard`) and the visualization cards (`NavBallCard`, `LinkBudgetCard`,
  `PropulsionCard`, `EclipseTimerCard`, `ThermalHeatmapCard`,
  `SensorFootprintCard`), plus the data types their props require.

  The three sub-barrels are now re-exported wholesale rather than through another
  hand-maintained list, since a curated barrel drifts silently every time a
  component is added — which is how this arose.

  Additive only: nothing was renamed or removed, so existing imports are
  unaffected.

- cc5084b: `ChatPanel` accepts `renderMessageContent` to render message text with a renderer
  the host supplies.

  The built-in renderer covers `**bold**`, `` `code` `` and fenced code blocks, but
  not lists, tables or links — so an assistant instructed to answer in numbered
  steps has every one of them shown as literal text. A markdown renderer is a
  dependency this package does not carry, so the prop hands that choice, and its
  bundle cost, to the consumer.

  Omitted, the built-in renderer is used exactly as before. Blocks are unaffected
  and still render beneath the returned content.

### Patch Changes

- 949dee3: Dependency and tooling maintenance, no public API or behavior change:

  - Resolved all open Dependabot alerts by bumping transitive dev-toolchain dependencies (undici, brace-expansion, postcss, js-yaml, fast-uri, dompurify, protobufjs) via `npm audit fix`. Lockfile-only — no `package.json` ranges changed. Production dependency audit was already clean.
  - Fixed the `css-vars.ts` token generator (`scripts/build-tokens.ts`) to run its output through Prettier, so the generated file stays format-clean regardless of future token-name lengths.

## 0.6.3

### Patch Changes

- **HexViewer**: partial final rows keep full column alignment — the
  two-space pad cells no longer collapse, so the ASCII column lines up on
  the last line of any byte length.
- **Checkbox**: unchecked boxes use `border.default` instead of
  `border.muted`; themes may define the muted hairline equal to an elevated
  surface colour (nebula does), which made unchecked boxes invisible inside
  panels and dropdowns.

## 0.6.2

### Patch Changes

- **GroundTrackMap**: vector layers (tracks, footprints, terminator) render
  to canvas instead of SVG, removing the DOM churn that destabilised Safari
  on the map view.

## 0.6.1

### Patch Changes

- **ThemeProvider**: set the theme variant on `data-theme` and the mode on
  `data-mode` (previously `data-theme` carried the mode and the variant went
  to the unused `data-variant`). The generated `tokens.css` keys its blocks on
  `[data-theme="<variant>"]` / `[data-mode="light"]`, so the mismatch meant the
  static `--zendir-*` CSS variables always resolved to the default (hybrid)
  block — non-hybrid variants (nebula, astro, purple-hue) only themed via
  `useThemeTokens()`, not via CSS variables. CSS-variable consumers now get the
  selected variant. `data-variant` is retained for backward compatibility.

## 0.6.0

### Minor Changes

- **ImageGallery**: new optional `badge` field on `GalleryImage` renders an
  overlay pill on thumbnails (e.g. flagging newly downlinked images).
- **ThemeProvider**: explicit `theme`/`mode` props are now authoritative and
  no longer overridden by persisted preferences; use `defaultVariant`/
  `defaultMode` to opt into localStorage restore.
- New opt-in `nebula` theme (Prussian Slate / Ultraviolet).
- Primitive color scales are now importable from `@zendir/ui/tokens` (Zendir
  brand, Astro UXDS status, Tailwind v4.2) and emitted as `--zendir-*` CSS
  variables, so product code and charts can reference endorsed palette stops
  directly.
- Theme types and palettes extracted into dedicated modules
  (`react/theme/types`, `tokens/palettes`, `tokens/core`); public API
  unchanged.
- Toolchain security refresh: Vitest 4, Vite 8, Storybook 10 — clears every
  outstanding dependency audit advisory (production deps were already clean).

## 0.5.0

### Minor Changes

- Add a `stat` variant to `DataValue` for compact top-bar telemetry chips
  (value over a small uppercase label, status-tinted).
- Refresh `SideNav` styling and layout.

### Patch Changes

- Harden Popover first-open positioning: seed the panel's position state as
  `fixed` and gate visibility on a `positioned` flag that resets on close, so
  neither the first open nor any reopen flashes at stale coordinates.

## 0.4.1

### Patch Changes

- Fix Popover first-open positioning flash: panel now renders with
  `visibility: hidden` until the initial `requestAnimationFrame` resolves
  real coordinates, preventing the one-frame misposition on first open.

## 0.4.0

### Minor Changes

- Add `ImageFrame` widget: renders a single-channel sensor/CCD data frame
  (`number[]` of samples) as a false-colour image on a canvas, with
  fixed-ceiling normalisation, `cividis`/`gray` colormaps, an optional raw
  value grid, and loading/empty states. Purely presentational — callers
  pass the frame data in. Also exports the pure `decodeImage` helper.

### Patch Changes

- Scope the Dialog focus trap to the open transition so typing in a dialog
  input isn't interrupted when the surrounding parent re-renders.

## 0.3.0

**Upgrade from npm:** The latest public release on the registry is
[0.2.21](https://www.npmjs.com/package/@zendir/ui). This **0.3.0** release
is the next version on npm and combines the hook / 3D integration work
with the token / `react/core/` architecture pass below.

This release reframes the React hook layer around a single, well-defined
bridge from `zendir-ts` to the live `<ZenSpace3D />` surface, and
replaces three legacy SDK-coupled hooks with smaller, REST-aligned
primitives. The Cesium backend gains proper sub-satellite ground-track
alignment, and the `ZenSpace3DUtils` helper module is slimmed to only
what the live backends actually consume. It also eliminates token drift
hazards by consolidating literals, auto-generating `tokens.css`, and
reorganising `react/core/` into thematic subfolders.

### Features

- **`useSimulationScene`** — primary integration hook
  (`@zendir/ui/react/hooks` and `@zendir/ui/react/3d`). Bridges a
  `zendir-ts` `ZendirClient` to `ZenSpace3D` props: fetches
  `GetSimulationStructure` once, buckets spacecraft / ground stations /
  planets, and polls `Position_BN_N` / `Velocity_BN_N` on a configurable
  interval (default 2 s). Exports `SimulationSceneClient`,
  `SimulationSceneObject`, `StructureDiag`, `PositionTickDiag`, and
  `UseSimulationSceneResult` for typed app integration.
- **Operator 3D surface** — the hook is intended to drive a full-screen
  `ZenSpace3D` in host apps (e.g. `space-range-operator`
  `/admin/3d/:containerId/:simulationId`). Feed
  `useSimulationScene` with `containerId`, `simulationId`, and the
  session `client`.
- **`ZenSpace3DProps.referenceTime`** — new `Date` prop used by the
  Cesium backend to convert ECI/J2000 positions into the Earth-Fixed
  (ECEF) frame Cesium renders. Passing the simulation clock (sim epoch
  - sim seconds) lines the sub-satellite point on Earth up with the
    true ground track; omitting it falls back to wall-clock time and the
    ground track will drift.
- **Top-level re-exports** — `ZenSpace3D`, `useSimulationScene`, and
  their types are now available from the main `@zendir/ui/react`
  entry in addition to the `react/3d` and `react/hooks` subpaths.

### Breaking Changes

- **Removed legacy SDK hooks**: `useTelemetry`, `useSpacecraftPosition`,
  and `useSimulationTime` no longer exist. Their
  responsibilities are covered by `useSimulationScene` (live position /
  velocity bucketed onto scene objects) and by the `referenceDate` /
  `positionTickDiag` fields on its result.
- **`useAccessWindows` is now pure client-side math**. The Zendir REST
  API does not expose a server-side access-prediction endpoint, so the
  hook no longer attempts to load `zendir-ts` or fetch passes. It now
  accepts a `windows: AccessPass[]` input (compute these with your own
  propagator / SGP4 / demo data) and returns `TimerConfig` objects for
  `<MissionClock />`. Removed options: `spacecraftId`, `groundStationId`,
  `passCount`. Removed result fields: `isConnected`, `isSdkAvailable`,
  `error`. The `manualWindows` option is kept as a deprecated alias
  for `windows` for one minor.
- **Renames in `useAccessWindows`**: the `AccessWindow` type is now
  `AccessPass`, and `createDemoAccessWindows` is now
  `createDemoAccessPasses`. Update imports accordingly.
- **`ZenSpace3DUtils` pruned to the live surface**. Unused helpers were
  removed: `cartesianToLatLonAlt`, `cartesianToSpherical`,
  `sphericalToCartesian`, `eciToEcef`, `calculateGMST`,
  `dateToJulianDate`, `parseTLE`, `propagateSGP4Simple`,
  `propagateSatellite`, `generateOrbitPath`, `isVisible`,
  `calculateVisibilityConeGeometry`, `calculateFootprint`,
  `generateHexGrid`, `updateHexGridCoverage`, all vector-math helpers
  (`dot`, `cross`, `magnitude`, `normalize`, `subtract`, `add`,
  `scale`, `distance`, `lerp`), `easeInOutCubic`, `smoothstep`,
  `lerpColor`, `hexToRgb`, `getGradientColor`, `formatDuration`,
  `formatDistance`, `formatVelocity`, `formatDateTime`, and the
  constants `EARTH_MU`, `AU_KM`, `J2000_EPOCH`. Retained:
  `EARTH_RADIUS_KM`, `DEG_TO_RAD`, `RAD_TO_DEG`, `SCENE_EARTH_RADIUS`,
  `KM_TO_SCENE`, `latLonAltToCartesian`, `easeOutCubic`. For orbital
  mechanics, use a dedicated library (`satellite.js` for SGP4) or
  Cesium's built-in transforms; the Three.js path already draws
  coverage from the explicit `SatelliteCoverage` props.

### Improvements

- **`useZendirSession`** — reworked container + simulation lifecycle
  (provision container, poll until `RUNNING`, then create a simulation),
  with a new `SessionInfo` export and `preloadZendirSdk` /
  `isZendirSdkAvailable` utilities.
- **`ZenSpace3D` Cesium backend** — substantial rework
  (`ZenSpace3DCesium.tsx`) so positions streamed in J2000/ECI are
  correctly rotated into ECEF each frame using the simulation clock,
  keeping the orbit and its ground track visually consistent.
- **`ZenSpace3DTypes` docs** — updated SDK-compatibility example to the
  new `useSimulationScene` pattern and clarified that coverage geometry
  is either explicit (`footprintPolygon` / `halfAngle`) or computed
  locally by the viewer.
- **`types.ts`** — clarified that UI-domain types (`Quaternion`,
  `Vector3D`, `PointingMode`, etc.) ship with `@zendir/ui` and are not
  provided by `zendir-ts` (which is REST-only and intentionally
  minimal). Helpers like `estimateOrbitalPeriod` /
  `estimateOrbitalVelocity` are documented as client-side approximations.
- **Docs refresh** — `SDKIntegration.mdx`, `ComponentsOverview.mdx`,
  and `Introduction.mdx` updated to reflect the `zendir-ts` rename
  (previously `@zendir/sdk`) and the new hook surface.

### Migration

```tsx
// Before (0.2.x)
import {
  useTelemetry,
  useSpacecraftPosition,
  useSimulationTime,
} from "@zendir/ui/react/hooks";

// After (0.3.0)
import { useSimulationScene } from "@zendir/ui/react/hooks";

const scene = useSimulationScene({ client, containerId, simulationId });
// scene.satellites, scene.groundStations, scene.referenceDate, scene.positionTickDiag
```

```tsx
// Before (0.2.x)
import {
  useAccessWindows,
  createDemoAccessWindows,
  AccessWindow,
} from "@zendir/ui/react/hooks";

const { aosTimer, losTimer } = useAccessWindows({
  spacecraftId: "my-sat",
  groundStationId: "gs-1",
});

// After (0.3.0)
import {
  useAccessWindows,
  createDemoAccessPasses,
  AccessPass,
} from "@zendir/ui/react/hooks";

const passes: AccessPass[] = computePasses(satellite, groundStation); // your propagator
const { aosTimer, losTimer } = useAccessWindows({ windows: passes });
```

### Notes

- Consumer apps that use a published tarball should depend on
  `"@zendir/ui": "^0.3.0"`. Monorepo/local development can keep
  `"@zendir/ui": "file:../zendir-ui"`.
- After `npm publish` in this package, run `npm install` in consuming
  apps to pick up 0.3.0 (no automatic push; release is manual).

### Architecture — `core/` reorganised

The 42-file flat `react/core/` folder is split into thematic subfolders:
`inputs/`, `feedback/`, `overlays/`, `navigation/`, `data/`, `display/`,
`widgets/` (alongside the existing `layout/`).

- The `core/index.ts` barrel still exports everything by name. Consumers
  using `import { Button } from '@zendir/ui'` or `from '@zendir/ui/react/core'`
  are unaffected.
- Deep imports like `from '@zendir/ui/react/core/Button'` now resolve via
  the new path; if you have these, switch to the barrel for stability.

### Theme + a11y

- **JS-side `tokens.animation.*` honours `prefers-reduced-motion`.**
  The global CSS @media rule already stripped CSS animations; this
  extends the same behaviour to the JS-side tokens surface so animation
  libraries (Framer Motion etc.) reading `tokens.animation.fast` get
  collapsed values too.

### Removed

- Legacy `EarthViewer` and `SolarSystemViewer` 3D components (~1.6k LOC
  of dead code; superseded by `ZenSpace3D`).
- Deprecated `ChatGPTCard` alias (use `AppCard`) and `useIntrinsicHeight`
  no-op hook (use `useMaxHeight`).
- `scripts/build-tokens.js` (had its own duplicate token literal —
  replaced by `build-tokens.ts` that imports from `tokens/index.ts`).

### Internal

- `src/react/core/` reorganised — see "Architecture" above.
- Operator and dashboard verified working with new structure.
- **Toolchain** — ESLint 9 flat config (`eslint.config.js`), `typescript-eslint` v8,
  Vitest 2.1 + Vite 5.4; `npm audit fix -w @zendir/ui --legacy-peer-deps` at monorepo
  root to clear hoisted dev CVEs without `--force` onto Vite 8. Storybook 8
  `eslint-plugin-storybook` rules removed until Storybook 10+ (plugin v0.8 uses
  APIs dropped in ESLint 9).
- 284/284 tests pass; typecheck clean; build clean.

---

## 0.2.21

### Improvements

- **SidePanel**: Fixed sudden layout shift when opening/closing — width animates from `0px` smoothly during the `opening` phase (previously snapped to full width instantly, causing the flex layout to jump). Closing transition no longer drops width to `0px` instantly while the panel is still sliding out.
- **SidePanel**: Slowed transitions from `0.5s` to `0.6s` and switched to a calmer `cubic-bezier(0.2, 0.8, 0.2, 1)` ease curve for a smoother, less jarring feel — based on UX research showing 500–600ms with deceleration easing reads as "calm" without feeling sluggish.

---

## 0.2.20

### Improvements

- **SidePanel**: Smoother open/close animation — CSS transitions increased from 0.3s to 0.5s with matched JavaScript timing (550ms closing timeout) to prevent premature unmount during slide-out.

---

## 0.2.5

### Bug Fixes

- **Container**: Fixed `[object Object]` rendering when `subtitle` is a React element. Non-string subtitles (JSX) are now rendered directly below the `CardHeader` instead of being coerced via `String()`.

---

## 0.2.4

### Features

#### GroundTrackMap — Layers Panel

- **Layers icon + dropdown panel** — the top-right control bar now shows a stacked-diamond icon that opens a clean dropdown panel with two sections: **Base Map** (Dark/Satellite radio) and **Overlays** (toggle switches for Day/Night terminator, Grid Lines, and custom layers).
- **Custom layers API** — pass `customLayers: MapLayerDef[]` to add your own toggle items to the Overlays section. The map provides the toggle UI; the consumer app renders its own overlay content via `onLayerChange(layerId, enabled)`.
- **Persistent tile style** — Dark/Satellite choice is saved to `localStorage` and restored across tabs and page reloads.
- **Unified control bar** — Recenter, Layers, and Zoom (+/−) are now a single horizontal row at top-right, replacing both the old separate controls and Leaflet's native zoom.
- **Runtime overlay toggling** — built-in overlays (Terminator, Grid) can now be toggled on/off at runtime from the Layers panel without remounting the map.

### Breaking Changes

- `showMapStyleToggle` prop is still accepted but now controls whether the "Base Map" section appears in the Layers panel (default `true`).
- Leaflet's native zoom controls have been replaced by custom-styled buttons matching the Astro UXDS dark ops aesthetic.

### Documentation

- **LayersPanel** story — demonstrates the full layers panel with base map toggle and overlay switches.
- **CustomLayers** story — shows how to add developer-defined overlay toggles with `MapLayerDef[]` and respond via `onLayerChange`.

---

## 0.2.3

### Features

#### GroundTrackMap — Map Style Toggle

- **Built-in Dark / Satellite tile switcher** — when no explicit `tileUrl` is passed, a compact toggle appears below the Leaflet zoom controls allowing operators to switch between Dark (CartoDB) and Satellite (Esri World Imagery) tile layers.
- **No-flash tile swap** — toggling swaps only the tile layer without recreating the Leaflet map, preserving the current zoom, pan, and overlay state.
- **`showMapStyleToggle` prop** — defaults to `true`. Set to `false` to hide the toggle. Automatically hidden when an explicit `tileUrl` is provided (backward compatible).
- **`TILE_PRESETS`** — exported preset URLs (`dark`, `satellite`, `light`) for quick tile configuration.

#### GroundTrackMap — Terminator Boundary Line

- **Geometric terminator line** — a subtle dashed line at the 0° solar depression boundary (geometric sunset/sunrise) is now drawn on both Canvas and Leaflet backends, giving a clear visual reference for the day/night edge while the graduated fade provides the realistic transition.

#### UnifiedTimeline — Playhead Auto-Follow

- **Auto-scroll to playhead** — when the playhead marker moves near the edge of the visible chart viewport, the view auto-scrolls to keep it centered. Throttled at 300ms to avoid fighting user scrolling.
- **Smooth playhead animation** — the playhead line now uses a CSS transition (`left 0.4s linear`) for glide-smooth movement between position updates.

### Documentation

- **Map Style Toggle** story — demonstrates the built-in tile switcher with a satellite track and terminator, including opt-out patterns.
- Updated component description with Map Style Toggle feature.

### Internal

- Refactored tile layer lifecycle in `GroundTrackMapLeaflet` — tile creation is now a separate effect from map initialization, enabling smooth in-place tile swaps.
- **Fixed duplicate tile layer on mount** — the initial mount effect no longer creates a tile layer; the dedicated tile-swap effect is solely responsible, eliminating z-order conflicts that caused the terminator overlay to render behind tiles.
- **Attribution management** — tile swap effect now properly adds/removes Carto vs OSM attribution when switching tile styles.
- Map controls repositioned to bottom-left for reliable visibility across container sizes.

---

## 0.2.2

### Features

#### GroundTrackMap — Night Visualization

- **Seamless night polygon** — completely rewritten terminator geometry: sunset/sunrise curves are now computed as continuous longitude pairs, producing a single closed polygon with no antimeridian artifacts. Eliminates all "jerk" lines and rendering glitches at the poles.
- **`nightTileUrl` prop** — optional second tile layer (e.g. NASA Black Marble, VIIRS city lights) rendered beneath the terminator overlay. The graduated twilight gradient naturally masks the day-to-night tile transition. Leaflet backend only.
- **`lightSources` prop** — array of point light sources (`LightSource` interface) rendered as soft glow dots on the night side of the map. Each light is masked by the terminator: invisible in daylight, fading in through twilight, full brightness at night. Supports custom colors, radii, intensity, and labels.
- **Solar elevation masking** — light source visibility is computed per-point using the exact solar elevation angle (not polygon containment), giving a smooth and physically correct twilight fade for each individual light.

### Documentation

- **City Lights at Night** story — 35+ world cities as `lightSources`, demonstrating realistic nighttime glow.
- **City Lights Time Travel** story — interactive UTC hour slider showing lights switching on/off as the terminator sweeps.
- **Custom Light Sources** story — mission scenario with colored RF emitters, bases, and relay nodes.
- **Night Tile Layer** story — demonstrates `nightTileUrl` combined with `lightSources`.
- Updated component description with Night Tile Layer and Dynamic Light Sources sections.

### Bug Fixes

- Fixed terminator night polygon antimeridian wrapping — sunset and sunrise curves are now generated as continuous coordinate pairs (no `lon ± 180` mirroring), preventing the polygon from crossing the day side.
- Removed unused `buildBandPolygon` function from Leaflet backend.

---

## 0.2.0

### Features

#### SDK Category System (New)

- **`CategoryDef`** — shared interface for a named, colored data category (`id`, `color`, `label?`), used across Timeline, GroundTrackMap, and any future component.
- **`CategoryPalette`** — utility class that maps category IDs to stable colors and labels, with built-in fallback palettes for auto-assignment.
- **`CategoryProvider` / `useCategoryPalette`** — optional React Context for sharing a `CategoryPalette` and a custom display label (e.g. "Squad", "Division") across an entire dashboard. Fully opt-in; components work standalone without it.
- New Storybook documentation page (`SDK Category Concept`) with interactive examples, architecture diagrams, and integration guidance.

#### UnifiedTimeline

- **Team / category-aware events** — events now carry structured team identity (`id`, `color`, `label?`), enabling category-colored event bars and badges.
- **Multi-select team filter** — replaces the former boolean `teamColoredOnly` toggle; users can filter by specific teams via pill-style checkboxes.
- **`teamLabel` prop** — devs can rename "Team" to any domain-specific term (e.g. "Squad", "Division"); also readable from `CategoryProvider` context.
- **Day markers** — `showDayMarkers` draws labeled day boundaries on multi-day timelines.
- **Redesigned filter bar** — collapsible panel with Astro UX filter icon, search field moved to top header row, responsive breakpoints.
- **Performance** — bounding-box culling for `ChartView` events outside the visible viewport.

#### GroundTrackMap

- **Twilight terminator with gradient zones** — realistic Civil / Nautical / Astronomical / Full Night shading derived from solar depression angle, replacing the former hard-edge day/night line. Uses `depressionDeg` for precise boundaries.
- **Collaborative pins** — new `MapPin` interface and `pins` / `pinsEditable` / `onPinAdd` / `onPinUpdate` / `onPinRemove` props for placing colored points-of-interest synced across terminals.
- **Enhanced satellite visualization** — `showFootprint` / `footprintRadius` for sensor coverage circles, `futureTrackIndex` for solid-past / dashed-future track split, `passMarkers` for AOS/LOS diamonds.
- **Ground station antenna icons** — `dish`, `phased-array`, `omni`, and `relay` icon types with coverage circles and Astro status colors.
- **Legend overlay** — auto-generated satellite + ground station legend with status indicators.
- Category-aware pin colors via `CategoryProvider` integration.

#### SimulationControls

- Increased control-button background opacities for better visibility on dark themes.

### Documentation

- Comprehensive Storybook updates for Timeline (`WithCategoryProvider`, `MultiDayWithCategories` stories) and GroundTrackMap (`SDK Category Integration` section).
- New `CategoryConcept` Storybook page with `PaletteBasics`, `ProviderAndHook`, and `DynamicCategories` interactive stories.

### Breaking Changes

- `TimelineEvent.color` replaced by `TimelineEvent.team` (`TimelineTeam` / `CategoryDef`). Plain string colors are still accepted for backward compatibility but are treated as legacy entries.
- `TimelineFilter.teamColoredOnly` replaced by `TimelineFilter.teams` (multi-select `string[]`).

---

## 0.1.0

Initial public release.

### Features

- 60+ React components for space, air, land, and maritime operations
- Built on Astro UX Design System
- Themeable (Zen, Hybrid, Astro, Glass) with dark/light mode
- ECharts-based charting (line, area, bar, radar, treemap, sankey, and more)
- 3D viewers (Earth, Solar System, ZenSpace3D) via Three.js
- Ground track maps (Leaflet and canvas backends)
- Mission control widgets (MissionClock, SimulationControls, UnifiedTimeline)
- Layout primitives (Box, Flex, Grid, Stack, Container, SideNav, SidePanel)
- Accessibility-first (WCAG 2.2 AA oriented)
- Design tokens as JS objects and CSS custom properties
- Optional `zendir-ts` (Zendir TypeScript SDK) integration with graceful fallback
