Embed main menu panels in RTS shell

This commit is contained in:
Meta Vens 2026-07-05 15:50:22 +01:00
commit 0a1403bda3
6 changed files with 212 additions and 57 deletions

View file

@ -347,6 +347,7 @@ export type AetheriaMenuSurfaceRequest = {
surfaceId?: string;
inGame?: boolean;
canOpenRuntimeInputScreen?: boolean;
panelOnly?: boolean;
};
export type AetheriaEveSurfaceRequest = {

View file

@ -126,6 +126,7 @@ function renderMainMenuSurface(surface: AetheriaMenuSurfaceDocument): void {
assetUrlResolver: resolveAetheriaAssetUrl,
clientId: "aetheria.rts.electron",
commandSink: intent => handleMenuCommand(intent.command, surface),
documentResolver: resolveEveDocument,
source: "Aetheria Electron",
statusElement: statusEl,
});
@ -181,7 +182,21 @@ async function showDaemonEveSurface(): Promise<void> {
async function resolveEveDocument(
request: EveEmbeddedDocumentRequest,
component: EveSurfaceComponent,
): Promise<{ document: unknown; documentId: string; schemaId: string } | undefined> {
): Promise<{ document?: unknown; documentId: string; schemaId: string; surface?: AetheriaMenuSurfaceDocument["surface"] } | undefined> {
if (request.schemaId === "gamecult.eve.surface.v1" || request.slotId === "mainMenuPanel") {
const surface = await window.aetheriaRts.mainMenuSurface({
surfaceId: request.documentId,
inGame: false,
canOpenRuntimeInputScreen: false,
panelOnly: true,
});
return {
documentId: request.documentId,
schemaId: "gamecult.eve.surface.v1",
surface: surface.surface,
};
}
const viewport = viewportFromComponent(component);
if (request.schemaId === "gamecult.aetheria.render_splats_viewport.v1" || request.slotId === "renderSplats") {
return {
@ -278,6 +293,12 @@ function handleMenuCommand(commandOrIntent: string | EveCommandIntent, surface?:
case "aetheria.main_menu.root.show_settings":
void showMainMenu("aetheria.main_menu.settings");
return;
case "aetheria.main_menu.settings.show_player_settings":
void showMainMenu("aetheria.main_menu.player_settings");
return;
case "aetheria.main_menu.settings.show_verse_settings":
void showMainMenu("aetheria.main_menu.verse_settings");
return;
case "aetheria.main_menu.settings.show_input_settings":
void showMainMenu("aetheria.main_menu.input_settings");
return;

View file

@ -100,6 +100,7 @@ export type AetheriaMenuSurfaceRequest = {
surfaceId?: string;
inGame?: boolean;
canOpenRuntimeInputScreen?: boolean;
panelOnly?: boolean;
};
export type AetheriaEveSurfaceRequest = {
@ -336,7 +337,8 @@ export class AetheriaCultMeshClient {
return buildMainMenuSurface(
request.surfaceId || "aetheria.main_menu.root",
request.canOpenRuntimeInputScreen === true,
request.inGame === true);
request.inGame === true,
request.panelOnly === true);
}
public async eveSurface(request: AetheriaEveSurfaceRequest = {}): Promise<AetheriaMenuSurfaceDocument> {
@ -698,8 +700,42 @@ function buildMainMenuSurface(
surfaceId: string,
canOpenRuntimeInputScreen: boolean,
inGame: boolean,
panelOnly = false,
): AetheriaMenuSurfaceDocument {
const updatedAtUtc = new Date().toISOString();
const activeSurfaceId = normalizeMainMenuSurfaceId(surfaceId);
return panelOnly
? buildMainMenuPanelSurface(activeSurfaceId, canOpenRuntimeInputScreen, inGame, updatedAtUtc)
: buildMainMenuShellSurface(activeSurfaceId, updatedAtUtc);
}
function buildMainMenuShellSurface(
activeSurfaceId: string,
updatedAtUtc: string,
): AetheriaMenuSurfaceDocument {
return surfaceDocument(
"aetheria.main_menu.shell",
"Aetheria Starbridge",
updatedAtUtc,
mainMenuCommands(),
gravitySurface("aetheria.main_menu.shell.gravity"),
surfaceSlot(
"aetheria.main_menu.shell.panel",
"mainMenuPanel",
activeSurfaceId,
eveSurfaceSchemaId,
"menu.overlay",
{ position: "absolute", top: "0", right: "0", bottom: "0", left: "0", width: "100%", height: "100%" },
{ background: "rgba(0,0,0,0)" }),
);
}
function buildMainMenuPanelSurface(
surfaceId: string,
canOpenRuntimeInputScreen: boolean,
inGame: boolean,
updatedAtUtc: string,
): AetheriaMenuSurfaceDocument {
if (surfaceId === "aetheria.main_menu.settings") {
return surfaceDocument(
surfaceId,
@ -711,20 +747,25 @@ function buildMainMenuSurface(
command("aetheria.main_menu.settings.show_input_settings", "Input"),
command("aetheria.main_menu.settings.back_to_main", "Back"),
],
text("aetheria.mainMenu.settings.title", "SETTINGS", "text.title"),
buttonColumn(
"aetheria.mainMenu.settings.actions",
button("aetheria.mainMenu.settings.playerSettings", "Player Settings", "aetheria.main_menu.settings.show_player_settings"),
button("aetheria.mainMenu.settings.verse", "Verse", "aetheria.main_menu.settings.show_verse_settings"),
button("aetheria.mainMenu.settings.input", "Input", "aetheria.main_menu.settings.show_input_settings"),
button("aetheria.mainMenu.settings.back", "Back", "aetheria.main_menu.settings.back_to_main"),
menuPanel(
surfaceId,
[
text("aetheria.mainMenu.settings.title", "SETTINGS", "text.title", { margin: "0 0 2.4rem 0" }, mainMenuTitleStyle("4.4rem")),
buttonColumn(
"aetheria.mainMenu.settings.actions",
button("aetheria.mainMenu.settings.playerSettings", "Player Settings", "aetheria.main_menu.settings.show_player_settings"),
button("aetheria.mainMenu.settings.verse", "Verse", "aetheria.main_menu.settings.show_verse_settings"),
button("aetheria.mainMenu.settings.input", "Input", "aetheria.main_menu.settings.show_input_settings"),
button("aetheria.mainMenu.settings.back", "Back", "aetheria.main_menu.settings.back_to_main"),
),
],
),
);
}
if (surfaceId === "aetheria.main_menu.input_settings") {
const children = [
text("aetheria.mainMenu.input.title", "INPUT", "text.title"),
text("aetheria.mainMenu.input.title", "INPUT", "text.title", { margin: "0 0 2.2rem 0" }, mainMenuTitleStyle("4.4rem")),
metric("aetheria.mainMenu.input.bindingOverrides", "Binding Overrides", "0"),
metric("aetheria.mainMenu.input.actionBarInputs", "Action-Bar Inputs", "0"),
text(
@ -742,7 +783,47 @@ function buildMainMenuSurface(
"Aetheria Input Settings",
updatedAtUtc,
[command("aetheria.main_menu.settings.back_to_settings", "Back")],
...children);
menuPanel(surfaceId, children));
}
if (surfaceId === "aetheria.main_menu.player_settings") {
return surfaceDocument(
surfaceId,
"Aetheria Player Settings",
updatedAtUtc,
[command("aetheria.main_menu.settings.back_to_settings", "Back")],
menuPanel(
surfaceId,
[
text("aetheria.mainMenu.player.title", "PLAYER", "text.title", { margin: "0 0 2.2rem 0" }, mainMenuTitleStyle("4.4rem")),
metric("aetheria.mainMenu.player.temperatureUnit", "Temperature Unit", "Celsius"),
metric("aetheria.mainMenu.player.significantDigits", "Significant Digits", "3"),
metric("aetheria.mainMenu.player.shutdown", "Default Shutdown", "25%"),
buttonColumn(
"aetheria.mainMenu.player.actions",
button("aetheria.mainMenu.player.back", "Back", "aetheria.main_menu.settings.back_to_settings"),
),
]));
}
if (surfaceId === "aetheria.main_menu.verse_settings") {
return surfaceDocument(
surfaceId,
"Aetheria Verse Settings",
updatedAtUtc,
[command("aetheria.main_menu.settings.back_to_settings", "Back")],
menuPanel(
surfaceId,
[
text("aetheria.mainMenu.verse.title", "VERSE", "text.title", { margin: "0 0 2.2rem 0" }, mainMenuTitleStyle("4.4rem")),
metric("aetheria.mainMenu.verse.name", "Name", "Local Aetheria"),
metric("aetheria.mainMenu.verse.id", "Verse", "aetheria.local"),
metric("aetheria.mainMenu.verse.visibility", "Visibility", "local"),
buttonColumn(
"aetheria.mainMenu.verse.actions",
button("aetheria.mainMenu.verse.back", "Back", "aetheria.main_menu.settings.back_to_settings"),
),
]));
}
const actionButtons = [
@ -761,36 +842,50 @@ function buildMainMenuSurface(
command("aetheria.main_menu.root.show_settings", "Settings"),
command("aetheria.main_menu.root.quit", "Quit"),
],
gravitySurface("aetheria.main_menu.root.gravity"),
node(
"aetheria.main_menu.root.menu",
"column",
{},
{
position: "relative",
padding: "7.25rem 0 0 6.75rem",
gap: "1.1rem",
width: "44rem",
maxWidth: "calc(100vw - 3rem)",
minHeight: "100vh",
alignItems: "flex-start",
},
{ color: "#e9fbff" },
text("aetheria.main_menu.root.title", "AETHERIA", "text.title", { margin: "0 0 -1.6rem 0" }, {
font: "100 5.9rem/0.98 Montserrat, sans-serif",
color: "rgba(232, 250, 255, 0.94)",
whiteSpace: "nowrap",
}),
text("aetheria.main_menu.root.subtitle", "STARBRIDGE", "text.subtitle", { margin: "0 0 0.35rem 16.8rem" }, {
font: "100 2.6rem/1 Montserrat, sans-serif",
color: "rgba(232, 250, 255, 0.9)",
whiteSpace: "nowrap",
}),
buttonColumn("aetheria.main_menu.root.actions", ...actionButtons),
),
menuPanel(
"aetheria.main_menu.root",
[
text("aetheria.main_menu.root.title", "AETHERIA", "text.title", { margin: "0 0 -1.6rem 0" }, mainMenuTitleStyle("5.9rem")),
text("aetheria.main_menu.root.subtitle", "STARBRIDGE", "text.subtitle", { margin: "0 0 0.35rem 16.8rem" }, {
font: "100 2.6rem/1 Montserrat, sans-serif",
color: "rgba(232, 250, 255, 0.9)",
whiteSpace: "nowrap",
}),
buttonColumn("aetheria.main_menu.root.actions", ...actionButtons),
]),
);
}
function normalizeMainMenuSurfaceId(surfaceId: string): string {
switch (surfaceId) {
case "aetheria.main_menu.shell":
case "":
return "aetheria.main_menu.root";
case "aetheria.main_menu.root":
case "aetheria.main_menu.settings":
case "aetheria.main_menu.input_settings":
case "aetheria.main_menu.player_settings":
case "aetheria.main_menu.verse_settings":
return surfaceId;
default:
return "aetheria.main_menu.root";
}
}
function mainMenuCommands(): AetheriaMenuSurfaceCommand[] {
return [
command("aetheria.main_menu.root.continue", "Continue"),
command("aetheria.main_menu.root.new_game", "New Game"),
command("aetheria.main_menu.root.show_settings", "Settings"),
command("aetheria.main_menu.root.quit", "Quit"),
command("aetheria.main_menu.settings.show_player_settings", "Player Settings"),
command("aetheria.main_menu.settings.show_verse_settings", "Verse"),
command("aetheria.main_menu.settings.show_input_settings", "Input"),
command("aetheria.main_menu.settings.back_to_main", "Back"),
command("aetheria.main_menu.settings.back_to_settings", "Back"),
];
}
function surfaceDocument(
surfaceId: string,
title: string,
@ -852,6 +947,26 @@ function node(
return { id, kind, props, layout, style, children: normalizedChildren };
}
function surfaceSlot(
id: string,
slotId: string,
documentId: string,
schemaId: string,
presentationKind: string,
layout?: Record<string, string>,
style?: Record<string, string>,
): AetheriaMenuSurfaceComponent {
return {
id,
kind: "surface.slot",
props: { slotId, documentId, schemaId, presentationKind },
layout,
style,
embeddedDocuments: [{ slotId, documentId, schemaId, presentationKind }],
children: [],
};
}
function text(
id: string,
value: string,
@ -866,18 +981,44 @@ function metric(id: string, label: string, value: string): AetheriaMenuSurfaceCo
return node(id, "metric", { label, value });
}
function menuPanel(id: string, children: AetheriaMenuSurfaceComponent[]): AetheriaMenuSurfaceComponent {
return node(
`${id}.menu`,
"column",
{},
{
position: "relative",
padding: "7.25rem 0 0 6.75rem",
gap: "1.1rem",
width: "44rem",
maxWidth: "calc(100vw - 3rem)",
minHeight: "100vh",
alignItems: "flex-start",
},
{ color: "#e9fbff", background: "rgba(0,0,0,0)" },
...children);
}
function mainMenuTitleStyle(fontSize: string): Record<string, string> {
return {
font: `100 ${fontSize}/0.98 Montserrat, sans-serif`,
color: "rgba(232, 250, 255, 0.94)",
whiteSpace: "nowrap",
};
}
function button(id: string, label: string, commandId: string): AetheriaMenuSurfaceComponent {
return node(
id,
"control.button",
{ label, command: commandId },
{ minWidth: "0", padding: "0.04rem 0" },
{ minWidth: "0", width: "220px", height: "32px", padding: "0" },
{
background: "rgba(0, 0, 0, 0)",
borderWidth: "0",
borderStyle: "solid",
boxShadow: "none",
font: "400 1.55rem/1.2 Ubuntu, sans-serif",
font: "400 1.55rem/32px Ubuntu, sans-serif",
color: "#e8fbff",
textAlign: "left",
});

View file

@ -1916,6 +1916,7 @@ export type AetheriaMenuSurfaceRequest = {
surfaceId?: string;
inGame?: boolean;
canOpenRuntimeInputScreen?: boolean;
panelOnly?: boolean;
};
export type AetheriaEveSurfaceRequest = {

View file

@ -485,6 +485,7 @@ export type AetheriaMenuSurfaceRequest = {
surfaceId?: string;
inGame?: boolean;
canOpenRuntimeInputScreen?: boolean;
panelOnly?: boolean;
};
export type AetheriaEveSurfaceRequest = {
@ -1154,6 +1155,7 @@ export type AetheriaMenuSurfaceRequest = {
surfaceId?: string;
inGame?: boolean;
canOpenRuntimeInputScreen?: boolean;
panelOnly?: boolean;
};
export type AetheriaEveSurfaceRequest = {

View file

@ -25,11 +25,7 @@ body {
body.main-menu-mode {
-webkit-app-region: drag;
background:
radial-gradient(circle at 76% 66%, rgba(255, 213, 112, 0.74) 0, rgba(247, 121, 88, 0.36) 13%, transparent 31%),
radial-gradient(circle at 61% 37%, rgba(240, 45, 201, 0.54) 0, rgba(176, 38, 174, 0.28) 19%, transparent 45%),
radial-gradient(circle at 40% 45%, rgba(145, 21, 122, 0.42) 0, rgba(54, 8, 41, 0.4) 34%, transparent 62%),
#050407;
background: #020606;
}
body.main-menu-mode .shell {
@ -100,22 +96,15 @@ body.eve-game-mode .window-control-close:focus-visible {
body.main-menu-mode .main-menu-host {
-webkit-app-region: no-drag;
display: flex;
align-items: flex-start;
justify-content: flex-start;
display: block;
min-height: 100%;
padding: 128px 96px;
background:
radial-gradient(ellipse at 78% 64%, rgba(255, 194, 95, 0.36) 0, rgba(255, 148, 92, 0.18) 16%, transparent 35%),
radial-gradient(ellipse at 59% 38%, rgba(238, 56, 216, 0.28) 0, rgba(164, 37, 160, 0.2) 22%, transparent 52%),
linear-gradient(90deg, rgba(0, 0, 0, 0.5), transparent 48%);
padding: 0;
background: transparent;
}
body.main-menu-mode .eve-surface {
width: 720px;
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
min-height: 100vh;
}
body.main-menu-mode .eve-text-title,