Jacob Schmidt bdc1e36e63 Implement interactive garage UI with service-based client bridge
- replace placeholder garage interaction with real UI open flow
- add catalog/session/UI bridge services for hydrate, sync, store, and retrieve actions
- migrate garage web UI bundle to new app shell/runtime structure
- align org/store function naming with shared init and UI bridge patterns
2026-03-14 03:06:18 -05:00

1292 lines
44 KiB
JavaScript

/* Generated by tools/build-webui.mjs for Garage UI app. Do not edit directly. */
(function () {
const runtime = window.ForgeWebUI;
const GarageApp = (window.GarageApp = window.GarageApp || {});
GarageApp.runtime = runtime;
window.AppRuntime = runtime;
})();
(function () {
const GarageApp = (window.GarageApp = window.GarageApp || {});
const defaultSession = {
garageName: "Vehicle Garage",
capacityUsed: 0,
capacityMax: 5,
nearbyCount: 0,
spawnBlocked: false,
spawnStatus: "Ready",
};
const defaultGarage = {
vehicles: [],
};
const defaultNearby = {
vehicles: [],
};
function cloneValue(value) {
return JSON.parse(JSON.stringify(value));
}
function replaceObject(target, source) {
Object.keys(target).forEach((key) => delete target[key]);
Object.assign(target, cloneValue(source));
}
GarageApp.data = {
categories: [
{ id: "all", label: "All" },
{ id: "car", label: "Cars" },
{ id: "armor", label: "Armor" },
{ id: "air", label: "Air" },
{ id: "naval", label: "Naval" },
{ id: "other", label: "Other" },
],
session: Object.assign({}, defaultSession),
garage: Object.assign({}, defaultGarage),
nearby: Object.assign({}, defaultNearby),
applyHydratePayload(payload) {
replaceObject(
this.session,
Object.assign({}, defaultSession, payload?.session || {}),
);
replaceObject(
this.garage,
Object.assign({}, defaultGarage, payload?.garage || {}),
);
replaceObject(
this.nearby,
Object.assign({}, defaultNearby, payload?.nearby || {}),
);
},
};
})();
(function () {
const GarageApp = (window.GarageApp = window.GarageApp || {});
const { createSignal } = GarageApp.runtime;
class GarageStore {
constructor() {
[this.getSelectedKind, this.setSelectedKind] = createSignal("");
[this.getSelectedId, this.setSelectedId] = createSignal("");
[this.getSearchQuery, this.setSearchQuery] = createSignal("");
[this.getCategoryFilter, this.setCategoryFilter] =
createSignal("all");
[this.getPendingAction, this.setPendingAction] = createSignal("");
[this.getNotice, this.setNotice] = createSignal({
type: "",
text: "",
});
}
getSelection() {
return {
id: this.getSelectedId(),
kind: this.getSelectedKind(),
};
}
clearSelection() {
this.setSelectedKind("");
this.setSelectedId("");
}
select(kind, id) {
this.setSelectedKind(String(kind || ""));
this.setSelectedId(String(id || ""));
}
startAction(action) {
this.setPendingAction(String(action || ""));
}
finishAction() {
this.setPendingAction("");
}
matchesSelection(entry) {
if (!entry || typeof entry !== "object") {
return false;
}
const selection = this.getSelection();
if (!selection.kind || !selection.id) {
return false;
}
if (selection.kind === "stored") {
return (
entry.entryKind === "stored" &&
String(entry.plate || "") === selection.id
);
}
if (selection.kind === "nearby") {
return (
entry.entryKind === "nearby" &&
String(entry.netId || "") === selection.id
);
}
return false;
}
ensureSelection() {
const garageVehicles = Array.isArray(
GarageApp.data?.garage?.vehicles,
)
? GarageApp.data.garage.vehicles
: [];
const nearbyVehicles = Array.isArray(
GarageApp.data?.nearby?.vehicles,
)
? GarageApp.data.nearby.vehicles
: [];
const hasCurrentSelection = [
...garageVehicles,
...nearbyVehicles,
].some((entry) => this.matchesSelection(entry));
if (hasCurrentSelection) {
return;
}
const firstStored = garageVehicles[0] || null;
if (firstStored) {
this.select("stored", firstStored.plate || "");
return;
}
const firstNearby = nearbyVehicles[0] || null;
if (firstNearby) {
this.select("nearby", firstNearby.netId || "");
return;
}
this.clearSelection();
}
hydrateFromPayload() {
this.finishAction();
this.ensureSelection();
}
}
GarageApp.store = new GarageStore();
})();
(function () {
const GarageApp = (window.GarageApp = window.GarageApp || {});
const store = GarageApp.store;
const bridge = window.ForgeWebUI.createBridge({
closeEvent: "garage::close",
globalName: "ForgeBridge",
readyEvent: "garage::ready",
});
function requestClose() {
return bridge.close({});
}
function requestRefresh() {
return bridge.send("garage::refresh", {});
}
function requestRetrieve(payload) {
return bridge.send("garage::vehicle::retrieve::request", payload);
}
function requestStore(payload) {
return bridge.send("garage::vehicle::store::request", payload);
}
function notifyReady() {
return bridge.ready({ loaded: true });
}
function hydrate(payloadData) {
GarageApp.data.applyHydratePayload(payloadData);
store.hydrateFromPayload(payloadData);
}
bridge.on("garage::hydrate", hydrate);
bridge.on("garage::sync", hydrate);
bridge.on("garage::retrieve::success", (payloadData) => {
store.finishAction();
if (GarageApp.actions) {
GarageApp.actions.showNotice(
"success",
payloadData.message || "Vehicle retrieved from the garage.",
);
}
});
bridge.on("garage::retrieve::failure", (payloadData) => {
store.finishAction();
if (GarageApp.actions) {
GarageApp.actions.showNotice(
"error",
payloadData.message || "Unable to retrieve vehicle.",
);
}
});
bridge.on("garage::store::success", (payloadData) => {
store.finishAction();
if (GarageApp.actions) {
GarageApp.actions.showNotice(
"success",
payloadData.message || "Vehicle stored in the garage.",
);
}
});
bridge.on("garage::store::failure", (payloadData) => {
store.finishAction();
if (GarageApp.actions) {
GarageApp.actions.showNotice(
"error",
payloadData.message || "Unable to store vehicle.",
);
}
});
GarageApp.bridge = {
notifyReady,
receive: bridge.receive,
requestClose,
requestRefresh,
requestRetrieve,
requestStore,
sendEvent: bridge.send,
};
})();
(function () {
const GarageApp = (window.GarageApp = window.GarageApp || {});
const store = GarageApp.store;
let noticeTimer = null;
function getStoredVehicles() {
return Array.isArray(GarageApp.data?.garage?.vehicles)
? GarageApp.data.garage.vehicles
: [];
}
function getNearbyVehicles() {
return Array.isArray(GarageApp.data?.nearby?.vehicles)
? GarageApp.data.nearby.vehicles
: [];
}
function getSelectedEntry() {
const selection = store.getSelection();
if (selection.kind === "stored") {
return (
getStoredVehicles().find(
(vehicle) => String(vehicle.plate || "") === selection.id,
) || null
);
}
if (selection.kind === "nearby") {
return (
getNearbyVehicles().find(
(vehicle) => String(vehicle.netId || "") === selection.id,
) || null
);
}
return null;
}
function showNotice(type, text) {
store.setNotice({ type, text });
if (noticeTimer) {
clearTimeout(noticeTimer);
}
noticeTimer = setTimeout(() => {
store.setNotice({ type: "", text: "" });
noticeTimer = null;
}, 3200);
}
function closeGarage() {
const bridge = GarageApp.bridge;
if (bridge && typeof bridge.requestClose === "function") {
const sent = bridge.requestClose();
if (sent) {
return true;
}
}
showNotice("error", "Garage bridge is unavailable.");
return false;
}
function refreshGarage() {
const bridge = GarageApp.bridge;
if (bridge && typeof bridge.requestRefresh === "function") {
const sent = bridge.requestRefresh();
if (sent) {
return true;
}
}
showNotice("error", "Garage refresh bridge is unavailable.");
return false;
}
function applySearchQuery(value) {
store.setSearchQuery(String(value || "").trim());
}
function clearSearch() {
store.setSearchQuery("");
}
function selectCategory(categoryId) {
store.setCategoryFilter(String(categoryId || "all").trim() || "all");
}
function selectEntry(kind, id) {
store.select(kind, id);
}
function requestRetrieveSelected() {
const selectedEntry = getSelectedEntry();
if (!selectedEntry || selectedEntry.entryKind !== "stored") {
showNotice("error", "Select a stored vehicle to retrieve.");
return false;
}
if (GarageApp.data?.session?.spawnBlocked) {
showNotice("error", "The garage spawn area is blocked.");
return false;
}
const bridge = GarageApp.bridge;
if (!bridge || typeof bridge.requestRetrieve !== "function") {
showNotice("error", "Garage retrieve bridge is unavailable.");
return false;
}
store.startAction("retrieve");
const sent = bridge.requestRetrieve({
plate: selectedEntry.plate || "",
});
if (!sent) {
store.finishAction();
showNotice("error", "Garage retrieve bridge is unavailable.");
return false;
}
return true;
}
function requestStoreSelected() {
const selectedEntry = getSelectedEntry();
if (!selectedEntry || selectedEntry.entryKind !== "nearby") {
showNotice("error", "Select a nearby vehicle to store.");
return false;
}
if (selectedEntry.isEmpty === false) {
showNotice(
"error",
"All crew must exit the vehicle before storing it.",
);
return false;
}
const bridge = GarageApp.bridge;
if (!bridge || typeof bridge.requestStore !== "function") {
showNotice("error", "Garage store bridge is unavailable.");
return false;
}
store.startAction("store");
const sent = bridge.requestStore({
netId: selectedEntry.netId || "",
});
if (!sent) {
store.finishAction();
showNotice("error", "Garage store bridge is unavailable.");
return false;
}
return true;
}
GarageApp.actions = {
showNotice,
closeGarage,
refreshGarage,
applySearchQuery,
clearSearch,
selectCategory,
selectEntry,
getSelectedEntry,
requestRetrieveSelected,
requestStoreSelected,
};
})();
(function () {
const GarageApp = (window.GarageApp = window.GarageApp || {});
const { h } = GarageApp.runtime;
const WindowTitleBar = window.SharedUI.componentFns.WindowTitleBar;
const store = GarageApp.store;
const actions = GarageApp.actions;
const { categories, garage, nearby, session } = GarageApp.data;
function q(query, values) {
const needle = String(query || "")
.trim()
.toLowerCase();
if (!needle) {
return true;
}
return values.some((value) =>
String(value || "")
.toLowerCase()
.includes(needle),
);
}
function pct(value) {
return Math.max(0, Math.min(100, Math.round(Number(value || 0) * 100)));
}
function categoryLabel(category) {
const match = categories.find(
(entry) => entry.id === String(category || "other").toLowerCase(),
);
return match ? match.label : "Other";
}
function distanceLabel(value) {
return `${Math.round(Number(value || 0))} m`;
}
function plateLabel(value) {
return String(value || "").trim() || "Untracked";
}
function statusLabel(vehicle) {
if (!vehicle) {
return "-";
}
if (vehicle.entryKind === "stored") {
return "Stored";
}
return vehicle.isEmpty === false ? "Crewed" : "Ready";
}
function normalizeHitPointLabel(value) {
return String(value || "")
.replace(/^Hit/i, "")
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/_/g, " ")
.trim();
}
function sameEntry(left, right) {
if (!left || !right) {
return false;
}
return (
String(left.entryKind || "") === String(right.entryKind || "") &&
String(left.plate || "") === String(right.plate || "") &&
String(left.netId || "") === String(right.netId || "")
);
}
function selectedEntry(state) {
if (state.selectedKind === "stored") {
return (
(garage.vehicles || []).find(
(vehicle) =>
String(vehicle.plate || "") === state.selectedId,
) || null
);
}
if (state.selectedKind === "nearby") {
return (
(nearby.vehicles || []).find(
(vehicle) =>
String(vehicle.netId || "") === state.selectedId,
) || null
);
}
return null;
}
function visibleVehicles(vehicles, state) {
return (vehicles || []).filter((vehicle) => {
if (
state.categoryFilter !== "all" &&
String(vehicle.category || "").toLowerCase() !==
state.categoryFilter
) {
return false;
}
return q(state.searchQuery, [
vehicle.displayName,
vehicle.classname,
vehicle.plate,
vehicle.netId,
vehicle.category,
]);
});
}
function stat(label, value, tone = "") {
return h(
"div",
{
className: tone
? `garage-stat-card is-${tone}`
: "garage-stat-card",
},
h("span", { className: "garage-stat-label" }, label),
h("span", { className: "garage-stat-value" }, value),
);
}
function meter(label, percent, tone) {
return h(
"div",
{ className: "garage-meter" },
h(
"div",
{ className: "garage-meter-label-row" },
h("span", { className: "garage-meter-label" }, label),
h("span", { className: "garage-meter-value" }, `${percent}%`),
),
h(
"div",
{ className: "garage-meter-track" },
h("span", {
className: `garage-meter-fill is-${tone}`,
style: { width: `${percent}%` },
}),
),
);
}
function vehicleItem(vehicle, currentSelection) {
const id =
vehicle.entryKind === "stored"
? String(vehicle.plate || "")
: String(vehicle.netId || "");
const isNearby = vehicle.entryKind === "nearby";
return h(
"button",
{
type: "button",
className: sameEntry(vehicle, currentSelection)
? "garage-vehicle-item is-selected"
: "garage-vehicle-item",
onClick: () => actions.selectEntry(vehicle.entryKind, id),
},
h(
"div",
{ className: "garage-vehicle-item-head" },
h(
"div",
{ className: "garage-vehicle-copy" },
h(
"span",
{ className: "garage-vehicle-title" },
vehicle.displayName || vehicle.classname || "Vehicle",
),
h(
"span",
{ className: "garage-vehicle-meta" },
isNearby
? `Nearby ${distanceLabel(vehicle.distance)}`
: `Plate ${plateLabel(vehicle.plate)}`,
),
),
h(
"span",
{
className:
isNearby && vehicle.isEmpty === false
? "garage-badge is-warning"
: "garage-badge",
},
isNearby
? vehicle.isEmpty === false
? "Crewed"
: "Empty"
: categoryLabel(vehicle.category),
),
),
h(
"div",
{ className: "garage-inline-meters" },
meter("Health", pct(vehicle.health), "health"),
meter("Fuel", pct(vehicle.fuel), "fuel"),
),
);
}
function vehicleList(title, eyebrow, scrollId, vehicles, currentSelection) {
return h(
"section",
{ className: "garage-card garage-list-card" },
h(
"div",
{ className: "garage-card-header" },
h(
"div",
null,
h("span", { className: "garage-eyebrow" }, eyebrow),
h("h2", { className: "garage-section-title" }, title),
),
h(
"span",
{ className: "garage-pill" },
`${vehicles.length} ${vehicles.length === 1 ? "Vehicle" : "Vehicles"}`,
),
),
h(
"div",
{
className: "garage-card-body garage-scroll-body",
"data-preserve-scroll-id": scrollId,
},
vehicles.length > 0
? vehicles.map((vehicle) =>
vehicleItem(vehicle, currentSelection),
)
: h(
"div",
{ className: "garage-empty-state" },
h(
"h3",
{ className: "garage-empty-title" },
"No matching vehicles",
),
h(
"p",
{ className: "garage-empty-copy" },
"Adjust the current search or category filter to view more records.",
),
),
),
);
}
function hitPointRows(hitPoints) {
const rows = (Array.isArray(hitPoints) ? hitPoints : [])
.slice()
.sort(
(left, right) =>
Number(right.value || 0) - Number(left.value || 0),
)
.slice(0, 6)
.filter((row) => Number(row.value || 0) > 0);
if (rows.length === 0) {
return h(
"div",
{ className: "garage-empty-inline" },
"No subsystem damage reported.",
);
}
return h(
"div",
{ className: "garage-hitpoint-grid" },
rows.map((row) =>
h(
"div",
{ className: "garage-hitpoint-row" },
h(
"div",
{ className: "garage-hitpoint-copy" },
h(
"span",
{ className: "garage-hitpoint-name" },
normalizeHitPointLabel(row.name) || "Subsystem",
),
row.selection
? h(
"span",
{ className: "garage-hitpoint-selection" },
row.selection,
)
: null,
),
h(
"span",
{ className: "garage-hitpoint-value" },
`${Math.round(Number(row.value || 0) * 100)}%`,
),
),
),
);
}
function detailPanel(currentSelection, state) {
if (!currentSelection) {
return h(
"section",
{ className: "garage-card garage-detail-card" },
h(
"div",
{ className: "garage-card-header" },
h(
"div",
null,
h("span", { className: "garage-eyebrow" }, "Selection"),
h(
"h2",
{ className: "garage-section-title" },
"Vehicle Detail",
),
),
),
h(
"div",
{ className: "garage-card-body garage-detail-empty" },
h(
"h3",
{ className: "garage-empty-title" },
"Select a vehicle",
),
h(
"p",
{ className: "garage-empty-copy" },
"Choose a stored record to retrieve or a nearby vehicle to store.",
),
),
);
}
const isStored = currentSelection.entryKind === "stored";
const pendingAction = String(state.pendingAction || "");
const isBusy =
pendingAction === "retrieve" || pendingAction === "store";
const canRetrieve = isStored && !session.spawnBlocked && !isBusy;
const canStore =
!isStored && currentSelection.isEmpty !== false && !isBusy;
return h(
"section",
{ className: "garage-card garage-detail-card" },
h(
"div",
{ className: "garage-card-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
isStored ? "Stored Record" : "Nearby Vehicle",
),
h(
"h2",
{ className: "garage-section-title" },
currentSelection.displayName ||
currentSelection.classname ||
"Vehicle",
),
),
h(
"span",
{
className:
currentSelection.entryKind === "nearby" &&
currentSelection.isEmpty === false
? "garage-badge is-warning"
: "garage-badge",
},
isStored
? `Plate ${plateLabel(currentSelection.plate)}`
: currentSelection.isEmpty === false
? "Crewed"
: "Ready",
),
),
h(
"div",
{ className: "garage-card-body garage-detail-body" },
h(
"div",
{ className: "garage-detail-grid" },
h(
"div",
{ className: "garage-detail-copy" },
h(
"div",
{ className: "garage-detail-meta" },
stat(
"Category",
categoryLabel(currentSelection.category),
),
stat(
"Status",
statusLabel(currentSelection),
currentSelection.entryKind === "nearby" &&
currentSelection.isEmpty === false
? "danger"
: "",
),
stat(
isStored ? "Record" : "Distance",
isStored
? plateLabel(currentSelection.plate)
: distanceLabel(currentSelection.distance),
isStored ? "" : "accent",
),
),
h(
"div",
{ className: "garage-meter-stack" },
meter(
"Health",
pct(currentSelection.health),
"health",
),
meter("Fuel", pct(currentSelection.fuel), "fuel"),
),
h(
"div",
{ className: "garage-action-row" },
isStored
? h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-primary",
disabled: !canRetrieve,
onClick: () =>
actions.requestRetrieveSelected(),
},
pendingAction === "retrieve"
? "Retrieving..."
: "Retrieve Vehicle",
)
: h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-primary",
disabled: !canStore,
onClick: () =>
actions.requestStoreSelected(),
},
pendingAction === "store"
? "Storing..."
: "Store Vehicle",
),
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-secondary",
disabled: isBusy,
onClick: () => actions.refreshGarage(),
},
"Refresh",
),
),
h(
"p",
{ className: "garage-detail-note" },
isStored
? session.spawnBlocked
? "The garage spawn lane is currently blocked."
: "Retrieve this stored vehicle into the active spawn lane."
: currentSelection.isEmpty === false
? "Only empty nearby vehicles can be stored."
: "Store this nearby vehicle back into persistent garage storage.",
),
),
h(
"div",
{ className: "garage-detail-subsystems" },
h(
"div",
{ className: "garage-subsystem-header" },
h(
"span",
{ className: "garage-eyebrow" },
"Subsystems",
),
h(
"span",
{ className: "garage-detail-caption" },
"Highest damage first",
),
),
hitPointRows(currentSelection.hitPoints),
),
),
),
);
}
GarageApp.components = GarageApp.components || {};
GarageApp.components.App = function App() {
const state = {
categoryFilter: store.getCategoryFilter(),
notice: store.getNotice(),
pendingAction: store.getPendingAction(),
searchQuery: store.getSearchQuery(),
selectedId: store.getSelectedId(),
selectedKind: store.getSelectedKind(),
};
const currentSelection = selectedEntry(state);
const storedVehicles = visibleVehicles(garage.vehicles || [], state);
const nearbyVehicles = visibleVehicles(nearby.vehicles || [], state);
const searchLabel = state.searchQuery
? `Search: ${state.searchQuery}`
: "Live";
return h(
"div",
{ className: "garage-shell" },
WindowTitleBar({
kicker: "FORGE Logistics",
title: "Vehicle Garage",
onClose: () => actions.closeGarage(),
closeLabel: "Close garage interface",
}),
state.notice.text
? h(
"div",
{ className: "garage-toast-stack" },
h(
"div",
{
className:
state.notice.type === "error"
? "garage-toast is-error"
: "garage-toast is-success",
},
state.notice.text,
),
)
: null,
h(
"div",
{ className: "garage-layout" },
h(
"aside",
{ className: "garage-sidebar" },
h(
"section",
{ className: "garage-module" },
h(
"div",
{ className: "garage-module-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Search",
),
h(
"h2",
{ className: "garage-section-title" },
"Vehicle Records",
),
),
h(
"span",
{ className: "garage-pill" },
searchLabel,
),
),
h(
"div",
{ className: "garage-search-form" },
h("input", {
id: "garage-search-input",
type: "text",
className: "garage-search-input",
placeholder:
"Search by name, plate, or category",
value: state.searchQuery,
}),
h(
"div",
{ className: "garage-search-actions" },
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-primary",
onClick: () =>
actions.applySearchQuery(
document.getElementById(
"garage-search-input",
)?.value || "",
),
},
"Apply Search",
),
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-secondary",
onClick: () => actions.clearSearch(),
},
"Clear",
),
),
),
),
h(
"section",
{ className: "garage-module" },
h(
"div",
{ className: "garage-module-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Filter",
),
h(
"h2",
{ className: "garage-section-title" },
"Vehicle Categories",
),
),
),
h(
"div",
{ className: "garage-category-grid" },
categories.map((category) =>
h(
"button",
{
type: "button",
className:
state.categoryFilter === category.id
? "garage-chip is-active"
: "garage-chip",
onClick: () =>
actions.selectCategory(category.id),
},
category.label,
),
),
),
),
h(
"section",
{ className: "garage-module" },
h(
"div",
{ className: "garage-module-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Status",
),
h(
"h2",
{ className: "garage-section-title" },
"Garage Summary",
),
),
h(
"button",
{
type: "button",
className:
"garage-btn garage-btn-secondary",
disabled: Boolean(state.pendingAction),
onClick: () => actions.refreshGarage(),
},
"Refresh",
),
),
h(
"div",
{ className: "garage-summary-grid" },
stat(
"Stored",
`${session.capacityUsed}/${session.capacityMax}`,
),
stat("Nearby", session.nearbyCount, "accent"),
stat(
"Spawn Lane",
session.spawnStatus,
session.spawnBlocked ? "danger" : "",
),
),
),
),
h(
"main",
{ className: "garage-main" },
h(
"section",
{ className: "garage-panel" },
h(
"div",
{ className: "garage-panel-header" },
h(
"div",
null,
h(
"span",
{ className: "garage-eyebrow" },
"Operations Bay",
),
h(
"h1",
{ className: "garage-title" },
session.garageName || "Vehicle Garage",
),
),
h(
"span",
{ className: "garage-pill" },
`${session.capacityUsed}/${session.capacityMax} Stored`,
),
),
h(
"div",
{ className: "garage-panel-intro" },
h(
"p",
{ className: "garage-copy" },
"Retrieve stored vehicles into the active spawn lane or store nearby empty vehicles back into persistent ownership records.",
),
),
h(
"div",
{ className: "garage-dashboard" },
vehicleList(
"Stored Vehicles",
"Persistent Records",
"garage-stored-list",
storedVehicles,
currentSelection,
),
vehicleList(
"Nearby Vehicles",
"Store Window",
"garage-nearby-list",
nearbyVehicles,
currentSelection,
),
detailPanel(currentSelection, state),
),
),
),
),
h(
"footer",
{ className: "garage-footer" },
h(
"div",
{ className: "garage-footer-block" },
h(
"span",
{ className: "garage-footer-title" },
"Storage Capacity",
),
h(
"span",
{ className: "garage-footer-copy" },
`${session.capacityUsed} of ${session.capacityMax} vehicle slot(s) are currently occupied.`,
),
),
h(
"div",
{ className: "garage-footer-block" },
h(
"span",
{ className: "garage-footer-title" },
"Retrieval Window",
),
h(
"span",
{ className: "garage-footer-copy" },
session.spawnBlocked
? "Spawn lane is blocked. Clear the bay before retrieving another vehicle."
: "Spawn lane is clear. Stored vehicles can be retrieved immediately.",
),
),
h(
"div",
{ className: "garage-footer-block" },
h(
"span",
{ className: "garage-footer-title" },
"Store Rules",
),
h(
"span",
{ className: "garage-footer-copy" },
"Only nearby empty vehicles can be stored. Nearby count updates from the live world state.",
),
),
),
);
};
})();
(function () {
const ForgeWebUI = window.ForgeWebUI;
const GarageApp = window.GarageApp;
const app = ForgeWebUI.createApp({
name: "garage",
root: "#app",
setup({ root }) {
ForgeWebUI.mount(root, () => GarageApp.components.App(), {
preserveScroll: true,
});
if (GarageApp.bridge) {
GarageApp.bridge.notifyReady();
}
},
});
app.start();
})();