diff --git a/.gitea/workflows/gitea-main-ci.yaml b/.gitea/workflows/gitea-main-ci.yaml index 449e2ed..f521a62 100644 --- a/.gitea/workflows/gitea-main-ci.yaml +++ b/.gitea/workflows/gitea-main-ci.yaml @@ -49,9 +49,10 @@ env: # per-environment DNS name (e.g. nvme.fleet.lab) resolved at boot so one UKI # works across environments — the serving stack's dnsmasq resolves it. NVMET_TRADDR: "192.168.50.10" - # URL the fullscreen Chromium kiosk loads at boot. Defaults to the test page - # served by the boot HTTP server (serving/http/kiosk) at /kiosk/; boot.fleet.lab - # resolves to the serving host via the stack's dnsmasq. Empty disables the kiosk. + # URL the fullscreen Chromium kiosk loads at boot. Defaults to the kiosk SPA + # (serving/kiosk-app) reverse-proxied by the boot HTTP server at /kiosk/; + # boot.fleet.lab resolves to the serving host via the stack's dnsmasq. Empty + # disables the kiosk. KIOSK_URL: "http://boot.fleet.lab/kiosk/" # Plymouth boot splash. "false" => verbose console boot (rhgb quiet NOT baked; # plymouth.enable=0 instead) — kept OFF in CI while the network-manager port is diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27a3afb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +reports diff --git a/CLAUDE.md b/CLAUDE.md index db5dad6..72ca1f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,8 @@ files/kiosk/ weston+Chromium kiosk + chromium lockd scripts/extract-artifacts.sh qcow2 -> kernel/initramfs/erofs+verity/cmdline scripts/build-oci.sh ukify + sbsign + roothash gate + OCI image serving/ Lab serving stack: dnsmasq + nginx + SPDK +serving/kiosk-app/ Svelte+Tailwind spaceship-terminal SPA + (kiosk endpoint; nginx reverse-proxies /kiosk/) .gitea/workflows/ Gitea CI: lint -> packer build -> push OCI docs/ Design PDF and supporting notes ``` @@ -152,9 +154,17 @@ instead of working around it. build; the supplicant config carries both options. - erofs module availability on the target RHEL kernel (fallback: journal-less ext4 in `extract-artifacts.sh`). -- STIG remediation: (a) verify the SSG datastream name on the target distro - (`ssg-rhel9-ds.xml` vs `ssg-rl9-ds.xml` — the provisioner tries both) and that - the `...content_profile_stig` profile id exists; (b) remediation must not break +- STIG remediation: (a) datastream **must match the distro**. The provisioner + maps `/etc/os-release` `$ID` → datastream (Rocky→`ssg-rl9-ds.xml`, + RHEL→`ssg-rhel9-ds.xml`, al9/cs9/ol9). **Do NOT run the RHEL9 datastream on + Rocky** — its CPE dictionary has no Rocky entry, so every rule returns + `notapplicable`, the scan evaluates **0** rules, remediation does nothing, and + the report is *falsely clean*. (Seen 2026-06-18: a build picked rhel9 via + `ls … | head -1` and produced 462 notapplicable + 1068 notselected, **0 + pass/fail**.) Two guards now exist: the provisioner aborts the build if 0 rules + are evaluated, and `stig-deviations.sh` flags an INVALID SCAN + exits 2. Also + confirm the `...content_profile_stig` profile id exists in the chosen + datastream; (b) remediation must not break boot/login (NM/NVMe, key-only sshd, kiosk, SELinux enforcing) — `enable_stig=false` is the escape hatch; (c) **reproducibility**: build twice and confirm identical `root_hash` in verity.json — if it drifts, oscap wrote something @@ -174,7 +184,10 @@ instead of working around it. into the initramfs (`lsinitrd | grep -E 'fleet.script|script.so'`), and (c) it renders (text via `Image.Text` needs the label plugin + a font in the early initramfs). If blank, the splash silently no-ops but boot continues; ESC shows - the log. Untested in this workspace (needs a display). + the log. **RENDER-VERIFIED 2026-06-18** (test-path build): the scripted theme + draws and the handshake checklist updates from the hook status tokens. Still + unverified on the **secure path** (the auth/macsec rows reaching `done` — see + #8 bench items). - `systemd.verity_root_options=hash-offset` is **NOT supported** by Rocky 9.8 systemd-veritysetup (verified: "unknown option hash-offset=" → "Failed to load verity superblock"). Resolved by the documented fallback — data and hash are diff --git a/diskless.pkr.hcl b/diskless.pkr.hcl index 3de45f1..f832e93 100644 --- a/diskless.pkr.hcl +++ b/diskless.pkr.hcl @@ -440,15 +440,31 @@ build { "if [ \"$ENABLE_STIG\" != \"true\" ]; then echo 'enable_stig=false -> skipping STIG hardening.'; mkdir -p /root; echo 'STIG hardening disabled for this build (enable_stig=false).' > /root/stig-report.html; : > /root/stig-arf.xml; exit 0; fi", "echo '==> Installing OpenSCAP + SCAP Security Guide'", "dnf install -y openscap-scanner scap-security-guide", - # SSG ships rhel9 content (usable on Rocky); some rebuilds name it rl9. - "DS=$(ls /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml /usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml 2>/dev/null | head -1)", - "[ -n \"$DS\" ] || { echo 'ERROR: SSG rhel9 datastream not found' >&2; exit 1; }", + # Pick the datastream that MATCHES THIS DISTRO. The RHEL9 datastream's CPE + # dictionary has no Rocky entry, so running it on Rocky makes EVERY rule + # 'notapplicable' -> the scan evaluates nothing and the report is falsely + # clean. SSG ships a Rocky-specific datastream (ssg-rl9-ds.xml); map by + # /etc/os-release ID (al9=AlmaLinux, cs9=CentOS Stream, ol9=Oracle). + ". /etc/os-release", + "case \"$ID\" in rocky) SSG=rl9;; almalinux) SSG=al9;; centos) SSG=cs9;; ol|oracle) SSG=ol9;; rhel|redhat) SSG=rhel9;; *) SSG=rhel9;; esac", + "DS=/usr/share/xml/scap/ssg/content/ssg-$SSG-ds.xml", + "[ -f \"$DS\" ] || DS=/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml", # last-resort fallback + "[ -f \"$DS\" ] || { echo \"ERROR: no SSG datastream found for $ID ($DS)\" >&2; exit 1; }", + "echo \"==> Using SSG datastream $DS for $ID $VERSION_ID\"", "install -D -m0644 /tmp/stig/diskless-tailoring.xml /etc/stig/diskless-tailoring.xml", "echo \"==> Remediating against tailored STIG profile using $DS (this changes a lot)\"", # --remediate returns nonzero when rules remain failed (expected) — tolerate. "oscap xccdf eval --remediate --profile xccdf_org.ssgproject.content_profile_stig_diskless --tailoring-file /etc/stig/diskless-tailoring.xml --results-arf /root/stig-arf.xml --report /root/stig-report.html \"$DS\" || true", # Clean post-remediation eval = the report/ARF of record (no --remediate). "oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_stig_diskless --tailoring-file /etc/stig/diskless-tailoring.xml --results-arf /root/stig-arf.xml --report /root/stig-report.html \"$DS\" || true", + # GUARD: a scan with 0 evaluated rules (everything notapplicable/notselected) + # means the datastream/CPE didn't match this host — NO hardening happened and + # the report would be falsely 'clean'. Fail the build rather than ship an + # unhardened image masquerading as STIG-compliant. (enable_stig=false is the + # intentional way to skip hardening.) + "evaluated=$(grep -oE '(pass|fixed|fail)' /root/stig-arf.xml | wc -l)", + "[ \"$evaluated\" -gt 0 ] || { echo 'ERROR: STIG scan evaluated 0 rules (all notapplicable/notselected) — SSG datastream/CPE mismatch for this distro. Hardening did NOT run; aborting.' >&2; exit 1; }", + "echo \"==> STIG scan evaluated $evaluated rules (pass/fixed/fail)\"", "rm -rf /tmp/stig", "echo '==> STIG remediation complete (report at /root/stig-report.html, ARF at /root/stig-arf.xml)'", ] diff --git a/docs/TODO.md b/docs/TODO.md index 2fab6e8..28ea51f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -5,6 +5,24 @@ Persistent backlog, kept in sync with the working session task tracker. Status: --- +## Current build posture (LAB / test) — as of 2026-06-18 + +The image currently builds in **open-network test mode**. These toggles must be +flipped back for a secure/production build (consolidated as item #8): +- `disable_dot1x=true` / CI `DISABLE_DOT1X="true"` → `rd.dot1x=0` (NO 802.1X / + MACsec; open NVMe-oF). **TEMPORARY.** +- `nvmet_traddr` / CI `NVMET_TRADDR="192.168.50.10"` → hardcoded IP (should be a + per-env DNS name like `nvme.fleet.lab` once DNS-at-boot is trusted). +- Unsigned UKI (`sb_key`/`sb_cert` not passed) → Secure Boot must be OFF on the + test target. Production needs CI/HSM signing. +- `enable_kiosk=false` by default (CI may set true); `enable_stig=true`; + `boot_splash=true`; NTP via DHCP→serving-host chrony. + +What's working/verified vs awaiting bench is per-item below. Cross-cutting +bench-verify checklist lives in `CLAUDE.md` → "Known bench-verify items". + +--- + ## [x] 1. Port the dracut module off `network-legacy` → `network-manager` **DONE — test path VERIFIED ON THE BENCH 2026-06-17** (host stays up past the @@ -56,11 +74,28 @@ in **[stig-deviations.md](stig-deviations.md)** (curated register) and reported per-build by **[../scripts/stig-deviations.sh](../scripts/stig-deviations.sh)** (non-pass rules tagged intentional vs REVIEW); CI publishes `stig-report.html` + `stig-arf.xml` + `stig-deviations.md`. Scanner tooling is -removed from the shipped image. **BENCH-VERIFY:** SSG datastream name; boot/login -still work; reproducible verity hash across two builds; triage REVIEW rows. +removed from the shipped image. **BENCH-VERIFY:** boot/login still work; +reproducible verity hash across two builds; triage REVIEW rows. + +**FINDING 2026-06-18 (FIXED) — false-clean scan.** The first hardened build +(branch `hardening`, `ada4aa4`) produced a report reading "0 REVIEW / clean", +but the ARF had **0 pass, 0 fail** — only 462 `notapplicable` + 1068 +`notselected`. Root cause: the provisioner selected the datastream with +`ls ssg-rhel9-ds.xml ssg-rl9-ds.xml | head -1`, which picks **rhel9** +alphabetically; the RHEL9 datastream's CPE dictionary has **no Rocky entry**, so +on Rocky 9.8 every selected rule went `notapplicable` and **nothing was actually +evaluated or remediated** — the image was unhardened despite a clean-looking +report. Fixes: (a) provisioner now maps `/etc/os-release $ID` → datastream +(Rocky→`ssg-rl9-ds.xml`); (b) provisioner **aborts the build** if the scan +evaluates 0 rules; (c) `stig-deviations.sh` flags an **INVALID SCAN** and exits +non-zero on 0-evaluated. **Re-run the hardened build** and confirm pass/fail +counts are non-zero before trusting any deviations report. Remaining (future hardening, currently waived — see the register): FIPS, -firewall, remote audit offload, per-host/rotatable admin access. +firewall, per-host/rotatable admin access. **Remote audit offload — receiver +DONE** (`serving/audit-collector/`, rsyslog over TCP/514, per-source-host files; +DNS name `${AUDIT_HOST}`); the **image-side sender** (audisp-syslog + rsyslog +forward to `audit.fleet.lab`) is still to wire — see that dir's README. ### Original spec (retained for reference) @@ -116,7 +151,12 @@ re-export a **v3** PDF (key change: `rootovl` → `systemd.volatile=overlay` in step 8 and §5). Retire/reset the errata afterward. Needs the design-doc source (not in this repo). -## [~] 4. Kiosk mode — autologin + fullscreen Chromium loading a URL +## [x] 4. Kiosk mode — autologin + fullscreen Chromium loading a URL + +**WORKING (display-verified 2026-06-17).** Remaining bits are polish (GPU/mesa +trim, ship via CI `enable_kiosk=true`) and the phone-home/NTP lockdown (done) — +all captured in the sub-notes below. Secure-path render check is folded into #8. + **WORKING / display-verified 2026-06-17** (kiosk renders Chromium fullscreen on the configured URL). Build-flagged OFF by default for now (`enable_kiosk=false`) @@ -147,11 +187,19 @@ Path to getting here (don't re-discover): smaller image. **Re-enable with `-var enable_kiosk=true` when returning to kiosk work** — CI does not pass the var, so CI currently builds lean (no kiosk) too; add `-var enable_kiosk=true` to the Gitea workflow to ship it. -- `kiosk_url` Packer var (default `http://boot.fleet.lab/kiosk/` — the - self-contained test page served by the boot HTTP server at - `serving/http/kiosk/`, via the `/kiosk/` nginx location; validates a boot - without external connectivity. Empty disables the whole install). Wired in CI - as `KIOSK_URL` env. +- `kiosk_url` Packer var (default `http://boot.fleet.lab/kiosk/` — the kiosk + **SPA** (`serving/kiosk-app/`, Svelte 5 + Tailwind v4, a cyber sci-fi + spaceship monitoring/control terminal) served by the boot HTTP server, which + **reverse-proxies** `/kiosk/` to the `kiosk` container; self-contained so a + boot validates without external connectivity. Empty disables the whole + install). Wired in CI as `KIOSK_URL` env. +- **Kiosk SPA (`serving/kiosk-app/`)** — replaced the old single static + `serving/http/kiosk/index.html` (2026-06-18). Top navbar + 5 pages + (Bridge/Reactor/Nav/Life Support/Comms) with dummy telemetry; hash routing; + builds in a 2-stage Docker image (Vite → nginx), reverse-proxied at `/kiosk/` + by the main serving nginx (loopback `127.0.0.1:${KIOSK_APP_PORT:-8088}`). + `npm run build` verified locally; container build untested (no docker here). + Dummy data lives in `src/lib/data.js` (swap for a fleet API later). - Provisioner enables EPEL, installs `weston chromium mesa-dri-drivers mesa-libEGL mesa-libgbm dejavu-sans-fonts`, creates a passwordless unprivileged `kiosk` user (video/input groups). @@ -439,3 +487,33 @@ export is read-only + single-namespace = inherently small attack surface. — it's the fleet's disk). Ties into #6 (rolling updates) availability. - Revisit a memory-safe target only if/when an off-the-shelf one matures (e.g. a future Rust-for-Linux `nvmet`). + +## [ ] 8. Return to secure / production posture (un-toggle the lab flags) + +Consolidates the cleanup of the temporary lab toggles (see "Current build +posture" at the top) and the secure-path bench-verifies deferred while testing +the open-network path. Do these together, then run the full secure boot-chain +smoke test. + +- **Re-enable 802.1X/MACsec:** `disable_dot1x=false` (CI `DISABLE_DOT1X="false"`). + Removes `rd.dot1x=0`/`ip=dhcp`; the hook owns the link (macsec0). +- **NVMe discovery by DNS:** `nvmet_traddr=nvme.fleet.lab` (CI), resolved at boot + by the serving dnsmasq — drop the hardcoded `192.168.50.10`. +- **Signing:** wire `sb_key`/`sb_cert` (CI/HSM secrets) for a signed UKI; enable + Secure Boot on targets. Until then the loud unsigned warning must stay. +- **Bench-verify on the secure path** (swtpm + hostapd + FreeRADIUS, + `disable_dot1x=false`) — the items deferred this session: + - NM port secure path: `en*` UNMANAGED + `macsec0` MANAGED/renewing, MKA + survives switch-root, NVMe stays up past lease (item #1 risk). + - NTP via DHCP option 42 reaches chrony over macsec0 (`chronyc sources`). + - Plymouth `fleet` theme: test-path render VERIFIED 2026-06-18; still confirm + the auth→macsec rows reach `done` live on the secure path. + - STIG remediation didn't break the secure path; reproducible verity hash; + triage the generated `stig-deviations.md` REVIEW rows (item #2). +- **Secure-path NVMe connect trigger:** the test path uses + `nvmf-connect.service` (gated `rd.dot1x=0`); confirm the secure path's + connect (dracut nvmf over macsec0) actually fires, or give it an equivalent + trigger. Apply the same `-k 30 -l -1 -c 10` keep-alive/reconnect flags to the + secure `nvme connect` as the test path has. +- **Future hardening waived for now** (from the STIG register): FIPS, firewall, + remote audit offload, per-host/rotatable admin (SSH CA / config-agent). diff --git a/docs/stig-deviations.md b/docs/stig-deviations.md index 36b474b..80ef8b3 100644 --- a/docs/stig-deviations.md +++ b/docs/stig-deviations.md @@ -26,7 +26,7 @@ Posture decisions (2026-06-18, lab phase): | 3 | **AIDE / file integrity** (`package_aide_installed`, `aide_build_database`, `aide_periodic_cron_checking`, `aide_scan_notification`, `aide_verify_*`) | The root is read-only and cryptographically verified; an on-disk AIDE db would also be non-deterministic and break the reproducible verity hash. | **dm-verity** verifies the whole root filesystem against a signed root hash on every read — continuous, hardware-backed integrity that supersedes AIDE's periodic scan. | | 4 | **Firewall** (`service_firewalld_enabled`, `package_firewalld_installed`, `set_firewalld_default_zone`) | Currently `firewall --disabled`; auto-enabling firewalld/nftables risks the NVMe/TCP (`:4420`) and NetworkManager paths during bring-up. | Isolated boot LAN; egress controlled at the serving-stack / network firewall layer. **Future hardening** — re-enable with explicit allow rules once the secure path is verified. | | 5 | **FIPS** (`enable_fips_mode`, `grub2_enable_fips_mode`, `package_dracut-fips_installed`) | Decision: off for the lab phase (interacts with the still-unsigned UKI / Secure Boot story). | Revisit with signing. No weakened crypto is configured in the meantime. | -| 6 | **Remote audit offload** (`auditd_audispd_configure_remote_server`, `..._encrypt_sent_records`, `..._network_failure_action`, `rsyslog_remote_loghost`) | Decision: local-only for now; no collector deployed. | auditd enabled and recording locally. **Future hardening** — `audisp-remote`/rsyslog to a per-environment collector DNS name (host-generic, no per-host state). | +| 6 | **Remote audit offload** (`auditd_audispd_configure_remote_server`, `..._encrypt_sent_records`, `..._network_failure_action`, `rsyslog_remote_loghost`) | Decision: local-only for now; image not yet forwarding. | auditd enabled and recording locally. **Receiver now deployed** — `serving/audit-collector/` (rsyslog over TCP/514, per-source-host files, DNS name `${AUDIT_HOST}`). **Remaining hardening** — wire the image-side sender (audisp-syslog + rsyslog forward to the host-generic `audit.fleet.lab`, no per-host state); then this row's `rsyslog_remote_loghost` should pass. | | 7 | **Smart card login** (`require_smart_card_login`, `sssd_enable_smartcard`, `install_smartcard_packages`) | Diskless hosts have no smartcard reader; admin access is SSH key-based. Enforcing smartcard login would lock the host out. | Admin auth is an SSH **key-only** account; root is locked; password auth disabled. | | 8 | **NOPASSWD sudo** (`sudo_remove_nopasswd`) | The baked admin is a **passwordless, key-only** account, so `sudo` cannot prompt for a password — NOPASSWD is required for it to function at all. | Single fleet-generic admin, key-only, `wheel`; no interactive password exists to protect. Tracked for the eventual per-host/rotatable access model (SSH CA / config-agent). | | 9 | **GUI / autologin / GDM** (gnome screensaver, GDM banner, automatic-login rules) | No GNOME/GDM is installed; the kiosk is an **intentional** autologin single-app (weston + Chromium) session on kiosk builds. | Kiosk user is unprivileged and passwordless; the session runs a single locked-down browser (no shell, no nav). Applies to kiosk builds only; non-kiosk builds don't install the GUI stack. | diff --git a/scripts/stig-deviations.sh b/scripts/stig-deviations.sh index 5737b40..bdd969b 100644 --- a/scripts/stig-deviations.sh +++ b/scripts/stig-deviations.sh @@ -71,8 +71,21 @@ for k in ("pass", "fixed", "fail", "error", "unknown", "notchecked", "notapplicable", "notselected", "informational"): if counts.get(k): print(f"- **{k}**: {counts[k]}") -print(f"\n**{review} rule(s) need REVIEW** " - f"(non-pass and not a documented intentional deviation).\n") + +# A scan where NOTHING was actually evaluated (no pass/fixed/fail — only +# notapplicable/notselected) is NOT clean: the datastream/CPE didn't match the +# host, so no rule was checked and no hardening was verified. Flag it loudly +# instead of letting "0 REVIEW" read as success. +evaluated = counts.get("pass", 0) + counts.get("fixed", 0) + counts.get("fail", 0) +if evaluated == 0: + print("\n> ⚠️ **INVALID SCAN — 0 rules evaluated.** Every rule came back " + "`notapplicable`/`notselected`, which means the SSG datastream/CPE did " + "not match this host (e.g. the RHEL9 datastream run on Rocky). **No " + "hardening was assessed** — this report is NOT a clean result. Use the " + "distro-matching datastream (`ssg-rl9-ds.xml` on Rocky) and rebuild.\n") +else: + print(f"\n**{review} rule(s) need REVIEW** " + f"(non-pass and not a documented intentional deviation).\n") if findings: print("## Findings (non-pass)\n") @@ -80,8 +93,11 @@ if findings: print("|---|---|---|---|---|") for sev, res, rid, title, tag in findings: print(f"| {sev} | {res} | `{short(rid)}` | {tag} | {title} |") -else: +elif evaluated > 0: print("No fail/error/unknown/notchecked rules — clean against the tailored profile.") + +# Non-zero exit on an invalid (non-evaluating) scan so callers can gate on it. +sys.exit(2 if evaluated == 0 else 0) PY [ "$OUT" = /dev/stdout ] || echo "wrote $OUT" >&2 diff --git a/serving/.env.example b/serving/.env.example index 1cb629c..b059bcd 100644 --- a/serving/.env.example +++ b/serving/.env.example @@ -28,12 +28,21 @@ DHCP_RANGE_START=192.168.50.100 DHCP_RANGE_END=192.168.50.200 DHCP_LEASE=12h +# --- Kiosk SPA ------------------------------------------------------------- +# Loopback port the kiosk-app container publishes; the host-network main nginx +# reverse-proxies /kiosk/ to http://127.0.0.1:${KIOSK_APP_PORT}. Change only if +# 8088 clashes on the serving host (also update http/default.conf proxy_pass). +KIOSK_APP_PORT=8088 + # --- DNS names ------------------------------------------------------------- BOOT_DOMAIN=fleet.lab BOOT_HOST=boot.fleet.lab # HTTP boot server name (UKI) # NVMe/TCP discovery name. Put THIS value in the UKI cmdline as NVMET_TRADDR # (extract-artifacts.sh) so rd.nvmf.discover resolves here per environment. NVME_HOST=nvme.fleet.lab +# Remote audit collector name. Host-generic — bake into the image's rsyslog +# forward rule so every host offloads auditd here; resolved to this serving host. +AUDIT_HOST=audit.fleet.lab # --- NVMe/TCP target ------------------------------------------------------- SUBNQN=nqn.2026-06.org.example.fleet:rootfs diff --git a/serving/README.md b/serving/README.md index fe299fe..3d0d5e5 100644 --- a/serving/README.md +++ b/serving/README.md @@ -58,7 +58,8 @@ docker compose ps # HTTP: UKI is served curl -sI http://${SERVER_IP}/fleet-${IMAGE_VERSION}.efi | head -1 # 200 OK -# HTTP: kiosk test page is served (what the diskless kiosk loads by default) +# HTTP: kiosk SPA is served (reverse-proxied to the kiosk-app container — +# the spaceship terminal the diskless kiosk loads by default) curl -sI http://${SERVER_IP}/kiosk/ | head -1 # 200 OK # NTP: lab time server answers (diskless hosts get it via DHCP option 42) @@ -71,6 +72,11 @@ sudo nvme discover -t tcp -a ${SERVER_IP} -s 4420 # lists the # DNS: discovery name resolves to the serving host dig +short @${SERVER_IP} nvme.fleet.lab # -> ${SERVER_IP} +# Audit collector: rsyslog receiver is listening on :514 (booted hosts offload +# auditd here; records land in audit-collector/logs//) +ss -lntp | grep ':514' +logger -n ${SERVER_IP} -P 514 -T -t audispd "collector smoke test" # -> logs//audit.log + # DHCP/boot: watch leases + bootfile handout docker compose logs -f dnsmasq ``` diff --git a/serving/audit-collector/.gitignore b/serving/audit-collector/.gitignore new file mode 100644 index 0000000..d28a08f --- /dev/null +++ b/serving/audit-collector/.gitignore @@ -0,0 +1,5 @@ +# Received logs and rsyslog spool are runtime state, not source. +logs/* +spool/* +!logs/.gitkeep +!spool/.gitkeep diff --git a/serving/audit-collector/Dockerfile b/serving/audit-collector/Dockerfile new file mode 100644 index 0000000..a255c69 --- /dev/null +++ b/serving/audit-collector/Dockerfile @@ -0,0 +1,17 @@ +# syntax=docker/dockerfile:1 +# +# rsyslog as a remote log/audit collector (RECEIVE side only) for the diskless +# fleet serving stack. Booted hosts forward auditd + syslog here; records land +# under /var/log/remote//. See rsyslog.conf + README.md. + +FROM alpine:3.20 + +RUN apk add --no-cache rsyslog tzdata \ + && mkdir -p /var/log/remote /var/spool/rsyslog + +COPY rsyslog.conf /etc/rsyslog.conf + +EXPOSE 514/tcp +# Foreground (-n), our config only (-f). rsyslog logs its own startup to stderr, +# captured by `docker compose logs audit`. +ENTRYPOINT ["rsyslogd", "-n", "-f", "/etc/rsyslog.conf"] diff --git a/serving/audit-collector/README.md b/serving/audit-collector/README.md new file mode 100644 index 0000000..24a0185 --- /dev/null +++ b/serving/audit-collector/README.md @@ -0,0 +1,82 @@ +# Audit / log collector (receive side) + +An **rsyslog** container that receives logs forwarded by booted diskless hosts +and writes them to durable, per-source-host files. Diskless hosts run on an +immutable erofs root with a **RAM-only volatile overlay**, so their local +`/var/log/audit/audit.log` is lost on every reboot — this gives auditd somewhere +permanent to offload to. It implements the remote-audit-offload that STIG +deviation #6 (`docs/stig-deviations.md`) lists as future hardening. + +``` +booted host serving host +┌───────────────────────────┐ ┌──────────────────────────────┐ +│ auditd → audisp-syslog │ TCP/514 │ audit (this container) │ +│ → rsyslog (forward) ├──────────►│ rsyslogd → /var/log/remote/ │ +│ to ${AUDIT_HOST} │ syslog │ /audit.log │ +└───────────────────────────┘ │ /messages.log │ + └──────────────────────────────┘ +``` + +## What this provides (the receiver) + +- Listens on **TCP/514** (plain syslog). Disk-assisted queue so bursts / brief + stalls don't drop records. +- Writes per **sender IP** (not the claimed hostname): audit records → + `audit.log`, everything else → `messages.log`, under + `./logs//` (bind-mounted, inspectable on the host). +- Host-networked, so hosts reach it at `${AUDIT_HOST}` (resolved to the serving + host by the stack's dnsmasq), matching the boot/NVMe DNS pattern. + +Run it with the rest of the stack: + +```bash +cd serving +docker compose build audit +docker compose up -d audit +docker compose logs -f audit # startup + connection messages +tail -F audit-collector/logs/*/audit.log +``` + +Quick self-test (send a line as if from a host): + +```bash +logger -n 127.0.0.1 -P 514 -T -t audispd "test audit record" +ls audit-collector/logs/ # a 127.0.0.1/ dir appears +``` + +## What's still needed (the sender — in the image) + +This container only **receives**. To make hosts actually send, the hardened +image needs (not yet wired — keep `no per-host state`, use the generic +`${AUDIT_HOST}` name, never a per-host address): + +1. **auditd → syslog**: enable the `audisp-syslog` plugin + (`/etc/audit/plugins.d/syslog.conf`: `active = yes`), so audit events also go + to the local syslog socket. +2. **rsyslog forward rule**: a drop-in (e.g. `/etc/rsyslog.d/60-fleet-remote.conf`) + forwarding to the collector with a disk-assisted action queue, so a network + blip doesn't drop records: + + ```rsyslog + *.* action(type="omfwd" target="audit.fleet.lab" port="514" protocol="tcp" + queue.type="LinkedList" queue.fileName="fwdq" + queue.maxDiskSpace="256m" queue.saveOnShutdown="on" + action.resumeRetryCount="-1") + ``` +3. Bake `audit.fleet.lab` (the generic name) — DNS resolves it per environment. + +Once the sender is wired, flip STIG deviation #6 in `docs/stig-deviations.md` +from "local-only" to "offloading to ${AUDIT_HOST}" and the +`rsyslog_remote_loghost` rule should pass. + +## Production hardening (lab simplifications to revisit) + +The lab stack is **plaintext TCP**, mirroring the HTTP-boot/no-TLS posture. For +a secured fleet: + +- **TLS**: rsyslog `gtls` driver (mutual auth with the boot-infra CA), or + **RELP + TLS** (`imrelp`/`omrelp`) for guaranteed, acknowledged delivery. +- **Restrict senders** by subnet (`$AllowedSender`) or firewall :514 to the + boot LAN. +- Add log rotation/retention (logrotate sidecar or rsyslog `outchannel`) and + ship to long-term storage / SIEM. diff --git a/serving/audit-collector/logs/.gitkeep b/serving/audit-collector/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/serving/audit-collector/rsyslog.conf b/serving/audit-collector/rsyslog.conf new file mode 100644 index 0000000..2767de0 --- /dev/null +++ b/serving/audit-collector/rsyslog.conf @@ -0,0 +1,46 @@ +# Remote audit / syslog collector for the diskless fleet (LAB). +# +# Receives logs forwarded by booted diskless hosts over plain TCP/514 and writes +# them per-source-host. Diskless hosts have an immutable, RAM-only overlay root, +# so their local audit log dies on reboot — this gives auditd somewhere durable +# to offload to (STIG remote-audit-offload, deviation #6 in docs/stig-deviations.md). +# +# Lab posture: PLAINTEXT TCP, mirroring the HTTP-boot/no-TLS lab stack. For +# production switch to TLS (gtls driver) or RELP+TLS and restrict senders by +# subnet — see README.md. + +global(workDirectory="/var/spool/rsyslog") + +# --- intake: plain TCP syslog --- +module(load="imtcp" maxSessions="1000") +input(type="imtcp" port="514") +# Some senders only speak UDP; uncomment to also accept it (lossy, no queue). +#module(load="imudp") +#input(type="imudp" port="514") + +# Disk-assisted main queue so a brief disk stall or a burst of records doesn't +# drop audit events (the records keep flowing to spool, then drain to disk). +main_queue( + queue.type="LinkedList" + queue.filename="mainq" + queue.maxdiskspace="1g" + queue.saveOnShutdown="on" +) + +# --- per-source-host files (keyed by SENDER IP, not the claimed hostname) --- +template(name="HostAudit" type="string" string="/var/log/remote/%FROMHOST-IP%/audit.log") +template(name="HostMessages" type="string" string="/var/log/remote/%FROMHOST-IP%/messages.log") + +# Audit records (audisp-syslog tags them 'audispd'/'audit', or forwards on the +# local6 facility) go to a dedicated per-host audit.log; everything else to +# messages.log. Files/dirs are created 0640/0750 (audit logs aren't world-read). +if ($programname == 'audispd') + or ($programname startswith 'audit') + or ($syslogfacility-text == 'local6') then { + action(type="omfile" dynaFile="HostAudit" + dirCreateMode="0750" fileCreateMode="0640") + stop +} + +action(type="omfile" dynaFile="HostMessages" + dirCreateMode="0750" fileCreateMode="0640") diff --git a/serving/audit-collector/spool/.gitkeep b/serving/audit-collector/spool/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/serving/docker-compose.yml b/serving/docker-compose.yml index 03c4805..0b0abc4 100644 --- a/serving/docker-compose.yml +++ b/serving/docker-compose.yml @@ -63,6 +63,9 @@ services: # resolve the per-environment DNS name to this serving host. - --address=/${NVME_HOST}/${SERVER_IP} - --address=/${BOOT_HOST}/${SERVER_IP} + # Host-generic remote audit collector name (baked into the image config so + # every host offloads auditd here; resolved per-environment to this host). + - --address=/${AUDIT_HOST}/${SERVER_IP} # Kiosk is LOCAL-ONLY (loads boot.fleet.lab), so sinkhole Chromium's # call-home to 0.0.0.0 — telemetry can't leave the lab even if the browser # tries. Defense-in-depth behind the in-image Chromium policy/flags; in @@ -94,6 +97,8 @@ services: image: nginx:stable network_mode: host # listens on :80 -> http://${SERVER_IP}/fleet-.efi restart: unless-stopped + depends_on: + - kiosk # /kiosk/ is reverse-proxied to the kiosk SPA volumes: - type: image source: ${OCI_IMAGE}:${IMAGE_VERSION} @@ -102,13 +107,23 @@ services: subpath: artifacts # mount only /artifacts from the image # Fallback (Docker < 28 / offline): bind the local Packer output dir: # - ${ARTIFACT_SRC}:/usr/share/nginx/html:ro - # Serve the kiosk test page at /kiosk/ alongside the artifacts. Config - # adds the location; the page is lab content (NOT in the artifact image). + # Config adds the artifact root + the /kiosk/ reverse-proxy location. The + # kiosk SPA itself is the separate 'kiosk' service below (not bind-mounted). - ./http/default.conf:/etc/nginx/conf.d/default.conf:ro - - ./http/kiosk:/usr/share/nginx/kiosk:ro # For HTTPS boot (production), terminate TLS here with the boot-infra CA's # cert and switch the dhcp-boot URL above to https://. Left HTTP for the lab. + # --- Kiosk SPA (spaceship terminal shown by the diskless kiosk) ------------ + # Svelte + Tailwind app built into a static bundle served by its own nginx. + # Bridge-networked (NOT host) so it doesn't fight :80; published on loopback + # only, where the host-network 'http' nginx reverse-proxies /kiosk/ to it. + # Build: docker compose build kiosk + kiosk: + build: ./kiosk-app + restart: unless-stopped + ports: + - "127.0.0.1:${KIOSK_APP_PORT:-8088}:80" # loopback only; main nginx proxies here + # --- NTP server (serves time to the boot LAN; handed out via DHCP opt 42) --- ntp: build: ./chrony @@ -123,6 +138,23 @@ services: # lab time source. Drop SYS_TIME (and add `chronyd -x`) if the host already # runs its own NTP and you only want this to relay. + # --- Remote audit / log collector (RECEIVE side) --------------------------- + # rsyslog receiver: booted diskless hosts (whose RAM-only overlay root loses + # local logs on reboot) forward auditd via audisp-syslog + syslog here over + # TCP/514. Records are written per-source-host under + # ./audit-collector/logs//{audit,messages}.log. Host-networked so + # hosts can reach it at ${AUDIT_HOST} (= this host) on the boot LAN. + # NOTE: this is the receiver only — the IMAGE must be configured to send + # (audisp-syslog + an rsyslog forward rule targeting ${AUDIT_HOST}); see + # audit-collector/README.md. Until then the collector simply waits, idle. + audit: + build: ./audit-collector + network_mode: host # receive from booted hosts on the boot LAN (:514) + restart: unless-stopped + volumes: + - ./audit-collector/logs:/var/log/remote # durable, inspectable on host + - ./audit-collector/spool:/var/spool/rsyslog # disk-assisted queue + # --- NVMe/TCP target (serves rootfs.erofs, read-only) ---------------------- nvmeof: # Prebuilt by CI (.gitea/workflows/spdk-image.yaml) and pulled here — no diff --git a/serving/http/default.conf b/serving/http/default.conf index d72a9d9..2eef56b 100644 --- a/serving/http/default.conf +++ b/serving/http/default.conf @@ -1,12 +1,15 @@ -# Lab boot HTTP server (nginx). Replaces the stock default.conf so one server on -# :80 serves BOTH: +# Lab boot HTTP server (nginx). One server on :80 serves BOTH: # / -> the OCI release artifacts (UKI fleet-.efi, etc.), mounted # read-only from the image volume at /usr/share/nginx/html -# /kiosk/ -> the diskless kiosk test page (lab content bind-mounted at -# /usr/share/nginx/kiosk, deliberately NOT part of the artifact -# image, which stays FROM-scratch). -# A separate kiosk container was considered, but every service here uses host -# networking, so a second nginx would fight for :80; a single location is cleaner. +# /kiosk/ -> reverse-proxied to the kiosk SPA container (serving/kiosk-app), +# a Svelte spaceship-terminal app. NOT part of the artifact image +# (that stays FROM-scratch); a separate, independently built image. +# +# This nginx uses host networking (firmware must fetch the UKI), so it can't join +# the kiosk container's bridge network by service name. The kiosk container +# publishes on 127.0.0.1:8088 (compose), which the host-network nginx reaches +# over loopback. The trailing slash on proxy_pass strips the /kiosk/ prefix, so +# the SPA (built with base /kiosk/) gets requests at its own root. server { listen 80 default_server; server_name _; @@ -17,10 +20,14 @@ server { autoindex on; } - # Kiosk test page. The diskless kiosk (kiosk_url) points at - # http://boot.fleet.lab/kiosk/ — resolved to this host by the stack's dnsmasq. + # Kiosk SPA. The diskless kiosk (kiosk_url) points at + # http://boot.fleet.lab/kiosk/ — resolved to this host by the stack's dnsmasq + # and proxied to the kiosk-app container. location /kiosk/ { - alias /usr/share/nginx/kiosk/; - index index.html; + proxy_pass http://127.0.0.1:8088/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; } } diff --git a/serving/http/kiosk/index.html b/serving/http/kiosk/index.html deleted file mode 100644 index 984f25f..0000000 --- a/serving/http/kiosk/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - -Fleet Kiosk — Boot OK - - - -

