@origingame/engine 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/og.mjs +1 -1
- package/dist/web/origin-engine.d.ts +219 -5
- package/dist/web/origin-engine.js +1344 -125
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +68 -3
- package/dist/web-publish/origin-engine-lanes.js +1007 -22
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +162 -5
- package/dist/web-publish/origin-engine.js +1004 -19
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +52 -3
- package/dist/web-publish-2d/origin-engine-lanes.js +1007 -22
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +162 -5
- package/dist/web-publish-2d/origin-engine.js +1004 -19
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +52 -3
- package/knowledge/looks/catalogue.json +40 -40
- package/knowledge/package.json +4 -4
- package/knowledge/scaffold/distribution.json +14 -0
- package/knowledge/scaffold/origin-engine.json +2 -2
- package/knowledge/scaffold/template/README.md +2 -1
- package/knowledge/scaffold/template/assets/audio/sting.ogt +10 -0
- package/knowledge/scaffold/template/assets/timelines/intro.ogt +101 -0
- package/knowledge/scaffold/template/assets/timelines/narration.srt +11 -0
- package/knowledge/scaffold/template/host.d.ts +46 -0
- package/knowledge/scaffold/template/package.json +1 -1
- package/knowledge/scaffold/template/project.ogt +1 -1
- package/knowledge/scaffold/template/src/main.ts +1 -1
- package/knowledge/scaffold/template/tests/frames.ts +134 -0
- package/knowledge/scaffold/template/tests/intro.og.ts +65 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +30 -36
- package/knowledge/scaffold/template/tsconfig.json +1 -1
- package/knowledge/scripts/mount.ts +2 -1
- package/knowledge/scripts/validate.ts +21 -11
- package/knowledge/skills/og-build/SKILL.md +25 -6
- package/knowledge/skills/og-core/SKILL.md +11 -0
- package/knowledge/skills/og-feel/SKILL.md +8 -1
- package/knowledge/skills/og-game/SKILL.md +994 -21
- package/knowledge/skills/og-i18n/SKILL.md +12 -3
- package/knowledge/skills/og-platform-sdk/SKILL.md +4 -1
- package/knowledge/skills/og-taste/SKILL.md +82 -73
- package/knowledge/skills/og-ui/SKILL.md +82 -54
- package/knowledge/wiki/ai.md +20 -20
- package/knowledge/wiki/anim.md +384 -41
- package/knowledge/wiki/atmosphere.md +36 -13
- package/knowledge/wiki/audio.md +18 -18
- package/knowledge/wiki/camera.md +19 -13
- package/knowledge/wiki/commands.md +19 -13
- package/knowledge/wiki/debug.md +414 -0
- package/knowledge/wiki/game.md +8 -8
- package/knowledge/wiki/i18n.md +7 -7
- package/knowledge/wiki/index.md +265 -7
- package/knowledge/wiki/input.md +47 -47
- package/knowledge/wiki/load.md +236 -68
- package/knowledge/wiki/math.md +2614 -7
- package/knowledge/wiki/nav.md +34 -34
- package/knowledge/wiki/net.md +340 -0
- package/knowledge/wiki/path.md +402 -0
- package/knowledge/wiki/physics.md +124 -106
- package/knowledge/wiki/platform.md +465 -31
- package/knowledge/wiki/probe.md +681 -67
- package/knowledge/wiki/reflect.md +15 -15
- package/knowledge/wiki/render.md +3904 -1897
- package/knowledge/wiki/rng.md +14 -14
- package/knowledge/wiki/stream.md +15 -15
- package/knowledge/wiki/system.md +8 -8
- package/knowledge/wiki/systems.md +2 -2
- package/knowledge/wiki/terrain.md +19 -19
- package/knowledge/wiki/tilemap.md +19 -15
- package/knowledge/wiki/time.md +4 -4
- package/knowledge/wiki/timeline.md +396 -0
- package/knowledge/wiki/transform.md +8 -8
- package/knowledge/wiki/tween.md +17 -17
- package/knowledge/wiki/ui.md +603 -629
- package/knowledge/wiki/video.md +157 -0
- package/knowledge/wiki/world.md +67 -37
- package/package.json +7 -6
- package/schema/api.schema.json +21071 -13964
- package/shell/page.ts +26 -2
- package/shell/platform.ts +46 -0
- package/shell/player.ts +7 -0
- package/src/depth.ts +71 -0
- package/src/e6.ts +32 -1
- package/src/env.d.ts +5 -1
- package/src/generated/abi.ts +189 -3
- package/src/index.ts +26 -4
- package/src/light.ts +18 -0
- package/src/math.ts +622 -0
- package/src/node/index.ts +17 -0
- package/src/node/playtest-run.ts +114 -18
- package/src/node/polish.ts +15 -4
- package/src/node/render.ts +291 -0
- package/src/node/run.ts +91 -6
- package/src/p2.ts +19 -1
- package/src/playtest.ts +57 -4
- package/src/runtime.ts +15 -3
- package/src/targets.ts +28 -0
- package/src/trace.ts +169 -0
- package/src/ui-kit/huds.ts +49 -45
- package/src/ui-kit/menus.ts +12 -12
- package/src/ui-kit/settings.ts +2 -2
- package/src/ui-kit/store.ts +18 -18
- package/src/ui-kit/story.ts +6 -4
- package/src/ui-kit/util.ts +8 -8
- package/src/web/shell.ts +58 -0
- package/src/web/worker.ts +103 -2
- package/types/bindings.json +8945 -6456
- package/types/index.d.ts +15507 -11437
package/src/ui-kit/huds.ts
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
// set of anchored groups over the game in the safe area, plus the touch
|
|
3
3
|
// controls they show on phones and tablets. HUDs turn keyboard and gamepad
|
|
4
4
|
// navigation off (the keys move the player); Back still answers `paused`,
|
|
5
|
-
// except under an open dialog, which keeps it (og.ui.back).
|
|
5
|
+
// except under an open dialog, which keeps it (og.ui.back). Every word a HUD
|
|
6
|
+
// draws or names is a field of its desc with the English default (og-i18n
|
|
7
|
+
// §2), so a game in another language has none of the kit's English left.
|
|
6
8
|
|
|
7
9
|
import type * as Api from "@origingame/engine";
|
|
8
10
|
import { kit } from "./bind.ts";
|
|
9
|
-
import { all,
|
|
11
|
+
import { all, hudBox, clock, fit, fmt, frame, grow, iconOr, lapTime, px, token } from "./util.ts";
|
|
10
12
|
|
|
11
13
|
type Hud = { paused: boolean; action: string | null; moveX: number; moveY: number; held: string[] };
|
|
12
14
|
|
|
@@ -15,7 +17,8 @@ function hud(): Hud {
|
|
|
15
17
|
return { paused: kit.back(), action: null, moveX: 0, moveY: 0, held: [] };
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
/** The pause button: its icon, named by the desc's `pauseLabel` (what a screen reader says). */
|
|
21
|
+
function pauseButton(out: Hud, label: string): void {
|
|
19
22
|
const r = kit.actionButton("Pause", label, { size: 44, icon: "pause" });
|
|
20
23
|
if (r.clicked) out.paused = true;
|
|
21
24
|
}
|
|
@@ -24,7 +27,7 @@ function pauseButton(out: Hud, label = "Pause"): void {
|
|
|
24
27
|
function stat(id: string, value: string, caption: string, step: Api.TypeStepName = "xl3", align: Api.TextAlign = "start"): void {
|
|
25
28
|
kit.column(id, { gap: 0, cross: align === "end" ? "end" : align === "center" ? "center" : "start" }, () => {
|
|
26
29
|
kit.label("Value", value, { step, font: "mono", nowrap: true, align });
|
|
27
|
-
kit.label("Caption", caption, { step: "meta", color: token("
|
|
30
|
+
kit.label("Caption", caption, { step: "meta", color: token("inkMuted"), nowrap: true, align });
|
|
28
31
|
});
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -55,45 +58,45 @@ export function platformerHud(id: string, desc: Api.PlatformerHudDesc = {}): Api
|
|
|
55
58
|
const { t, touch } = frame();
|
|
56
59
|
const out = hud();
|
|
57
60
|
// What the game leaves out is not drawn: no hearts with maxLives 0 (and
|
|
58
|
-
// no health bar), no coins or score
|
|
61
|
+
// no health bar), no coins or score box without coins or score.
|
|
59
62
|
const health = desc.health ?? null;
|
|
60
63
|
const maxLives = desc.maxLives ?? 3;
|
|
61
64
|
const coins = desc.coins ?? null;
|
|
62
65
|
const score = desc.score ?? null;
|
|
63
66
|
if (health !== null || maxLives > 0 || desc.level) {
|
|
64
67
|
kit.anchor(`${id}.left`, { anchor: "topLeft", direction: "column", gap: t.sm }, () => {
|
|
65
|
-
if (health !== null)
|
|
68
|
+
if (health !== null) hudBox("Lives", () => kit.meter("Health", health, 1, { width: px(120) }));
|
|
66
69
|
else if (maxLives > 0) {
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < maxLives; i++) iconOr(kit.idi("Life", i), "heart", 20, token(i < (desc.lives ?? 0) ? "danger" : "
|
|
70
|
+
hudBox("Lives", () => {
|
|
71
|
+
for (let i = 0; i < maxLives; i++) iconOr(kit.idi("Life", i), "heart", 20, token(i < (desc.lives ?? 0) ? "danger" : "controlStrong"));
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
|
-
if (desc.level) kit.label("Level", desc.level, { step: "meta", color: token("
|
|
74
|
+
if (desc.level) kit.label("Level", desc.level, { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
72
75
|
});
|
|
73
76
|
}
|
|
74
77
|
if (desc.timeLeft !== null && desc.timeLeft !== undefined) {
|
|
75
78
|
const low = desc.timeLeft <= 10;
|
|
76
79
|
kit.anchor(`${id}.top`, { anchor: "top" }, () => {
|
|
77
|
-
|
|
80
|
+
hudBox("Timer", () => kit.label("Time", clock(desc.timeLeft!), { step: "lg", font: "mono", color: token(low ? "danger" : "ink"), nowrap: true }));
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
83
|
kit.anchor(`${id}.right`, { anchor: "topRight", gap: t.sm, cross: "center" }, () => {
|
|
81
84
|
if (coins !== null) {
|
|
82
|
-
|
|
83
|
-
iconOr("Coin", "coin", 20, token("
|
|
85
|
+
hudBox("Coins", () => {
|
|
86
|
+
iconOr("Coin", "coin", 20, token("gold"));
|
|
84
87
|
kit.label("Count", fmt(coins), { font: "mono", nowrap: true });
|
|
85
88
|
});
|
|
86
89
|
}
|
|
87
|
-
if (score !== null)
|
|
88
|
-
pauseButton(out);
|
|
90
|
+
if (score !== null) hudBox("Score", () => kit.label("Points", fmt(score), { font: "mono", nowrap: true }));
|
|
91
|
+
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
89
92
|
});
|
|
90
|
-
if ((desc.touch ?? false) && touch) touchControls(out, { dpad: true, buttons: [{ id: "jump", label: "Jump", primary: true }] });
|
|
93
|
+
if ((desc.touch ?? false) && touch) touchControls(out, { dpad: true, buttons: [{ id: "jump", label: desc.jumpLabel ?? "Jump", primary: true }] });
|
|
91
94
|
return out;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
/** A crosshair at the centre: four short strokes and a gap. */
|
|
95
98
|
function crosshair(): void {
|
|
96
|
-
const ink = token("
|
|
99
|
+
const ink = token("ink", 0.9);
|
|
97
100
|
const bar = (id: string, w: number, h: number): void => kit.image(id, { tint: ink, width: px(w), height: px(h) });
|
|
98
101
|
kit.anchor("Crosshair", { anchor: "center", direction: "column", cross: "center", gap: 6 }, () => {
|
|
99
102
|
bar("N", 2, 8);
|
|
@@ -118,20 +121,20 @@ export function shooterHud(id: string, desc: Api.ShooterHudDesc = {}): Api.HudRe
|
|
|
118
121
|
});
|
|
119
122
|
if (desc.map && !showTouch) kit.anchor(`${id}.map`, { anchor: "topLeft" }, () => kit.minimap("Map", desc.map!));
|
|
120
123
|
kit.anchor(`${id}.top`, { anchor: "topRight", gap: t.sm, cross: "center" }, () => {
|
|
121
|
-
|
|
124
|
+
hudBox("Kills", () => {
|
|
122
125
|
kit.label("Count", fmt(desc.kills ?? 0), { font: "mono", nowrap: true });
|
|
123
|
-
kit.label("Unit", "kills", { step: "meta", color: token("
|
|
126
|
+
kit.label("Unit", desc.killsLabel ?? "kills", { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
124
127
|
});
|
|
125
|
-
pauseButton(out);
|
|
128
|
+
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
126
129
|
});
|
|
127
130
|
kit.anchor(`${id}.ammo`, { anchor: showTouch ? "topRight" : "bottomRight", offsetY: showTouch ? 56 : 0, direction: "column", cross: "end", gap: t.xs }, () => {
|
|
128
131
|
const mag = desc.magazine ?? 0;
|
|
129
132
|
const ammo = desc.ammo ?? 0;
|
|
130
133
|
kit.row("Rounds", { gap: t.xs, cross: "end" }, () => {
|
|
131
|
-
kit.label("Ammo", `${ammo}`, { step: "xl5", font: "mono", color: token(mag > 0 && ammo <= Math.floor(mag / 4) ? "warn" : "
|
|
132
|
-
kit.label("Reserve", `/ ${desc.reserve ?? 0}`, { step: "lg", font: "mono", color: token("
|
|
134
|
+
kit.label("Ammo", `${ammo}`, { step: "xl5", font: "mono", color: token(mag > 0 && ammo <= Math.floor(mag / 4) ? "warn" : "ink"), nowrap: true });
|
|
135
|
+
kit.label("Reserve", `/ ${desc.reserve ?? 0}`, { step: "lg", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
133
136
|
});
|
|
134
|
-
if (desc.weapon) kit.label("Weapon", desc.weapon, { step: "meta", color: token("
|
|
137
|
+
if (desc.weapon) kit.label("Weapon", desc.weapon, { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
135
138
|
});
|
|
136
139
|
const abilities = desc.abilities ?? [];
|
|
137
140
|
if (abilities.length > 0) {
|
|
@@ -144,7 +147,7 @@ export function shooterHud(id: string, desc: Api.ShooterHudDesc = {}): Api.HudRe
|
|
|
144
147
|
});
|
|
145
148
|
}
|
|
146
149
|
if (desc.crosshair ?? true) crosshair();
|
|
147
|
-
if (showTouch) touchControls(out, { buttons: [{ id: "fire", label: "Fire", primary: true }] });
|
|
150
|
+
if (showTouch) touchControls(out, { buttons: [{ id: "fire", label: desc.fireLabel ?? "Fire", primary: true }] });
|
|
148
151
|
return out;
|
|
149
152
|
}
|
|
150
153
|
|
|
@@ -155,22 +158,22 @@ export function puzzleHud(id: string, desc: Api.PuzzleHudDesc = {}): Api.HudResu
|
|
|
155
158
|
const score = desc.score ?? 0;
|
|
156
159
|
const marks = desc.stars && desc.stars.length > 0 ? desc.stars : [Math.round(target / 3), Math.round((2 * target) / 3), target];
|
|
157
160
|
kit.anchor(`${id}.left`, { anchor: "topLeft", direction: "column", gap: t.xs }, () => {
|
|
158
|
-
if (desc.level) kit.label("Level", desc.level, { step: "meta", color: token("
|
|
159
|
-
if (desc.moves !== null && desc.moves !== undefined) stat("Moves", `${desc.moves}`, "moves left", "xl3");
|
|
161
|
+
if (desc.level) kit.label("Level", desc.level, { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
162
|
+
if (desc.moves !== null && desc.moves !== undefined) stat("Moves", `${desc.moves}`, desc.movesLabel ?? "moves left", "xl3");
|
|
160
163
|
});
|
|
161
164
|
kit.anchor(`${id}.score`, { anchor: "top", direction: "column", cross: "center", gap: t.xs, width: px(260) }, () => {
|
|
162
165
|
kit.label("Score", fmt(score), { step: "displaySm", font: "display", align: "center", width: grow() });
|
|
163
|
-
kit.meter("Progress", score, target, { kind: "
|
|
166
|
+
kit.meter("Progress", score, target, { kind: "primary", width: grow(), segments: 3 });
|
|
164
167
|
kit.row("Stars", { gap: t.sm, cross: "center" }, () => {
|
|
165
|
-
marks.slice(0, 3).forEach((m, i) => iconOr(kit.idi("Star", i), "star", 20, token(score >= m ? "
|
|
166
|
-
kit.label("Target",
|
|
168
|
+
marks.slice(0, 3).forEach((m, i) => iconOr(kit.idi("Star", i), "star", 20, token(score >= m ? "primary" : "controlStrong")));
|
|
169
|
+
kit.label("Target", (desc.targetLabel ?? "Target {target}").replaceAll("{target}", fmt(target)), { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
167
170
|
});
|
|
168
171
|
});
|
|
169
172
|
kit.anchor(`${id}.right`, { anchor: "topRight", gap: t.sm, cross: "center" }, () => {
|
|
170
173
|
if (desc.timeLeft !== null && desc.timeLeft !== undefined) {
|
|
171
|
-
|
|
174
|
+
hudBox("Timer", () => kit.label("Time", clock(desc.timeLeft!), { font: "mono", color: token(desc.timeLeft! <= 10 ? "danger" : "ink"), nowrap: true }));
|
|
172
175
|
}
|
|
173
|
-
pauseButton(out);
|
|
176
|
+
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
174
177
|
});
|
|
175
178
|
return out;
|
|
176
179
|
}
|
|
@@ -182,13 +185,13 @@ export function idleHud(id: string, desc: Api.IdleHudDesc = {}): Api.HudResult {
|
|
|
182
185
|
const generators = desc.generators ?? [];
|
|
183
186
|
kit.anchor(`${id}.wallet`, { anchor: "top", gap: t.sm, cross: "center" }, () => {
|
|
184
187
|
currencies.forEach((c, i) => {
|
|
185
|
-
|
|
186
|
-
if (c.icon) iconOr("Icon", c.icon, 20, token("
|
|
188
|
+
hudBox(c.id ?? kit.idi("Currency", i), () => {
|
|
189
|
+
if (c.icon) iconOr("Icon", c.icon, 20, token("primary"));
|
|
187
190
|
kit.label("Amount", fmt(c.amount ?? 0), { step: i === 0 ? "lg" : "body", font: "mono", nowrap: true });
|
|
188
|
-
if ((c.perSecond ?? 0) !== 0) kit.label("Rate",
|
|
191
|
+
if ((c.perSecond ?? 0) !== 0) kit.label("Rate", (desc.rateLabel ?? "+{rate}/s").replaceAll("{rate}", fmt(c.perSecond ?? 0)), { step: "meta", color: token("success"), nowrap: true });
|
|
189
192
|
}, { height: i === 0 ? t.controlLg : t.controlMd });
|
|
190
193
|
});
|
|
191
|
-
pauseButton(out);
|
|
194
|
+
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
192
195
|
});
|
|
193
196
|
if (desc.tapLabel !== "") {
|
|
194
197
|
kit.anchor(`${id}.tap`, { anchor: phone ? "top" : "center", offsetY: phone ? 96 : 0, offsetX: phone ? 0 : -150 }, () => {
|
|
@@ -201,13 +204,13 @@ export function idleHud(id: string, desc: Api.IdleHudDesc = {}): Api.HudResult {
|
|
|
201
204
|
kit.panel("Generators", { width: grow(), padding: all(t.sm), gap: t.xs }, () => {
|
|
202
205
|
kit.list("List", { count: generators.length, rowHeight: 64, gap: t.xs, height: fit(0, listHeight), width: grow() }, (i) => {
|
|
203
206
|
const g = generators[i]!;
|
|
204
|
-
if (g.icon) iconOr("Icon", g.icon, 32, token("
|
|
207
|
+
if (g.icon) iconOr("Icon", g.icon, 32, token("ink"));
|
|
205
208
|
kit.column("Text", { width: grow(), gap: t.xxs }, () => {
|
|
206
209
|
kit.row("Head", { gap: t.sm, cross: "center", width: grow() }, () => {
|
|
207
210
|
kit.label("Name", g.name ?? g.id ?? "", { step: "lg", nowrap: true, width: grow() });
|
|
208
|
-
kit.badge("Level",
|
|
211
|
+
kit.badge("Level", (desc.levelLabel ?? "Lv {level}").replaceAll("{level}", `${g.level ?? 0}`), "neutral");
|
|
209
212
|
});
|
|
210
|
-
kit.meter("Cycle", g.progress ?? 0, 1, { kind: "
|
|
213
|
+
kit.meter("Cycle", g.progress ?? 0, 1, { kind: "primary", height: 4, width: grow() });
|
|
211
214
|
});
|
|
212
215
|
const buy = kit.button("Buy", fmt(g.cost ?? 0), { size: "small", variant: g.affordable ? "primary" : "secondary", disabled: !g.affordable }) as Api.Response;
|
|
213
216
|
if (buy.clicked) out.action = `buy:${g.id ?? i}`;
|
|
@@ -226,7 +229,7 @@ function deltaText(seconds: number): string {
|
|
|
226
229
|
|
|
227
230
|
/** A delta's ink: ahead (negative) success, behind danger, even muted. */
|
|
228
231
|
function deltaInk(seconds: number): Api.Paint {
|
|
229
|
-
return token(seconds < 0 ? "success" : seconds > 0 ? "danger" : "
|
|
232
|
+
return token(seconds < 0 ? "success" : seconds > 0 ? "danger" : "inkMuted");
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResult {
|
|
@@ -241,7 +244,7 @@ export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResu
|
|
|
241
244
|
if (racers > 0) {
|
|
242
245
|
kit.row("Position", { gap: t.xxs, cross: "end" }, () => {
|
|
243
246
|
kit.label("Place", `${desc.position ?? 1}`, { step: "displayXl", font: "display", nowrap: true });
|
|
244
|
-
kit.label("Of", `/${racers}`, { step: "xl", font: "mono", color: token("
|
|
247
|
+
kit.label("Of", `/${racers}`, { step: "xl", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
245
248
|
});
|
|
246
249
|
}
|
|
247
250
|
stat("Lap", `${Math.min(desc.lap ?? 1, desc.laps ?? 3)}/${desc.laps ?? 3}`, desc.lapLabel ?? "lap", "xl3");
|
|
@@ -254,16 +257,17 @@ export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResu
|
|
|
254
257
|
const delta = desc.delta ?? null;
|
|
255
258
|
if (delta !== null) kit.label("Delta", deltaText(delta), { step: "lg", font: "mono", color: deltaInk(delta), nowrap: true });
|
|
256
259
|
if (desc.best !== null && desc.best !== undefined) {
|
|
257
|
-
|
|
260
|
+
// A finished lap: m:ss.mmm as lapTime cuts it (30.717 s is 0:30.717, where the running clock's floor reads 0:30.716).
|
|
261
|
+
kit.label("Best", (desc.bestLabel ?? "Best {best}").replaceAll("{best}", lapTime(desc.best)), { step: "meta", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
258
262
|
}
|
|
259
263
|
const splits = desc.splits ?? [];
|
|
260
264
|
if (splits.length > 0) {
|
|
261
265
|
kit.column("Splits", { gap: 0, cross: "end" }, () => {
|
|
262
266
|
splits.forEach((sp, i) => {
|
|
263
267
|
kit.row(kit.idi("Split", i), { gap: t.sm, cross: "center" }, () => {
|
|
264
|
-
kit.label("Label", sp.label ?? "", { step: "meta", color: token("
|
|
268
|
+
kit.label("Label", sp.label ?? "", { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
265
269
|
const time = sp.time ?? null;
|
|
266
|
-
kit.label("Time", time === null ? "-:--.---" : lapTime(time), { step: "meta", font: "mono", color: token(time === null ? "
|
|
270
|
+
kit.label("Time", time === null ? "-:--.---" : lapTime(time), { step: "meta", font: "mono", color: token(time === null ? "inkFaint" : "ink"), nowrap: true });
|
|
267
271
|
const d = sp.delta ?? null;
|
|
268
272
|
if (d !== null) kit.label("Delta", deltaText(d), { step: "meta", font: "mono", color: deltaInk(d), nowrap: true });
|
|
269
273
|
});
|
|
@@ -276,11 +280,11 @@ export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResu
|
|
|
276
280
|
kit.row("Speed", { gap: t.sm, cross: "end", width: grow(), align: "end" }, () => {
|
|
277
281
|
kit.label("Value", `${Math.round(desc.speed ?? 0)}`, { step: "displayXl2", font: "display", nowrap: true });
|
|
278
282
|
kit.column("Side", { cross: "end" }, () => {
|
|
279
|
-
kit.label("Gear", desc.gear ?? "N", { step: "xl", font: "mono", color: token("
|
|
280
|
-
kit.label("Unit", desc.unit ?? "km/h", { step: "meta", color: token("
|
|
283
|
+
kit.label("Gear", desc.gear ?? "N", { step: "xl", font: "mono", color: token("highlight"), nowrap: true });
|
|
284
|
+
kit.label("Unit", desc.unit ?? "km/h", { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
281
285
|
});
|
|
282
286
|
});
|
|
283
|
-
kit.meter("Rev", desc.speed ?? 0, desc.maxSpeed ?? 300, { kind: "
|
|
287
|
+
kit.meter("Rev", desc.speed ?? 0, desc.maxSpeed ?? 300, { kind: "primary", width: grow(), height: 6 });
|
|
284
288
|
if (boost !== null) kit.meter("Boost", boost, 1, { color: token("info"), label: boostLabel, width: grow(), segments: 4 });
|
|
285
289
|
});
|
|
286
290
|
if (showTouch) {
|
package/src/ui-kit/menus.ts
CHANGED
|
@@ -36,17 +36,17 @@ export function mainMenu(id: string, desc: Api.MainMenuDesc = {}): Api.MenuResul
|
|
|
36
36
|
cross: "center",
|
|
37
37
|
width: phone ? grow() : px(380),
|
|
38
38
|
padding: all(phone ? t.lg : t.xl),
|
|
39
|
-
background: token("
|
|
39
|
+
background: token("hud"),
|
|
40
40
|
radius: t.panel,
|
|
41
41
|
instant: desc.instant ?? false,
|
|
42
42
|
}, () => {
|
|
43
43
|
kit.column("Heading", { gap: t.xs, cross: "center", width: grow() }, () => {
|
|
44
44
|
kit.label("Title", desc.title ?? "", { step: phone ? "displayMd" : "displayLg", font: "display", align: "center", width: grow(), heading: true });
|
|
45
|
-
if (desc.subtitle) kit.label("Subtitle", desc.subtitle, { color: token("
|
|
45
|
+
if (desc.subtitle) kit.label("Subtitle", desc.subtitle, { color: token("inkMuted"), align: "center", width: grow() });
|
|
46
46
|
});
|
|
47
47
|
// A title menu is not a dialog, so it takes the focus itself: Enter and the pad's A answer at once.
|
|
48
48
|
chosen = menuColumn("Items", items, grow(), true);
|
|
49
|
-
if (desc.footer) kit.label("Footer", desc.footer, { step: "meta", color: token("
|
|
49
|
+
if (desc.footer) kit.label("Footer", desc.footer, { step: "meta", color: token("inkFaint"), align: "center", width: grow() });
|
|
50
50
|
});
|
|
51
51
|
if (chosen !== null) desc.onSelect?.(chosen);
|
|
52
52
|
return { selected: chosen };
|
|
@@ -68,7 +68,7 @@ export function confirmDialog(id: string, desc: Api.ConfirmDesc = {}): Api.Confi
|
|
|
68
68
|
let confirmed = false;
|
|
69
69
|
let cancelled = false;
|
|
70
70
|
const m = kit.modal(id, desc.open ?? false, { title: desc.title ?? "" }, () => {
|
|
71
|
-
if (desc.message) kit.richText("Message", desc.message, { step: "reading", color: token("
|
|
71
|
+
if (desc.message) kit.richText("Message", desc.message, { step: "reading", color: token("inkMuted"), width: grow() });
|
|
72
72
|
kit.row("Actions", { width: grow(), gap: t.sm, align: "end", padding: { left: 0, top: t.sm, right: 0, bottom: 0 } }, () => {
|
|
73
73
|
if ((kit.button("Cancel", desc.cancelLabel ?? "Cancel", {}) as Api.Response).clicked) cancelled = true;
|
|
74
74
|
if ((kit.button("Confirm", desc.confirmLabel ?? "Confirm", { variant: desc.danger ? "danger" : "primary" }) as Api.Response).clicked) confirmed = true;
|
|
@@ -82,7 +82,7 @@ export function alertDialog(id: string, desc: Api.AlertDesc = {}): boolean {
|
|
|
82
82
|
const { t } = frame();
|
|
83
83
|
let done = false;
|
|
84
84
|
const m = kit.modal(id, desc.open ?? false, { title: desc.title ?? "" }, () => {
|
|
85
|
-
if (desc.message) kit.richText("Message", desc.message, { step: "reading", color: token("
|
|
85
|
+
if (desc.message) kit.richText("Message", desc.message, { step: "reading", color: token("inkMuted"), width: grow() });
|
|
86
86
|
kit.row("Actions", { width: grow(), align: "end", padding: { left: 0, top: t.sm, right: 0, bottom: 0 } }, () => {
|
|
87
87
|
if ((kit.button("Ok", desc.okLabel ?? "OK", { variant: "primary" }) as Api.Response).clicked) done = true;
|
|
88
88
|
});
|
|
@@ -94,7 +94,7 @@ export function loadingScreen(id: string, desc: Api.LoadingDesc = {}): void {
|
|
|
94
94
|
const { t } = frame();
|
|
95
95
|
const tips = desc.tips ?? [];
|
|
96
96
|
const tip = tips.length > 0 ? tips[Math.floor(kit.seconds() / Math.max(1, desc.tipSeconds ?? 4)) % tips.length]! : "";
|
|
97
|
-
kit.modal(id, true, { locked: true, bare: true, backdrop: token("
|
|
97
|
+
kit.modal(id, true, { locked: true, bare: true, backdrop: token("backdrop"), transition: "fade", width: px(Math.min(380, frame().viewport.width - 32)) }, () => {
|
|
98
98
|
kit.label("Title", desc.title ?? "Loading", { step: "xl", align: "center", width: grow(), heading: true });
|
|
99
99
|
const p = desc.progress;
|
|
100
100
|
if (p === null || p === undefined) {
|
|
@@ -103,10 +103,10 @@ export function loadingScreen(id: string, desc: Api.LoadingDesc = {}): void {
|
|
|
103
103
|
// A real value only: the bar never runs ahead of what loaded.
|
|
104
104
|
kit.row("Bar", { width: grow(), gap: t.md, cross: "center" }, () => {
|
|
105
105
|
kit.progress("Progress", p, { width: grow() });
|
|
106
|
-
kit.label("Percent", `${Math.floor(Math.max(0, Math.min(1, p)) * 100)}%`, { step: "meta", font: "mono", color: token("
|
|
106
|
+
kit.label("Percent", `${Math.floor(Math.max(0, Math.min(1, p)) * 100)}%`, { step: "meta", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
-
if (tip) kit.label("Tip", tip, { step: "reading", color: token("
|
|
109
|
+
if (tip) kit.label("Tip", tip, { step: "reading", color: token("inkMuted"), align: "center", width: grow() });
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -118,7 +118,7 @@ function stars(id: string, earned: number, max: number, since: number, size: num
|
|
|
118
118
|
const shown = kit.seconds() - since >= 0.3 * (k + 1);
|
|
119
119
|
const on = k < earned;
|
|
120
120
|
kit.transition(kit.idi("Star", k), shown, { kind: "scale", instant: false }, () => {
|
|
121
|
-
iconOr("Mark", "star", size, token(on ? "
|
|
121
|
+
iconOr("Mark", "star", size, token(on ? "primary" : "controlStrong"));
|
|
122
122
|
if (on) kit.burst({ delayMs: 120 });
|
|
123
123
|
});
|
|
124
124
|
if (!shown) kit.spacer(kit.idi("Gap", k), px(size));
|
|
@@ -147,7 +147,7 @@ export function resultsScreen(id: string, desc: Api.ResultsDesc = {}): Api.MenuR
|
|
|
147
147
|
const max = desc.maxStars ?? 3;
|
|
148
148
|
if (max > 0) kit.row("StarRow", { width: grow(), align: "center" }, () => stars("Stars", desc.stars ?? 0, max, since, 44));
|
|
149
149
|
kit.column("ScoreBlock", { width: grow(), cross: "center", gap: t.xxs }, () => {
|
|
150
|
-
if (desc.scoreLabel) kit.label("ScoreLabel", desc.scoreLabel, { step: "meta", color: token("
|
|
150
|
+
if (desc.scoreLabel) kit.label("ScoreLabel", desc.scoreLabel, { step: "meta", color: token("inkMuted"), align: "center", width: grow(), nowrap: true });
|
|
151
151
|
if (time) {
|
|
152
152
|
// A time counts up as a clock (ease-out cubic over the same 0.9 s), the headline in m:ss.mmm.
|
|
153
153
|
const k = open ? Math.min(1, Math.max(0, (kit.seconds() - since) / 0.9)) : 1;
|
|
@@ -158,8 +158,8 @@ export function resultsScreen(id: string, desc: Api.ResultsDesc = {}): Api.MenuR
|
|
|
158
158
|
}
|
|
159
159
|
if (!time || best > 0 || desc.newBest) {
|
|
160
160
|
kit.row("BestRow", { gap: t.sm, cross: "center" }, () => {
|
|
161
|
-
if (!time || best > 0) kit.label("Best", (desc.bestLabel ?? "Best {best}").replaceAll("{best}", time ? lapTime(best) : fmt(best)), { step: "meta", color: token("
|
|
162
|
-
if (desc.newBest) kit.transition("Reveal", landed, { kind: "pop" }, () => kit.badge("NewBest", desc.newBestLabel ?? "New best", "
|
|
161
|
+
if (!time || best > 0) kit.label("Best", (desc.bestLabel ?? "Best {best}").replaceAll("{best}", time ? lapTime(best) : fmt(best)), { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
162
|
+
if (desc.newBest) kit.transition("Reveal", landed, { kind: "pop" }, () => kit.badge("NewBest", desc.newBestLabel ?? "New best", "primary"));
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
});
|
package/src/ui-kit/settings.ts
CHANGED
|
@@ -40,7 +40,7 @@ function sliderRow(id: string, label: string, value: number, desc: Api.SliderDes
|
|
|
40
40
|
kit.label("Label", label, { width: px(phone ? 96 : 140), nowrap: true });
|
|
41
41
|
const r = kit.slider("Slider", value, desc) as Api.SliderResult;
|
|
42
42
|
out = r.value;
|
|
43
|
-
kit.label("Value", shown, { width: px(48), align: "end", font: "mono", color: token("
|
|
43
|
+
kit.label("Value", shown, { width: px(48), align: "end", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
44
44
|
});
|
|
45
45
|
return out;
|
|
46
46
|
}
|
|
@@ -82,7 +82,7 @@ export function settingsScreen(id: string, desc: Api.SettingsDesc = {}): Api.Set
|
|
|
82
82
|
}
|
|
83
83
|
case "graphics": {
|
|
84
84
|
const quality = desc.qualityLabel ?? "Quality";
|
|
85
|
-
kit.label("QualityLabel", quality, { step: "meta", color: token("
|
|
85
|
+
kit.label("QualityLabel", quality, { step: "meta", color: token("inkMuted") });
|
|
86
86
|
const levels = [desc.lowLabel ?? "Low", desc.mediumLabel ?? "Medium", desc.highLabel ?? "High"];
|
|
87
87
|
const q = kit.radio("Quality", levels, QUALITIES.indexOf(s.quality), { horizontal: !phone, label: quality });
|
|
88
88
|
s.quality = QUALITIES[q.selected] ?? "medium";
|
package/src/ui-kit/store.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type * as Api from "@origingame/engine";
|
|
6
6
|
import { kit } from "./bind.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { hudBox, fit, fmt, frame, grow, iconOr, px, token } from "./util.ts";
|
|
8
8
|
|
|
9
9
|
/** A screen panel: a dialog over the game, wide on desktop, full width on a phone. With `scrim` a press on the backdrop closes it too. */
|
|
10
10
|
function screen(id: string, title: string, width: number, build: () => void, scrim = false): boolean {
|
|
@@ -16,7 +16,7 @@ function screen(id: string, title: string, width: number, build: () => void, scr
|
|
|
16
16
|
/** An item's picture: its icon, else its initial on the slot. */
|
|
17
17
|
function picture(id: string, icon: string | undefined, name: string, size: number): void {
|
|
18
18
|
if (icon && kit.hasIcon(icon)) kit.icon(id, icon, { size });
|
|
19
|
-
else kit.label(id, name.slice(0, 1).toUpperCase(), { step: "xl2", font: "display", color: token("
|
|
19
|
+
else kit.label(id, name.slice(0, 1).toUpperCase(), { step: "xl2", font: "display", color: token("inkMuted"), nowrap: true });
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function shopScreen(id: string, desc: Api.ShopDesc = {}): Api.ShopResult {
|
|
@@ -37,10 +37,10 @@ export function shopScreen(id: string, desc: Api.ShopDesc = {}): Api.ShopResult
|
|
|
37
37
|
} else {
|
|
38
38
|
kit.spacer("Gap", null);
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
if (desc.currencyIcon) iconOr("Icon", desc.currencyIcon, 18, token("
|
|
40
|
+
hudBox("Balance", () => {
|
|
41
|
+
if (desc.currencyIcon) iconOr("Icon", desc.currencyIcon, 18, token("gold"));
|
|
42
42
|
kit.counter("Amount", balance, { font: "mono", step: "base" });
|
|
43
|
-
kit.label("Unit", desc.currency ?? "coins", { step: "base", color: token("
|
|
43
|
+
kit.label("Unit", desc.currency ?? "coins", { step: "base", color: token("inkMuted"), nowrap: true });
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
const body = (): void => {
|
|
@@ -49,17 +49,17 @@ export function shopScreen(id: string, desc: Api.ShopDesc = {}): Api.ShopResult
|
|
|
49
49
|
picture("Picture", it.icon, it.name ?? "", 36);
|
|
50
50
|
kit.label("Name", it.name ?? "", { step: "base", nowrap: true });
|
|
51
51
|
if (it.owned) kit.badge("Owned", desc.ownedLabel ?? "Owned", "success");
|
|
52
|
-
else kit.label("Price", fmt(it.price ?? 0), { step: "meta", font: "mono", color: token(balance >= (it.price ?? 0) ? "
|
|
52
|
+
else kit.label("Price", fmt(it.price ?? 0), { step: "meta", font: "mono", color: token(balance >= (it.price ?? 0) ? "highlight" : "inkFaint"), nowrap: true });
|
|
53
53
|
});
|
|
54
54
|
if (g.clicked !== null && g.clicked !== undefined) sel = g.clicked;
|
|
55
55
|
const it = items[sel];
|
|
56
56
|
kit.column("Detail", { width: phone ? grow() : px(220), gap: t.sm }, () => {
|
|
57
57
|
if (!it) {
|
|
58
|
-
kit.label("Empty", desc.emptyLabel ?? "Nothing for sale here", { color: token("
|
|
58
|
+
kit.label("Empty", desc.emptyLabel ?? "Nothing for sale here", { color: token("inkMuted") });
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
kit.label("Name", it.name ?? "", { step: "lg", width: grow() });
|
|
62
|
-
if (it.description) kit.label("Description", it.description, { step: "meta", color: token("
|
|
62
|
+
if (it.description) kit.label("Description", it.description, { step: "meta", color: token("inkMuted"), width: grow() });
|
|
63
63
|
kit.keyValue("Price", desc.priceLabel ?? "Price", `${fmt(it.price ?? 0)} ${desc.currency ?? "coins"}`, true);
|
|
64
64
|
const short = balance < (it.price ?? 0);
|
|
65
65
|
// Short of the price, Buy still answers: it refuses, shaking with the error sound.
|
|
@@ -101,7 +101,7 @@ export function inventoryScreen(id: string, desc: Api.InventoryDesc = {}): Api.I
|
|
|
101
101
|
const it = bySlot.get(i);
|
|
102
102
|
if (!it) return;
|
|
103
103
|
picture("Picture", it.icon, it.name ?? "", cell * 0.5);
|
|
104
|
-
if ((it.count ?? 0) > 1) kit.label("Count", `${it.count}`, { step: "xs", font: "mono", color: token("
|
|
104
|
+
if ((it.count ?? 0) > 1) kit.label("Count", `${it.count}`, { step: "xs", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
105
105
|
});
|
|
106
106
|
if (g.clicked !== null && g.clicked !== undefined) sel = g.clicked;
|
|
107
107
|
if (g.dropFrom !== null && g.dropFrom !== undefined && g.dropTo !== null && g.dropTo !== undefined && bySlot.has(g.dropFrom)) {
|
|
@@ -111,11 +111,11 @@ export function inventoryScreen(id: string, desc: Api.InventoryDesc = {}): Api.I
|
|
|
111
111
|
const it = sel >= 0 ? bySlot.get(sel) : undefined;
|
|
112
112
|
kit.column("Detail", { width: phone ? grow() : px(200), gap: t.sm }, () => {
|
|
113
113
|
if (!it) {
|
|
114
|
-
kit.label("Hint", desc.hintLabel ?? "Select an item. Drag it to another slot to move it.", { step: "meta", color: token("
|
|
114
|
+
kit.label("Hint", desc.hintLabel ?? "Select an item. Drag it to another slot to move it.", { step: "meta", color: token("inkMuted"), width: grow() });
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
kit.label("Name", it.name ?? "", { step: "lg", width: grow() });
|
|
118
|
-
if (it.description) kit.label("Description", it.description, { step: "meta", color: token("
|
|
118
|
+
if (it.description) kit.label("Description", it.description, { step: "meta", color: token("inkMuted"), width: grow() });
|
|
119
119
|
if ((it.count ?? 0) > 1) kit.keyValue("Count", desc.countLabel ?? "Count", `${it.count}`, true);
|
|
120
120
|
if (desc.useLabel !== "") {
|
|
121
121
|
if ((kit.button("Use", desc.useLabel ?? "Use", { variant: "primary", width: grow() }) as Api.Response).clicked) out.used = it.id ?? null;
|
|
@@ -143,8 +143,8 @@ export function levelSelect(id: string, desc: Api.LevelSelectDesc = {}): Api.Lev
|
|
|
143
143
|
// Back from every input: the button, a press on the scrim, Escape, B.
|
|
144
144
|
out.closed = screen(id, desc.title ?? "Levels", 720, () => {
|
|
145
145
|
kit.row("Total", { gap: t.xs, cross: "center" }, () => {
|
|
146
|
-
iconOr("Star", "star", 18, token("
|
|
147
|
-
kit.label("Count", `${earned} / ${levels.length * 3}`, { font: "mono", color: token("
|
|
146
|
+
iconOr("Star", "star", 18, token("primary"), starsLabel(earned, levels.length * 3));
|
|
147
|
+
kit.label("Count", `${earned} / ${levels.length * 3}`, { font: "mono", color: token("inkMuted"), nowrap: true });
|
|
148
148
|
});
|
|
149
149
|
// A numeral is the card's face, in display type on a square card; a
|
|
150
150
|
// name takes a wider card and a smaller step, wrapped to two lines (cut
|
|
@@ -157,14 +157,14 @@ export function levelSelect(id: string, desc: Api.LevelSelectDesc = {}): Api.Lev
|
|
|
157
157
|
const l = levels[i]!;
|
|
158
158
|
const name = l.name ?? `${i + 1}`;
|
|
159
159
|
if (l.locked) {
|
|
160
|
-
iconOr("Lock", "lock", 28, token("
|
|
160
|
+
iconOr("Lock", "lock", 28, token("inkFaint"), (desc.lockedLabel ?? "{name}, locked").replaceAll("{name}", name));
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
if ([...name].length > 3) kit.label("Name", name, { step: phone ? "base" : "lg", font: "display", align: "center", width: grow(), maxLines: 2, ellipsis: true });
|
|
164
164
|
else kit.label("Name", name, { step: "xl3", font: "display", nowrap: true });
|
|
165
165
|
const stars = Math.min(3, l.stars ?? 0);
|
|
166
166
|
kit.row("Stars", { gap: 2 }, () => {
|
|
167
|
-
for (let k = 0; k < 3; k++) iconOr(kit.idi("Star", k), "star", 14, token(k < stars ? "
|
|
167
|
+
for (let k = 0; k < 3; k++) iconOr(kit.idi("Star", k), "star", 14, token(k < stars ? "primary" : "controlStrong"), k === 0 ? starsLabel(stars, 3) : "");
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
if (g.clicked !== null && g.clicked !== undefined && !levels[g.clicked]?.locked) out.chosen = g.clicked;
|
|
@@ -176,7 +176,7 @@ export function levelSelect(id: string, desc: Api.LevelSelectDesc = {}): Api.Lev
|
|
|
176
176
|
return out;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
const MEDALS: readonly Api.ThemeColor[] = ["warn", "
|
|
179
|
+
const MEDALS: readonly Api.ThemeColor[] = ["warn", "inkMuted", "highlight"];
|
|
180
180
|
|
|
181
181
|
export function leaderboardScreen(id: string, desc: Api.LeaderboardDesc = {}): Api.LeaderboardResult {
|
|
182
182
|
const { t, phone } = frame();
|
|
@@ -203,14 +203,14 @@ export function leaderboardScreen(id: string, desc: Api.LeaderboardDesc = {}): A
|
|
|
203
203
|
});
|
|
204
204
|
} else if (entries.length === 0) {
|
|
205
205
|
kit.column("Empty", { width: grow(), height, align: "center", cross: "center" }, () => {
|
|
206
|
-
kit.label("Message", desc.empty ?? "No scores yet", { color: token("
|
|
206
|
+
kit.label("Message", desc.empty ?? "No scores yet", { color: token("inkMuted"), align: "center", width: grow() });
|
|
207
207
|
});
|
|
208
208
|
} else {
|
|
209
209
|
const player = entries.findIndex((e) => e.player);
|
|
210
210
|
kit.list("Rows", { count: entries.length, rowHeight: t.row, height, width: grow(), selected: player >= 0 ? player : null }, (i) => {
|
|
211
211
|
const e = entries[i]!;
|
|
212
212
|
const rank = e.rank ?? i + 1;
|
|
213
|
-
kit.label("Rank", `${rank}`, { width: px(40), font: "mono", step: rank <= 3 ? "lg" : "body", color: token(rank <= 3 ? MEDALS[rank - 1]! : "
|
|
213
|
+
kit.label("Rank", `${rank}`, { width: px(40), font: "mono", step: rank <= 3 ? "lg" : "body", color: token(rank <= 3 ? MEDALS[rank - 1]! : "inkMuted"), nowrap: true });
|
|
214
214
|
kit.label("Name", e.name ?? "", { width: grow(), nowrap: true });
|
|
215
215
|
kit.label("Score", e.score ?? "", { font: "mono", nowrap: true });
|
|
216
216
|
});
|
package/src/ui-kit/story.ts
CHANGED
|
@@ -15,8 +15,10 @@ export function cutscene(id: string, desc: Api.CutsceneDesc = {}): Api.CutsceneR
|
|
|
15
15
|
kit.setNavigation(true);
|
|
16
16
|
const letterbox = desc.letterbox ?? true;
|
|
17
17
|
if (letterbox) {
|
|
18
|
+
// Cinematic bars are black in every style: not a token, which a light
|
|
19
|
+
// style (kestrel's paper) would turn into cream panels.
|
|
18
20
|
for (const [name, anchor] of [["Top", "top"], ["Bottom", "bottom"]] as const) {
|
|
19
|
-
kit.anchor(`${id}.${name}`, { anchor, margin: 0, width: grow(), height: pct(0.1), background:
|
|
21
|
+
kit.anchor(`${id}.${name}`, { anchor, margin: 0, width: grow(), height: pct(0.1), background: { color: { r: 0, g: 0, b: 0 } } }, () => {});
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
const line = lines[index]!;
|
|
@@ -68,13 +70,13 @@ export function tutorialOverlay(id: string, desc: Api.TutorialDesc = {}): Api.Tu
|
|
|
68
70
|
direction: "column",
|
|
69
71
|
gap: t.sm,
|
|
70
72
|
padding: all(t.lg),
|
|
71
|
-
background: token("
|
|
73
|
+
background: token("panelRaised"),
|
|
72
74
|
radius: t.panel,
|
|
73
75
|
}, () => {
|
|
74
76
|
const count = (desc.stepLabel ?? "{step} of {count}").replaceAll("{step}", `${index + 1}`).replaceAll("{count}", `${steps.length}`);
|
|
75
|
-
kit.label("Count", count, { step: "meta", color: token("
|
|
77
|
+
kit.label("Count", count, { step: "meta", color: token("inkMuted") });
|
|
76
78
|
if (step.title) kit.label("Title", step.title, { step: "lg", width: grow() });
|
|
77
|
-
if (step.text) kit.label("Text", step.text, { step: "reading", color: token("
|
|
79
|
+
if (step.text) kit.label("Text", step.text, { step: "reading", color: token("inkMuted"), width: grow() });
|
|
78
80
|
kit.row("Actions", { width: grow(), gap: t.sm, align: "spaceBetween", cross: "center" }, () => {
|
|
79
81
|
if ((kit.button("Skip", desc.skipLabel ?? "Skip", { variant: "quiet", size: "small" }) as Api.Response).clicked) out.skipped = true;
|
|
80
82
|
const lastStep = index + 1 >= steps.length;
|
package/src/ui-kit/util.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Small helpers the screen templates share: sizes, insets, the frame's
|
|
2
|
-
// tokens, number and time formats, and HUD
|
|
2
|
+
// tokens, number and time formats, and HUD boxes. Nothing here keeps
|
|
3
3
|
// state: what must persist lives in the UI's memory (og.ui.memory).
|
|
4
4
|
|
|
5
5
|
import type * as Api from "@origingame/engine";
|
|
@@ -25,7 +25,7 @@ export function frame(): { theme: Api.UiTheme; viewport: Api.Viewport; t: Tokens
|
|
|
25
25
|
|
|
26
26
|
export interface Tokens {
|
|
27
27
|
xxs: number; xs: number; sm: number; md: number; lg: number; xl: number; xxl: number; xl3: number;
|
|
28
|
-
|
|
28
|
+
small: number; control: number; bar: number; panel: number;
|
|
29
29
|
controlSm: number; controlMd: number; controlLg: number; gutter: number; row: number;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -34,7 +34,7 @@ function tokens(t: Api.UiTheme): Tokens {
|
|
|
34
34
|
const r = (k: string): number => t.radius[k] ?? 0;
|
|
35
35
|
return {
|
|
36
36
|
xxs: s("xxs"), xs: s("xs"), sm: s("sm"), md: s("md"), lg: s("lg"), xl: s("xl"), xxl: s("xxl"), xl3: s("xl3"),
|
|
37
|
-
|
|
37
|
+
small: r("small"), control: r("control"), bar: r("bar"), panel: r("panel"),
|
|
38
38
|
controlSm: t.sizes.controlSm, controlMd: t.sizes.controlMd, controlLg: t.sizes.controlLg, gutter: t.sizes.gutter, row: t.sizes.row,
|
|
39
39
|
};
|
|
40
40
|
}
|
|
@@ -73,17 +73,17 @@ export function lapTime(seconds: number): string {
|
|
|
73
73
|
return `${m}:${s.toString().padStart(2, "0")}.${(ms % 1000).toString().padStart(3, "0")}`;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/** A HUD
|
|
77
|
-
export function
|
|
76
|
+
/** A HUD box: the style's `hud` fill and frame, cornered by its control radius, that keeps text legible over any art. */
|
|
77
|
+
export function hudBox(id: string, build: () => void, desc: { gap?: number; padX?: number; height?: number } = {}): Api.Response {
|
|
78
78
|
const { t } = frame();
|
|
79
79
|
return kit.row(id, {
|
|
80
80
|
padding: xy(desc.padX ?? t.md, 0),
|
|
81
81
|
height: px(desc.height ?? t.controlMd),
|
|
82
82
|
gap: desc.gap ?? t.sm,
|
|
83
83
|
cross: "center",
|
|
84
|
-
background: token("
|
|
85
|
-
radius: t.
|
|
86
|
-
frame: "
|
|
84
|
+
background: token("hud"),
|
|
85
|
+
radius: t.control,
|
|
86
|
+
frame: "hud",
|
|
87
87
|
}, build) as Api.Response;
|
|
88
88
|
}
|
|
89
89
|
|