Render main menu surface in Electron
This commit is contained in:
parent
eb0760c81f
commit
b4f7be9df2
7 changed files with 466 additions and 7 deletions
|
|
@ -10,6 +10,46 @@ import type {
|
|||
|
||||
export type Viewport = CultMeshViewportRequest;
|
||||
|
||||
export type AetheriaMenuSurfaceRequest = {
|
||||
surfaceId?: string;
|
||||
inGame?: boolean;
|
||||
canOpenRuntimeInputScreen?: boolean;
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceDocument = {
|
||||
providerId: string;
|
||||
providerKind: string;
|
||||
title: string;
|
||||
version: number;
|
||||
updatedAtUtc: string;
|
||||
surface: AetheriaMenuSurfaceTree;
|
||||
commands: AetheriaMenuSurfaceCommand[];
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceTree = {
|
||||
id: string;
|
||||
root: AetheriaMenuSurfaceComponent;
|
||||
styles: AetheriaMenuStyleToken[];
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceComponent = {
|
||||
id: string;
|
||||
kind: string;
|
||||
props: Record<string, string>;
|
||||
children: AetheriaMenuSurfaceComponent[];
|
||||
};
|
||||
|
||||
export type AetheriaMenuStyleToken = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceCommand = {
|
||||
command: string;
|
||||
label: string;
|
||||
transport: string;
|
||||
};
|
||||
|
||||
export type AetheriaRuntimeSetMoveVectorRequest = {
|
||||
actorEntityKey: string;
|
||||
directionX: number;
|
||||
|
|
@ -305,5 +345,6 @@ export type AetheriaRtsApi = {
|
|||
setTarget(request: AetheriaRuntimeSetTargetRequest): Promise<AetheriaRuntimeDaemonCommandReceipt>;
|
||||
surfaceCatalog(): Promise<CultMeshSurfaceCatalogDiagnostic>;
|
||||
surfaceCatalogIndex(): Promise<CultMeshSurfaceCatalogIndexDiagnostic>;
|
||||
mainMenuSurface(request: AetheriaMenuSurfaceRequest): Promise<AetheriaMenuSurfaceDocument>;
|
||||
health(): Promise<unknown>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import type {
|
||||
AetheriaRtsApi,
|
||||
AetheriaMenuSurfaceComponent,
|
||||
AetheriaMenuSurfaceDocument,
|
||||
AuthorityStatusDocument,
|
||||
AetheriaRuntimeViewportFeedSnapshot,
|
||||
BodyView,
|
||||
|
|
@ -49,6 +51,7 @@ let latestReceivedAt = 0;
|
|||
let latestPollMs = 0;
|
||||
let refreshInFlight = false;
|
||||
let viewportFeedUnsubscribe: (() => void) | null = null;
|
||||
const mainMenuMode = window.location.hash === "#main-menu";
|
||||
|
||||
function requiredElement<TElement extends Element>(selector: string): TElement {
|
||||
const element = document.querySelector<TElement>(selector);
|
||||
|
|
@ -87,6 +90,117 @@ function setStatus(text: string): void {
|
|||
statusEl.textContent = text;
|
||||
}
|
||||
|
||||
async function showMainMenu(surfaceId = "aetheria.main_menu.root"): Promise<void> {
|
||||
document.body.classList.add("main-menu-mode");
|
||||
viewportFeedUnsubscribe?.();
|
||||
const surface = await window.aetheriaRts.mainMenuSurface({
|
||||
surfaceId,
|
||||
inGame: false,
|
||||
canOpenRuntimeInputScreen: false,
|
||||
});
|
||||
installMenuFonts(surface);
|
||||
renderMainMenuSurface(surface);
|
||||
}
|
||||
|
||||
function installMenuFonts(surface: AetheriaMenuSurfaceDocument): void {
|
||||
const googleFont = surface.surface.styles.find(token => token.name === "font.web.google")?.value;
|
||||
if (!googleFont || document.querySelector(`link[data-aetheria-menu-fonts="${googleFont}"]`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const preconnect = document.createElement("link");
|
||||
preconnect.rel = "preconnect";
|
||||
preconnect.href = "https://fonts.gstatic.com";
|
||||
preconnect.crossOrigin = "anonymous";
|
||||
document.head.append(preconnect);
|
||||
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = googleFont;
|
||||
link.dataset.aetheriaMenuFonts = googleFont;
|
||||
document.head.append(link);
|
||||
}
|
||||
|
||||
function renderMainMenuSurface(surface: AetheriaMenuSurfaceDocument): void {
|
||||
let host = document.querySelector<HTMLElement>("#main-menu-host");
|
||||
if (!host) {
|
||||
host = document.createElement("section");
|
||||
host.id = "main-menu-host";
|
||||
host.className = "main-menu-host";
|
||||
document.body.append(host);
|
||||
}
|
||||
|
||||
host.replaceChildren(lowerMenuComponent(surface.surface.root, surface));
|
||||
}
|
||||
|
||||
function lowerMenuComponent(
|
||||
component: AetheriaMenuSurfaceComponent,
|
||||
surface: AetheriaMenuSurfaceDocument,
|
||||
): HTMLElement {
|
||||
const kind = component.kind || "surface";
|
||||
if (kind === "control.button") {
|
||||
const button = document.createElement("button");
|
||||
button.id = component.id;
|
||||
button.className = "eve-control-button";
|
||||
button.type = "button";
|
||||
button.textContent = component.props.label ?? "";
|
||||
button.addEventListener("click", () => handleMenuCommand(component.props.command ?? "", surface));
|
||||
return button;
|
||||
}
|
||||
|
||||
if (kind.startsWith("text")) {
|
||||
const label = document.createElement("div");
|
||||
label.id = component.id;
|
||||
label.className = kind === "text.title" ? "eve-text-title" : "eve-text";
|
||||
label.textContent = component.props.value ?? component.props.text ?? "";
|
||||
return label;
|
||||
}
|
||||
|
||||
if (kind === "metric") {
|
||||
const metric = document.createElement("dl");
|
||||
metric.id = component.id;
|
||||
metric.className = "eve-metric";
|
||||
const term = document.createElement("dt");
|
||||
term.textContent = component.props.label ?? "";
|
||||
const value = document.createElement("dd");
|
||||
value.textContent = component.props.value ?? "";
|
||||
metric.append(term, value);
|
||||
return metric;
|
||||
}
|
||||
|
||||
const element = document.createElement("div");
|
||||
element.id = component.id;
|
||||
element.className = [
|
||||
kind === "surface" ? "eve-surface" : "",
|
||||
kind === "column" ? "eve-column" : "",
|
||||
kind === "row" ? "eve-row" : "",
|
||||
].filter(Boolean).join(" ");
|
||||
for (const child of component.children ?? []) {
|
||||
element.append(lowerMenuComponent(child, surface));
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function handleMenuCommand(command: string, surface: AetheriaMenuSurfaceDocument): void {
|
||||
switch (command) {
|
||||
case "aetheria.main_menu.root.show_settings":
|
||||
void showMainMenu("aetheria.main_menu.settings");
|
||||
return;
|
||||
case "aetheria.main_menu.settings.show_input_settings":
|
||||
void showMainMenu("aetheria.main_menu.input_settings");
|
||||
return;
|
||||
case "aetheria.main_menu.settings.back_to_settings":
|
||||
void showMainMenu("aetheria.main_menu.settings");
|
||||
return;
|
||||
case "aetheria.main_menu.settings.back_to_main":
|
||||
void showMainMenu("aetheria.main_menu.root");
|
||||
return;
|
||||
default:
|
||||
setStatus(`${surface.title}: ${command || "no command"}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function startViewportFeed(): void {
|
||||
const viewport = currentViewport();
|
||||
viewportFeedUnsubscribe?.();
|
||||
|
|
@ -585,6 +699,10 @@ function roleText(session: StarbridgeSessionDocument): string {
|
|||
.join(", ");
|
||||
}
|
||||
|
||||
updateZoomLabel();
|
||||
void refreshRuntimeSurfaces();
|
||||
void refresh();
|
||||
if (mainMenuMode) {
|
||||
void showMainMenu();
|
||||
} else {
|
||||
updateZoomLabel();
|
||||
void refreshRuntimeSurfaces();
|
||||
void refresh();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,46 @@ export type AetheriaCultMeshClientOptions = {
|
|||
snapshotTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceRequest = {
|
||||
surfaceId?: string;
|
||||
inGame?: boolean;
|
||||
canOpenRuntimeInputScreen?: boolean;
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceDocument = {
|
||||
providerId: string;
|
||||
providerKind: string;
|
||||
title: string;
|
||||
version: number;
|
||||
updatedAtUtc: string;
|
||||
surface: AetheriaMenuSurfaceTree;
|
||||
commands: AetheriaMenuSurfaceCommand[];
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceTree = {
|
||||
id: string;
|
||||
root: AetheriaMenuSurfaceComponent;
|
||||
styles: AetheriaMenuStyleToken[];
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceComponent = {
|
||||
id: string;
|
||||
kind: string;
|
||||
props: Record<string, string>;
|
||||
children: AetheriaMenuSurfaceComponent[];
|
||||
};
|
||||
|
||||
export type AetheriaMenuStyleToken = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type AetheriaMenuSurfaceCommand = {
|
||||
command: string;
|
||||
label: string;
|
||||
transport: string;
|
||||
};
|
||||
|
||||
export type {
|
||||
BodyView,
|
||||
EntityStatus,
|
||||
|
|
@ -232,6 +272,13 @@ export class AetheriaCultMeshClient {
|
|||
return this.aetheria.daemon.assetManifest();
|
||||
}
|
||||
|
||||
public async mainMenuSurface(request: AetheriaMenuSurfaceRequest = {}): Promise<AetheriaMenuSurfaceDocument> {
|
||||
return buildMainMenuSurface(
|
||||
request.surfaceId || "aetheria.main_menu.root",
|
||||
request.canOpenRuntimeInputScreen === true,
|
||||
request.inGame === true);
|
||||
}
|
||||
|
||||
public queryDiagnostics(): Readonly<Record<string, AetheriaRuntimeRtsQueryDiagnostic>> {
|
||||
return describeAetheriaRuntimeRtsQueryHandles(this.queries);
|
||||
}
|
||||
|
|
@ -449,6 +496,141 @@ export class AetheriaCultMeshClient {
|
|||
}
|
||||
}
|
||||
|
||||
function buildMainMenuSurface(
|
||||
surfaceId: string,
|
||||
canOpenRuntimeInputScreen: boolean,
|
||||
inGame: boolean,
|
||||
): AetheriaMenuSurfaceDocument {
|
||||
const updatedAtUtc = new Date().toISOString();
|
||||
if (surfaceId === "aetheria.main_menu.settings") {
|
||||
return surfaceDocument(
|
||||
surfaceId,
|
||||
"Aetheria Settings",
|
||||
updatedAtUtc,
|
||||
[
|
||||
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"),
|
||||
],
|
||||
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"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (surfaceId === "aetheria.main_menu.input_settings") {
|
||||
const children = [
|
||||
text("aetheria.mainMenu.input.title", "INPUT", "text.title"),
|
||||
metric("aetheria.mainMenu.input.bindingOverrides", "Binding Overrides", "0"),
|
||||
metric("aetheria.mainMenu.input.actionBarInputs", "Action-Bar Inputs", "0"),
|
||||
text(
|
||||
"aetheria.mainMenu.input.note",
|
||||
canOpenRuntimeInputScreen && inGame
|
||||
? "The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits."
|
||||
: "This title shell reports typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding."),
|
||||
buttonColumn(
|
||||
"aetheria.mainMenu.input.actions",
|
||||
button("aetheria.mainMenu.input.back", "Back", "aetheria.main_menu.settings.back_to_settings"),
|
||||
),
|
||||
];
|
||||
return surfaceDocument(
|
||||
surfaceId,
|
||||
"Aetheria Input Settings",
|
||||
updatedAtUtc,
|
||||
[command("aetheria.main_menu.settings.back_to_settings", "Back")],
|
||||
...children);
|
||||
}
|
||||
|
||||
const actionButtons = [
|
||||
...(!inGame ? [button("aetheria.main_menu.root.continue", "Continue", "aetheria.main_menu.root.continue")] : []),
|
||||
button("aetheria.main_menu.root.newGame", "New Game", "aetheria.main_menu.root.new_game"),
|
||||
button("aetheria.main_menu.root.settings", "Settings", "aetheria.main_menu.root.show_settings"),
|
||||
button("aetheria.main_menu.root.quit", "Quit", "aetheria.main_menu.root.quit"),
|
||||
];
|
||||
return surfaceDocument(
|
||||
"aetheria.main_menu.root",
|
||||
"Aetheria Terminus",
|
||||
updatedAtUtc,
|
||||
[
|
||||
...(!inGame ? [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"),
|
||||
],
|
||||
text("aetheria.main_menu.root.title", "AETHERIA", "text.title"),
|
||||
text("aetheria.main_menu.root.subtitle", "TERMINUS", "text.subtitle"),
|
||||
buttonColumn("aetheria.main_menu.root.actions", ...actionButtons),
|
||||
);
|
||||
}
|
||||
|
||||
function surfaceDocument(
|
||||
surfaceId: string,
|
||||
title: string,
|
||||
updatedAtUtc: string,
|
||||
commands: AetheriaMenuSurfaceCommand[],
|
||||
...children: AetheriaMenuSurfaceComponent[]
|
||||
): AetheriaMenuSurfaceDocument {
|
||||
return {
|
||||
providerId: "aetheria",
|
||||
providerKind: "game.menu",
|
||||
title,
|
||||
version: 1,
|
||||
updatedAtUtc,
|
||||
surface: {
|
||||
id: surfaceId,
|
||||
root: node(`${surfaceId}.root`, "surface", {}, ...children),
|
||||
styles: [
|
||||
{ name: "font.title.family", value: "Montserrat" },
|
||||
{ name: "font.title.style", value: "Thin" },
|
||||
{ name: "font.title.weight", value: "100" },
|
||||
{ name: "font.body.family", value: "Ubuntu" },
|
||||
{ name: "font.body.style", value: "Regular" },
|
||||
{ name: "font.body.weight", value: "400" },
|
||||
{
|
||||
name: "font.web.google",
|
||||
value: "https://fonts.googleapis.com/css2?family=Montserrat:wght@100&family=Ubuntu:wght@400&display=swap",
|
||||
},
|
||||
],
|
||||
},
|
||||
commands,
|
||||
};
|
||||
}
|
||||
|
||||
function command(commandId: string, label: string): AetheriaMenuSurfaceCommand {
|
||||
return { command: commandId, label, transport: "cultmesh" };
|
||||
}
|
||||
|
||||
function node(
|
||||
id: string,
|
||||
kind: string,
|
||||
props: Record<string, string>,
|
||||
...children: AetheriaMenuSurfaceComponent[]
|
||||
): AetheriaMenuSurfaceComponent {
|
||||
return { id, kind, props, children };
|
||||
}
|
||||
|
||||
function text(id: string, value: string, kind = "text"): AetheriaMenuSurfaceComponent {
|
||||
return node(id, kind, { value });
|
||||
}
|
||||
|
||||
function metric(id: string, label: string, value: string): AetheriaMenuSurfaceComponent {
|
||||
return node(id, "metric", { label, value });
|
||||
}
|
||||
|
||||
function button(id: string, label: string, commandId: string): AetheriaMenuSurfaceComponent {
|
||||
return node(id, "control.button", { label, command: commandId });
|
||||
}
|
||||
|
||||
function buttonColumn(id: string, ...children: AetheriaMenuSurfaceComponent[]): AetheriaMenuSurfaceComponent {
|
||||
return node(id, "column", {}, ...children);
|
||||
}
|
||||
|
||||
function stableToken(value: string): string {
|
||||
return value
|
||||
.replace(/[^a-zA-Z0-9]+/gu, "-")
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const rtsCultMeshAdvertiseHost = process.env.AETHERIA_RTS_CULTMESH_ADVERTISE_HOS
|
|||
const peerCultMeshEndpoints = parseEndpointList(process.env.AETHERIA_RTS_PEER_CULTMESH_ENDPOINTS);
|
||||
const electronSmoke = process.env.AETHERIA_RTS_ELECTRON_SMOKE === "1";
|
||||
const electronSmokeResultPath = process.env.AETHERIA_RTS_ELECTRON_SMOKE_RESULT;
|
||||
const rendererView = process.env.AETHERIA_RTS_VIEW?.trim() ?? "";
|
||||
|
||||
let daemonProcess: ChildProcessWithoutNullStreams | null = null;
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
|
@ -39,7 +40,10 @@ app.whenReady().then(async () => {
|
|||
showStartup("Preparing Aetheria RTS", "Building daemon if needed.");
|
||||
|
||||
try {
|
||||
if (launchLocalDaemon) {
|
||||
if (rendererView === "main-menu") {
|
||||
showStartup("Launching Aetheria Menu", "Lowering the CultUI main-menu surface.");
|
||||
mkdirSync(runtimeRoot, { recursive: true });
|
||||
} else if (launchLocalDaemon) {
|
||||
await ensureDotnetBuild();
|
||||
showStartup("Launching Aetheria RTS", "Starting the Aetheria daemon.");
|
||||
mkdirSync(runtimeRoot, { recursive: true });
|
||||
|
|
@ -73,9 +77,13 @@ app.whenReady().then(async () => {
|
|||
"aetheria-rts-electron",
|
||||
{ publicationMode: launchLocalDaemon ? "local" : "remote" });
|
||||
|
||||
showStartup("Launching Aetheria RTS", "Waiting for the daemon CultMesh frame.");
|
||||
await rtsClient.waitForFrame(30000);
|
||||
await mainWindow.loadFile(rendererIndex);
|
||||
if (rendererView === "main-menu") {
|
||||
await mainWindow.loadFile(rendererIndex, { hash: "main-menu" });
|
||||
} else {
|
||||
showStartup("Launching Aetheria RTS", "Waiting for the daemon CultMesh frame.");
|
||||
await rtsClient.waitForFrame(30000);
|
||||
await mainWindow.loadFile(rendererIndex);
|
||||
}
|
||||
if (electronSmoke) {
|
||||
const result = await runElectronSmoke(mainWindow);
|
||||
writeElectronSmokeResult({ ok: true, result });
|
||||
|
|
@ -291,6 +299,8 @@ function registerIpc(): void {
|
|||
daemonRunning: daemonProcess != null && !daemonProcess.killed,
|
||||
daemonMode: launchLocalDaemon ? "local" : "remote",
|
||||
}));
|
||||
ipcMain.handle("aetheria-rts:main-menu-surface", async (_event, request) =>
|
||||
requireClient().mainMenuSurface(request));
|
||||
}
|
||||
|
||||
function parseEndpointList(value: string | undefined): string[] {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const channels = {
|
|||
setTarget: "aetheria-rts:set-target",
|
||||
surfaceCatalog: "aetheria-rts:surface-catalog",
|
||||
surfaceCatalogIndex: "aetheria-rts:surface-catalog-index",
|
||||
mainMenuSurface: "aetheria-rts:main-menu-surface",
|
||||
health: "aetheria-rts:health",
|
||||
};
|
||||
|
||||
|
|
@ -51,5 +52,6 @@ contextBridge.exposeInMainWorld("aetheriaRts", {
|
|||
setTarget: request => ipcRenderer.invoke(channels.setTarget, request),
|
||||
surfaceCatalog: () => ipcRenderer.invoke(channels.surfaceCatalog),
|
||||
surfaceCatalogIndex: () => ipcRenderer.invoke(channels.surfaceCatalogIndex),
|
||||
mainMenuSurface: request => ipcRenderer.invoke(channels.mainMenuSurface, request),
|
||||
health: () => ipcRenderer.invoke(channels.health),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
<section id="main-menu-host" class="main-menu-host" aria-label="Aetheria main menu"></section>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,111 @@ body {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-menu-host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.main-menu-mode {
|
||||
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;
|
||||
}
|
||||
|
||||
body.main-menu-mode .shell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.main-menu-mode .main-menu-host {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
min-height: 100%;
|
||||
padding: 92px 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%);
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-surface {
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-text-title {
|
||||
width: 720px;
|
||||
color: rgba(209, 242, 255, 0.96);
|
||||
font-family: "Montserrat", sans-serif;
|
||||
font-weight: 100;
|
||||
line-height: 0.95;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
body.main-menu-mode #aetheria\.main_menu\.root\.title {
|
||||
margin: 0 0 -12px;
|
||||
font-size: 86px;
|
||||
}
|
||||
|
||||
body.main-menu-mode #aetheria\.main_menu\.root\.subtitle {
|
||||
margin: 0 0 36px;
|
||||
font-size: 39px;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-text,
|
||||
body.main-menu-mode .eve-metric,
|
||||
body.main-menu-mode .eve-control-button {
|
||||
font-family: "Ubuntu", system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-control-button {
|
||||
width: 220px;
|
||||
min-width: 0;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: rgba(219, 250, 255, 0.96);
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
text-align: left;
|
||||
letter-spacing: 0;
|
||||
transition: letter-spacing 160ms ease, color 160ms ease;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-control-button:hover,
|
||||
body.main-menu-mode .eve-control-button:focus-visible {
|
||||
background: transparent;
|
||||
color: white;
|
||||
letter-spacing: 8px;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-metric {
|
||||
display: grid;
|
||||
grid-template-columns: 180px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
width: 520px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
body.main-menu-mode .eve-text:not(.eve-text-title) {
|
||||
width: 520px;
|
||||
margin: 0 0 14px;
|
||||
color: rgba(219, 250, 255, 0.9);
|
||||
font-size: 16px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
button {
|
||||
min-width: 36px;
|
||||
height: 32px;
|
||||
|
|
|
|||
Loading…
Reference in a new issue