▣ FLEET KIOSK ONLINE

-
diskless boot validated — weston + chromium rendering local HTTP
-
-
--:--:--
-
- served from… - page url… - user agent… - screen… -
-
boot HTTP server · replace this page or point kiosk_url elsewhere
- - - diff --git a/serving/kiosk-app/.dockerignore b/serving/kiosk-app/.dockerignore new file mode 100644 index 0000000..f8fcbf2 --- /dev/null +++ b/serving/kiosk-app/.dockerignore @@ -0,0 +1,5 @@ +node_modules +dist +.vite +.git +*.local diff --git a/serving/kiosk-app/.gitignore b/serving/kiosk-app/.gitignore new file mode 100644 index 0000000..1cf7629 --- /dev/null +++ b/serving/kiosk-app/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.vite +*.local diff --git a/serving/kiosk-app/Dockerfile b/serving/kiosk-app/Dockerfile new file mode 100644 index 0000000..1546f4b --- /dev/null +++ b/serving/kiosk-app/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:1 +# +# Two-stage build: compile the Svelte SPA with Vite, then serve the static +# bundle from a tiny nginx. The main serving-stack nginx reverse-proxies +# /kiosk/ to this container (see ../docker-compose.yml + ../http/default.conf), +# so this image only needs to serve the built assets at its own root. + +# ---- build stage ---- +FROM node:22-alpine AS build +WORKDIR /app + +# Install deps first for layer caching. package-lock.json is committed, so use +# `npm ci` for a reproducible, lockfile-exact install. +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build # -> /app/dist (base path /kiosk/) + +# ---- serve stage ---- +FROM nginx:stable-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ + CMD wget -qO- http://127.0.0.1/ >/dev/null 2>&1 || exit 1 diff --git a/serving/kiosk-app/README.md b/serving/kiosk-app/README.md new file mode 100644 index 0000000..374239f --- /dev/null +++ b/serving/kiosk-app/README.md @@ -0,0 +1,54 @@ +# Fleet Kiosk App + +A cyber sci-fi **spaceship monitoring & control terminal** — the page the +diskless kiosk (weston + Chromium) loads at boot. Replaces the old single static +`serving/http/kiosk/index.html` test page. + +Built with **Svelte 5** + **Tailwind CSS v4** + **Vite**, served as a static +bundle from its own nginx, and reverse-proxied at `/kiosk/` by the main serving +nginx (`../http/default.conf`). Fully self-contained — no CDNs or web fonts — so +it renders on the air-gapped boot LAN (the stack's dnsmasq sinkholes external +call-home). + +## Pages + +Top navbar with five views, each with dummy telemetry: + +- **BRIDGE** — overview tiles, systems status, subsystem load, event log +- **REACTOR** — core output/temp/containment, power distribution, fuel cells +- **NAVIGATION** — heading/velocity, tactical radar plot, flight plan, FTL drive +- **LIFE SUPPORT** — atmosphere mix, per-deck environment, recyclers +- **COMMS** — channels, signal bars, message traffic + +Routing is hash-based (`#/power`, `#/nav`, …) so deep links stay client-side and +the proxy needs no SPA-fallback rewrite. A shared 1 Hz clock and small +random-walk "jitter" helpers make the static data feel live. + +## Develop + +```bash +npm install +npm run dev # http://localhost:5173/kiosk/ +``` + +## Build the container + +It builds as part of the serving stack: + +```bash +cd .. # serving/ +docker compose build kiosk +docker compose up -d +# main nginx serves it at http://${SERVER_IP}/kiosk/ (kiosk_url default) +``` + +The container publishes on `127.0.0.1:${KIOSK_APP_PORT:-8088}`; the host-network +main nginx proxies `/kiosk/` to it. To point the diskless kiosk here, keep the +Packer `kiosk_url=http://boot.fleet.lab/kiosk/` default (resolved to the serving +host by the stack's dnsmasq). + +## Replacing the dummy data + +All fictional telemetry lives in [src/lib/data.js](src/lib/data.js). A future +runtime agent could swap these constants for a `fetch()` to a fleet API; the +components only read from that module. diff --git a/serving/kiosk-app/index.html b/serving/kiosk-app/index.html new file mode 100644 index 0000000..7708fed --- /dev/null +++ b/serving/kiosk-app/index.html @@ -0,0 +1,13 @@ + + + + + + + FLEET // BRIDGE TERMINAL + + +
+ + + diff --git a/serving/kiosk-app/nginx.conf b/serving/kiosk-app/nginx.conf new file mode 100644 index 0000000..6eff1a2 --- /dev/null +++ b/serving/kiosk-app/nginx.conf @@ -0,0 +1,25 @@ +# Internal nginx for the kiosk SPA container. The main serving nginx proxies +# /kiosk/ here with the prefix stripped (proxy_pass .../;), so requests arrive +# at this server rooted at "/". try_files gives the SPA fallback; routing is +# hash-based anyway, so this fallback only matters for a hard refresh on "/". +server { + listen 80 default_server; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Don't cache the entry document (so a new build is picked up immediately); + # the hashed assets under /assets/ are immutable and cached hard. + location = /index.html { + add_header Cache-Control "no-cache"; + } + + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/serving/kiosk-app/package-lock.json b/serving/kiosk-app/package-lock.json new file mode 100644 index 0000000..bae8de3 --- /dev/null +++ b/serving/kiosk-app/package-lock.json @@ -0,0 +1,2075 @@ +{ + "name": "fleet-kiosk-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fleet-kiosk-app", + "version": "0.1.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@tailwindcss/vite": "^4.0.0", + "svelte": "^5.19.0", + "tailwindcss": "^4.0.0", + "vite": "^6.0.7" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "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.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "tailwindcss": "4.3.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.11.tgz", + "integrity": "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": 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==", + "dev": 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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "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.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.56.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.3.tgz", + "integrity": "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@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.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.11", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "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/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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/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/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/serving/kiosk-app/package.json b/serving/kiosk-app/package.json new file mode 100644 index 0000000..dc12fbe --- /dev/null +++ b/serving/kiosk-app/package.json @@ -0,0 +1,19 @@ +{ + "name": "fleet-kiosk-app", + "private": true, + "version": "0.1.0", + "type": "module", + "description": "Cyber sci-fi spaceship monitoring/control terminal (Svelte + Tailwind) shown by the diskless kiosk.", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview --host" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@tailwindcss/vite": "^4.0.0", + "svelte": "^5.19.0", + "tailwindcss": "^4.0.0", + "vite": "^6.0.7" + } +} diff --git a/serving/kiosk-app/src/App.svelte b/serving/kiosk-app/src/App.svelte new file mode 100644 index 0000000..2bd3067 --- /dev/null +++ b/serving/kiosk-app/src/App.svelte @@ -0,0 +1,44 @@ + + +
+ + + +
+ +
+ +
+ ● LIVE + {hhmmss(clock.now)} +
+ {ticker}{ticker} +
+
+
diff --git a/serving/kiosk-app/src/app.css b/serving/kiosk-app/src/app.css new file mode 100644 index 0000000..8b48924 --- /dev/null +++ b/serving/kiosk-app/src/app.css @@ -0,0 +1,95 @@ +@import "tailwindcss"; + +/* Cyber sci-fi palette. Defining these as @theme tokens makes them available + as Tailwind utilities (bg-hull, text-term, border-haze, ...). Font is the + stock DejaVu Sans Mono shipped in the kiosk image — no web fonts (offline). */ +@theme { + --color-void: #040a0d; + --color-hull: #081419; + --color-hull2: #0c1f27; + --color-term: #00ffd0; + --color-haze: #4a8c84; + --color-flare: #ff2e88; + --color-warn: #ffb000; + --color-ok: #2dff8a; + --color-alert: #ff3b3b; + + --font-mono: "DejaVu Sans Mono", ui-monospace, "SFMono-Regular", "Courier New", monospace; +} + +:root { color-scheme: dark; } + +html, body, #app { height: 100%; margin: 0; padding: 0; } + +body { + background-color: var(--color-void); + color: var(--color-term); + font-family: var(--font-mono); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Faint engineering-grid backdrop + center glow + vignette. */ +#app::before { + content: ""; + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + background-image: + linear-gradient(rgba(0, 255, 208, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 255, 208, 0.035) 1px, transparent 1px), + radial-gradient(circle at 50% 22%, rgba(0, 90, 100, 0.35), transparent 65%); + background-size: 42px 42px, 42px 42px, 100% 100%; +} + +/* CRT scanline overlay sitting above content. */ +#app::after { + content: ""; + position: fixed; + inset: 0; + z-index: 60; + pointer-events: none; + background: repeating-linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 0px, + rgba(0, 0, 0, 0) 2px, + rgba(0, 0, 0, 0.16) 3px + ); + mix-blend-mode: multiply; +} + +.glow { text-shadow: 0 0 10px currentColor; } +.glow-sm { text-shadow: 0 0 6px currentColor; } + +/* Cut-corner panel edge — the recurring "console module" silhouette. */ +.cut { + clip-path: polygon( + 0 0, + calc(100% - 14px) 0, + 100% 14px, + 100% 100%, + 14px 100%, + 0 calc(100% - 14px) + ); +} + +@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } } +.blink { animation: blink 1.4s steps(2, jump-none) infinite; } + +@keyframes ticker { from { transform: translateX(100%); } to { transform: translateX(-100%); } } +.ticker { display: inline-block; white-space: nowrap; animation: ticker 32s linear infinite; } + +/* Radar sweep wedge. */ +@keyframes radar { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } +.radar-sweep { + background: conic-gradient(from 0deg, rgba(0, 255, 208, 0.28), rgba(0, 255, 208, 0) 70deg); + border-radius: 9999px; + animation: radar 4s linear infinite; +} + +/* Thin neon scrollbars to match the theme. */ +* { scrollbar-width: thin; scrollbar-color: rgba(0, 255, 208, 0.3) transparent; } +*::-webkit-scrollbar { width: 8px; height: 8px; } +*::-webkit-scrollbar-thumb { background: rgba(0, 255, 208, 0.3); } +*::-webkit-scrollbar-track { background: transparent; } diff --git a/serving/kiosk-app/src/lib/Bar.svelte b/serving/kiosk-app/src/lib/Bar.svelte new file mode 100644 index 0000000..d228458 --- /dev/null +++ b/serving/kiosk-app/src/lib/Bar.svelte @@ -0,0 +1,15 @@ + + +
+
+ {label} + {value ?? clamped}{unit} +
+
+
+
+
diff --git a/serving/kiosk-app/src/lib/Navbar.svelte b/serving/kiosk-app/src/lib/Navbar.svelte new file mode 100644 index 0000000..ce99454 --- /dev/null +++ b/serving/kiosk-app/src/lib/Navbar.svelte @@ -0,0 +1,31 @@ + + + diff --git a/serving/kiosk-app/src/lib/Panel.svelte b/serving/kiosk-app/src/lib/Panel.svelte new file mode 100644 index 0000000..9534541 --- /dev/null +++ b/serving/kiosk-app/src/lib/Panel.svelte @@ -0,0 +1,17 @@ + + +
+
+
+ ▌ +

{title}

+
+
+ {@render children?.()} +
+
diff --git a/serving/kiosk-app/src/lib/Radar.svelte b/serving/kiosk-app/src/lib/Radar.svelte new file mode 100644 index 0000000..c0fb2ba --- /dev/null +++ b/serving/kiosk-app/src/lib/Radar.svelte @@ -0,0 +1,25 @@ + + +
+
+
+
+
+
+ +
+ + +
+ + {#each blips as b} + + {/each} +
diff --git a/serving/kiosk-app/src/lib/Stat.svelte b/serving/kiosk-app/src/lib/Stat.svelte new file mode 100644 index 0000000..e4c3e73 --- /dev/null +++ b/serving/kiosk-app/src/lib/Stat.svelte @@ -0,0 +1,11 @@ + + +
+ {label} + + {value}{#if unit}{unit}{/if} + + {#if sub}{sub}{/if} +
diff --git a/serving/kiosk-app/src/lib/StatusBar.svelte b/serving/kiosk-app/src/lib/StatusBar.svelte new file mode 100644 index 0000000..9e32bb5 --- /dev/null +++ b/serving/kiosk-app/src/lib/StatusBar.svelte @@ -0,0 +1,19 @@ + + +
+
+ CLASS {ship.class} + CMD {ship.captain} +
+
+ + + CONDITION GREEN + + STARDATE {stardate(clock.now)} + MISSION TIME {hhmmss(clock.now)} +
+
diff --git a/serving/kiosk-app/src/lib/Tag.svelte b/serving/kiosk-app/src/lib/Tag.svelte new file mode 100644 index 0000000..6079e69 --- /dev/null +++ b/serving/kiosk-app/src/lib/Tag.svelte @@ -0,0 +1,16 @@ + + + + {@render children?.()} + diff --git a/serving/kiosk-app/src/lib/clock.svelte.js b/serving/kiosk-app/src/lib/clock.svelte.js new file mode 100644 index 0000000..d004af3 --- /dev/null +++ b/serving/kiosk-app/src/lib/clock.svelte.js @@ -0,0 +1,24 @@ +// Shared 1 Hz clock. Module-level $state + a single interval, so every consumer +// (status bar, footer) shares one timer. Read `clock.now` reactively. + +let _now = $state(new Date()) + +if (typeof window !== 'undefined') { + setInterval(() => { _now = new Date() }, 1000) +} + +export const clock = { + get now() { return _now }, +} + +// Playful "stardate" derived from the wall clock, purely cosmetic. +export function stardate(d) { + const epoch = Date.UTC(2350, 0, 1) + const days = (d.getTime() - epoch) / 86_400_000 + return (47000 + days / 3.65).toFixed(1) +} + +export function hhmmss(d) { + const p = (n) => String(n).padStart(2, '0') + return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}` +} diff --git a/serving/kiosk-app/src/lib/data.js b/serving/kiosk-app/src/lib/data.js new file mode 100644 index 0000000..1a29e2a --- /dev/null +++ b/serving/kiosk-app/src/lib/data.js @@ -0,0 +1,137 @@ +// Dummy data for the spaceship terminal. All static/fictional — no real +// telemetry source yet. A future runtime agent could replace these with a +// fetch() to a fleet API; the components only read from here. + +export const navItems = [ + { id: 'dashboard', label: 'BRIDGE', glyph: '◈' }, + { id: 'power', label: 'REACTOR', glyph: '⬡' }, + { id: 'nav', label: 'NAVIGATION', glyph: '✧' }, + { id: 'life', label: 'LIFE SUPPORT', glyph: '❂' }, + { id: 'comms', label: 'COMMS', glyph: '☰' }, +] + +export const ship = { + name: 'ISV AURELIAN', + registry: 'FN-774-Δ', + class: 'Lancer-class Deep Survey Cruiser', + captain: 'Cmdr. V. Okonkwo', +} + +// --- BRIDGE / dashboard ------------------------------------------------------ +export const systemStatus = [ + { name: 'Reactor Core', state: 'NOMINAL', tone: 'ok' }, + { name: 'Shield Matrix', state: 'NOMINAL', tone: 'ok' }, + { name: 'FTL Drive', state: 'CHARGING', tone: 'warn' }, + { name: 'Life Support', state: 'NOMINAL', tone: 'ok' }, + { name: 'Hull Integrity', state: '98.4%', tone: 'ok' }, + { name: 'Weapons', state: 'STANDBY', tone: 'term' }, + { name: 'Sensor Array', state: 'NOMINAL', tone: 'ok' }, + { name: 'Comms Relay', state: 'DEGRADED', tone: 'alert' }, +] + +export const subsystemLoad = [ + { label: 'Propulsion', pct: 72, tone: 'bg-term' }, + { label: 'Shields', pct: 64, tone: 'bg-term' }, + { label: 'Life Support', pct: 38, tone: 'bg-ok' }, + { label: 'Sensors', pct: 51, tone: 'bg-term' }, + { label: 'FTL Charge', pct: 61, tone: 'bg-warn' }, + { label: 'Weapons', pct: 9, tone: 'bg-haze' }, +] + +export const logEntries = [ + { t: '07:42:11', tone: 'warn', msg: 'Jump capacitor at 61% — full charge ETA 00:18:24' }, + { t: '07:39:02', tone: 'alert', msg: 'Comms relay 2 packet loss 14% — rerouting via relay 1' }, + { t: '07:31:55', tone: 'term', msg: 'Sensor sweep complete — 3 contacts logged in grid 7-K' }, + { t: '07:28:40', tone: 'ok', msg: 'Hull micro-fracture sealed on deck 4, section C' }, + { t: '07:14:09', tone: 'ok', msg: 'Reactor output stabilised at 84.2% rated' }, + { t: '06:58:21', tone: 'term', msg: 'Course correction +0.4° applied to heading 287' }, +] + +// --- REACTOR / power --------------------------------------------------------- +export const reactor = { + output: 84.2, // % rated + coreTemp: 4120, // K + containment: 99.1, // % field strength + rpm: 11840, // turbine +} + +export const powerDistribution = [ + { label: 'Propulsion Bus', pct: 72, tone: 'bg-term' }, + { label: 'Shield Emitters', pct: 64, tone: 'bg-term' }, + { label: 'FTL Capacitor', pct: 61, tone: 'bg-warn' }, + { label: 'Sensors / EW', pct: 51, tone: 'bg-term' }, + { label: 'Life Support', pct: 38, tone: 'bg-ok' }, + { label: 'Habitation', pct: 27, tone: 'bg-ok' }, + { label: 'Weapons Standby', pct: 9, tone: 'bg-haze' }, +] + +export const fuelCells = [ + { id: 'CELL-A', charge: 96, tone: 'bg-ok' }, + { id: 'CELL-B', charge: 91, tone: 'bg-ok' }, + { id: 'CELL-C', charge: 78, tone: 'bg-term' }, + { id: 'CELL-D', charge: 44, tone: 'bg-warn' }, +] + +// --- NAVIGATION -------------------------------------------------------------- +export const navState = { + heading: 287.4, // degrees + velocity: 0.21, // c + system: 'Cygnus Reach', + sector: 'Grid 7-K', + destination: 'Tarsis Station', + eta: '04:12:38', + ftlCharge: 61, + coords: { x: '+1184.22', y: '-0473.90', z: '+0061.07' }, +} + +export const waypoints = [ + { name: 'Tarsis Station', dist: '2.4 AU', status: 'LOCKED', tone: 'ok' }, + { name: 'Helex Belt', dist: '0.8 AU', status: 'PASSING', tone: 'warn' }, + { name: 'Relay Buoy 19', dist: '11.6 AU', status: 'QUEUED', tone: 'term' }, + { name: 'Cygnus Gate', dist: '34.0 AU', status: 'QUEUED', tone: 'term' }, +] + +// blips positioned in % within the radar circle (50/50 = center = own ship) +export const contacts = [ + { x: 64, y: 38, tone: 'bg-term', label: 'Freighter "Coriol"' }, + { x: 41, y: 27, tone: 'bg-warn', label: 'Unknown — passive' }, + { x: 72, y: 66, tone: 'bg-ok', label: 'Tarsis Station' }, + { x: 30, y: 58, tone: 'bg-flare', label: 'Debris field' }, +] + +// --- LIFE SUPPORT ------------------------------------------------------------ +export const atmosphere = [ + { label: 'Oxygen', value: 20.9, unit: '%', pct: 87, tone: 'bg-ok' }, + { label: 'CO₂', value: 0.04, unit: '%', pct: 12, tone: 'bg-ok' }, + { label: 'Cabin Press.', value: 101.1, unit: 'kPa', pct: 92, tone: 'bg-ok' }, + { label: 'Temperature', value: 21.4, unit: '°C', pct: 71, tone: 'bg-term' }, + { label: 'Humidity', value: 44, unit: '%', pct: 44, tone: 'bg-term' }, + { label: 'Water Reserve', value: 68, unit: '%', pct: 68, tone: 'bg-term' }, +] + +export const decks = [ + { deck: 'Deck 1 — Bridge', o2: 'OK', temp: '21.0°C', tone: 'ok' }, + { deck: 'Deck 2 — Habitation', o2: 'OK', temp: '22.3°C', tone: 'ok' }, + { deck: 'Deck 3 — Med / Sci', o2: 'OK', temp: '20.6°C', tone: 'ok' }, + { deck: 'Deck 4 — Engineering', o2: 'LOW', temp: '26.8°C', tone: 'warn' }, + { deck: 'Deck 5 — Cargo', o2: 'SEALED', temp: '8.1°C', tone: 'term' }, +] + +export const crew = { total: 84, onDuty: 31, medical: 2, cryo: 12 } + +// --- COMMS ------------------------------------------------------------------- +export const channels = [ + { name: 'Fleet Command', band: 'Q-band', status: 'ENCRYPTED', tone: 'ok' }, + { name: 'Tarsis Control', band: 'S-band', status: 'OPEN', tone: 'term' }, + { name: 'Relay Buoy 19', band: 'X-band', status: 'WEAK', tone: 'warn' }, + { name: 'Distress Guard', band: '121.5', status: 'MONITOR', tone: 'term' }, + { name: 'Internal Net', band: 'Hardline', status: 'NOMINAL', tone: 'ok' }, +] + +export const messages = [ + { t: '07:41', from: 'FLEET CMD', tone: 'ok', text: 'Patrol pattern delta authorised. Maintain EMCON.' }, + { t: '07:33', from: 'TARSIS CTL', tone: 'term', text: 'Docking bay 4 reserved. Transmit manifest on approach.' }, + { t: '07:19', from: 'RELAY-19', tone: 'warn', text: 'Signal degradation detected. Recommend X-band switch.' }, + { t: '06:52', from: 'CORIOL', tone: 'term', text: 'Thanks for the escort, Aurelian. Safe travels.' }, + { t: '06:40', from: 'FLEET CMD', tone: 'flare', text: 'Priority weather: ion storm forming in grid 9-B.' }, +] diff --git a/serving/kiosk-app/src/lib/live.svelte.js b/serving/kiosk-app/src/lib/live.svelte.js new file mode 100644 index 0000000..5b39354 --- /dev/null +++ b/serving/kiosk-app/src/lib/live.svelte.js @@ -0,0 +1,17 @@ +// Reactive "telemetry" helper: returns an object whose .value drifts around a +// base value on an interval, so static dummy data feels like a live readout. +// Call inside a component's script (it registers an $effect for cleanup). + +export function useJitter(base, { amp = 1, decimals = 0, every = 2000 } = {}) { + let value = $state(base) + + $effect(() => { + const id = setInterval(() => { + const delta = (Math.random() - 0.5) * 2 * amp + value = Number((base + delta).toFixed(decimals)) + }, every) + return () => clearInterval(id) + }) + + return { get value() { return value } } +} diff --git a/serving/kiosk-app/src/lib/router.svelte.js b/serving/kiosk-app/src/lib/router.svelte.js new file mode 100644 index 0000000..ca9dcef --- /dev/null +++ b/serving/kiosk-app/src/lib/router.svelte.js @@ -0,0 +1,20 @@ +// Tiny hash-based router using a module-level rune. Hash routing keeps deep +// links client-side (they never reach nginx), so the reverse proxy needs no +// SPA-fallback rewrite. `nav.current` is reactive: read it in markup/$derived. + +function parse() { + if (typeof window === 'undefined') return 'dashboard' + const raw = window.location.hash.replace(/^#\/?/, '') + return raw.split(/[/?]/)[0] || 'dashboard' +} + +let _route = $state(parse()) + +if (typeof window !== 'undefined') { + window.addEventListener('hashchange', () => { _route = parse() }) + if (!window.location.hash) window.location.hash = '#/dashboard' +} + +export const nav = { + get current() { return _route }, +} diff --git a/serving/kiosk-app/src/main.js b/serving/kiosk-app/src/main.js new file mode 100644 index 0000000..32b6c67 --- /dev/null +++ b/serving/kiosk-app/src/main.js @@ -0,0 +1,7 @@ +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' + +const app = mount(App, { target: document.getElementById('app') }) + +export default app diff --git a/serving/kiosk-app/src/pages/Comms.svelte b/serving/kiosk-app/src/pages/Comms.svelte new file mode 100644 index 0000000..d007a5a --- /dev/null +++ b/serving/kiosk-app/src/pages/Comms.svelte @@ -0,0 +1,62 @@ + + +
+
+ + + + +
+ +
+ + + + + + + + + + + {#each channels as c} + + + + + + {/each} + +
ChannelBandStatus
{c.name}{c.band}{c.status}
+
+ + +
+
+ + +
    + {#each messages as m} +
  • +
    + {m.from} + {m.t} +
    +

    {m.text}

    +
  • + {/each} +
+
+
+
diff --git a/serving/kiosk-app/src/pages/Dashboard.svelte b/serving/kiosk-app/src/pages/Dashboard.svelte new file mode 100644 index 0000000..9b9159d --- /dev/null +++ b/serving/kiosk-app/src/pages/Dashboard.svelte @@ -0,0 +1,61 @@ + + +
+
+ + + + +
+ +
+ +
    + {#each systemStatus as s} +
  • + {s.name} + {s.state} +
  • + {/each} +
+
+ + +
+ {#each subsystemLoad as b} + + {/each} +
+
+ + +
    + {#each logEntries as e} +
  • + {e.t} + ▍ + {e.msg} +
  • + {/each} +
+
+
+
diff --git a/serving/kiosk-app/src/pages/LifeSupport.svelte b/serving/kiosk-app/src/pages/LifeSupport.svelte new file mode 100644 index 0000000..b397e8f --- /dev/null +++ b/serving/kiosk-app/src/pages/LifeSupport.svelte @@ -0,0 +1,60 @@ + + +
+
+ + + + +
+ +
+ +
+ {#each atmosphere as a} + + {/each} +
+
+ + + + + + + + + + + + {#each decks as d} + + + + + + {/each} + +
DeckO₂Temp
{d.deck}{d.o2}{d.temp}
+
+ + +
+ + + + +
+ Emergency O₂ + 72 hrs +
+
+
+
+
diff --git a/serving/kiosk-app/src/pages/Navigation.svelte b/serving/kiosk-app/src/pages/Navigation.svelte new file mode 100644 index 0000000..5322873 --- /dev/null +++ b/serving/kiosk-app/src/pages/Navigation.svelte @@ -0,0 +1,80 @@ + + +
+
+ + + + +
+ +
+ + +
+ {#each contacts as c} +
+ + {c.label} +
+ {/each} +
+
+ + + + + + + + + + + + {#each waypoints as w} + + + + + + {/each} + +
WaypointRangeStatus
{w.name}{w.dist}{w.status}
+
+ + +
+ +
+
+
X
+
{navState.coords.x}
+
+
+
Y
+
{navState.coords.y}
+
+
+
Z
+
{navState.coords.z}
+
+
+
+ Jump readiness + CHARGING +
+
+
+
+
diff --git a/serving/kiosk-app/src/pages/Power.svelte b/serving/kiosk-app/src/pages/Power.svelte new file mode 100644 index 0000000..9654b20 --- /dev/null +++ b/serving/kiosk-app/src/pages/Power.svelte @@ -0,0 +1,62 @@ + + +
+
+ + + + +
+ +
+ +
+ {#each powerDistribution as p} + + {/each} +
+
+ + +
+ {#each fuelCells as c} + + {/each} +
+ Deuterium reserve + 77% / 14 days +
+
+
+ + +
+ {#each [ + { l: 'Auto-throttle', v: 'ENGAGED', t: 'ok' }, + { l: 'SCRAM arming', v: 'SAFE', t: 'term' }, + { l: 'Coolant loop A', v: 'FLOW 100%', t: 'ok' }, + { l: 'Coolant loop B', v: 'STANDBY', t: 'term' }, + { l: 'Plasma injectors', v: '6 / 6 ONLINE', t: 'ok' }, + { l: 'Overload damper', v: 'ARMED', t: 'warn' }, + ] as r} +
+ {r.l} + {r.v} +
+ {/each} +
+
+
+
diff --git a/serving/kiosk-app/svelte.config.js b/serving/kiosk-app/svelte.config.js new file mode 100644 index 0000000..8abe436 --- /dev/null +++ b/serving/kiosk-app/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/serving/kiosk-app/vite.config.js b/serving/kiosk-app/vite.config.js new file mode 100644 index 0000000..c87d60b --- /dev/null +++ b/serving/kiosk-app/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import tailwindcss from '@tailwindcss/vite' + +// The kiosk loads the app at http://boot.fleet.lab/kiosk/ (the main serving +// nginx reverse-proxies /kiosk/ -> this container). base must match that path +// so emitted asset URLs are absolute under /kiosk/. Hash-based routing is used +// (see src/lib/router.svelte.js) so deep links never hit the server — no SPA +// fallback rewrite is needed through the proxy. +export default defineConfig({ + base: '/kiosk/', + plugins: [svelte(), tailwindcss()], + build: { + target: 'es2022', + // Fully self-contained bundle (no CDN/fonts) so it renders on the + // air-gapped boot LAN — the dnsmasq sinkhole blocks external call-home. + assetsInlineLimit: 4096, + }, +})