@origingame/engine 0.2.0 → 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 +1 -1
- 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 +19 -10
- package/knowledge/skills/og-build/SKILL.md +8 -5
- package/knowledge/skills/og-core/SKILL.md +11 -0
- package/knowledge/skills/og-game/SKILL.md +877 -8
- package/knowledge/skills/og-platform-sdk/SKILL.md +1 -0
- package/knowledge/skills/og-taste/SKILL.md +82 -73
- package/knowledge/skills/og-ui/SKILL.md +65 -50
- 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 +262 -7
- package/knowledge/wiki/input.md +47 -47
- package/knowledge/wiki/load.md +236 -68
- package/knowledge/wiki/math.md +2607 -0
- 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 +95 -95
- package/knowledge/wiki/platform.md +463 -29
- package/knowledge/wiki/probe.md +681 -67
- package/knowledge/wiki/reflect.md +15 -15
- package/knowledge/wiki/render.md +3893 -1896
- 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 +13 -13
- 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 +559 -629
- package/knowledge/wiki/video.md +157 -0
- package/knowledge/wiki/world.md +36 -36
- package/package.json +4 -3
- package/schema/api.schema.json +21707 -14725
- 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 +25 -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 +13 -3
- package/src/node/render.ts +291 -0
- package/src/node/run.ts +86 -5
- package/src/p2.ts +19 -1
- package/src/playtest.ts +57 -4
- package/src/runtime.ts +13 -3
- package/src/targets.ts +28 -0
- package/src/trace.ts +169 -0
- package/src/ui-kit/huds.ts +34 -34
- 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 +9101 -6647
- package/types/index.d.ts +15503 -11564
package/src/ui-kit/huds.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type * as Api from "@origingame/engine";
|
|
10
10
|
import { kit } from "./bind.ts";
|
|
11
|
-
import { all,
|
|
11
|
+
import { all, hudBox, clock, fit, fmt, frame, grow, iconOr, lapTime, px, token } from "./util.ts";
|
|
12
12
|
|
|
13
13
|
type Hud = { paused: boolean; action: string | null; moveX: number; moveY: number; held: string[] };
|
|
14
14
|
|
|
@@ -27,7 +27,7 @@ function pauseButton(out: Hud, label: string): void {
|
|
|
27
27
|
function stat(id: string, value: string, caption: string, step: Api.TypeStepName = "xl3", align: Api.TextAlign = "start"): void {
|
|
28
28
|
kit.column(id, { gap: 0, cross: align === "end" ? "end" : align === "center" ? "center" : "start" }, () => {
|
|
29
29
|
kit.label("Value", value, { step, font: "mono", nowrap: true, align });
|
|
30
|
-
kit.label("Caption", caption, { step: "meta", color: token("
|
|
30
|
+
kit.label("Caption", caption, { step: "meta", color: token("inkMuted"), nowrap: true, align });
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -58,36 +58,36 @@ export function platformerHud(id: string, desc: Api.PlatformerHudDesc = {}): Api
|
|
|
58
58
|
const { t, touch } = frame();
|
|
59
59
|
const out = hud();
|
|
60
60
|
// What the game leaves out is not drawn: no hearts with maxLives 0 (and
|
|
61
|
-
// no health bar), no coins or score
|
|
61
|
+
// no health bar), no coins or score box without coins or score.
|
|
62
62
|
const health = desc.health ?? null;
|
|
63
63
|
const maxLives = desc.maxLives ?? 3;
|
|
64
64
|
const coins = desc.coins ?? null;
|
|
65
65
|
const score = desc.score ?? null;
|
|
66
66
|
if (health !== null || maxLives > 0 || desc.level) {
|
|
67
67
|
kit.anchor(`${id}.left`, { anchor: "topLeft", direction: "column", gap: t.sm }, () => {
|
|
68
|
-
if (health !== null)
|
|
68
|
+
if (health !== null) hudBox("Lives", () => kit.meter("Health", health, 1, { width: px(120) }));
|
|
69
69
|
else if (maxLives > 0) {
|
|
70
|
-
|
|
71
|
-
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"));
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
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 });
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
if (desc.timeLeft !== null && desc.timeLeft !== undefined) {
|
|
78
78
|
const low = desc.timeLeft <= 10;
|
|
79
79
|
kit.anchor(`${id}.top`, { anchor: "top" }, () => {
|
|
80
|
-
|
|
80
|
+
hudBox("Timer", () => kit.label("Time", clock(desc.timeLeft!), { step: "lg", font: "mono", color: token(low ? "danger" : "ink"), nowrap: true }));
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
kit.anchor(`${id}.right`, { anchor: "topRight", gap: t.sm, cross: "center" }, () => {
|
|
84
84
|
if (coins !== null) {
|
|
85
|
-
|
|
86
|
-
iconOr("Coin", "coin", 20, token("
|
|
85
|
+
hudBox("Coins", () => {
|
|
86
|
+
iconOr("Coin", "coin", 20, token("gold"));
|
|
87
87
|
kit.label("Count", fmt(coins), { font: "mono", nowrap: true });
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
-
if (score !== null)
|
|
90
|
+
if (score !== null) hudBox("Score", () => kit.label("Points", fmt(score), { font: "mono", nowrap: true }));
|
|
91
91
|
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
92
92
|
});
|
|
93
93
|
if ((desc.touch ?? false) && touch) touchControls(out, { dpad: true, buttons: [{ id: "jump", label: desc.jumpLabel ?? "Jump", primary: true }] });
|
|
@@ -96,7 +96,7 @@ export function platformerHud(id: string, desc: Api.PlatformerHudDesc = {}): Api
|
|
|
96
96
|
|
|
97
97
|
/** A crosshair at the centre: four short strokes and a gap. */
|
|
98
98
|
function crosshair(): void {
|
|
99
|
-
const ink = token("
|
|
99
|
+
const ink = token("ink", 0.9);
|
|
100
100
|
const bar = (id: string, w: number, h: number): void => kit.image(id, { tint: ink, width: px(w), height: px(h) });
|
|
101
101
|
kit.anchor("Crosshair", { anchor: "center", direction: "column", cross: "center", gap: 6 }, () => {
|
|
102
102
|
bar("N", 2, 8);
|
|
@@ -121,9 +121,9 @@ export function shooterHud(id: string, desc: Api.ShooterHudDesc = {}): Api.HudRe
|
|
|
121
121
|
});
|
|
122
122
|
if (desc.map && !showTouch) kit.anchor(`${id}.map`, { anchor: "topLeft" }, () => kit.minimap("Map", desc.map!));
|
|
123
123
|
kit.anchor(`${id}.top`, { anchor: "topRight", gap: t.sm, cross: "center" }, () => {
|
|
124
|
-
|
|
124
|
+
hudBox("Kills", () => {
|
|
125
125
|
kit.label("Count", fmt(desc.kills ?? 0), { font: "mono", nowrap: true });
|
|
126
|
-
kit.label("Unit", desc.killsLabel ?? "kills", { step: "meta", color: token("
|
|
126
|
+
kit.label("Unit", desc.killsLabel ?? "kills", { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
127
127
|
});
|
|
128
128
|
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
129
129
|
});
|
|
@@ -131,10 +131,10 @@ export function shooterHud(id: string, desc: Api.ShooterHudDesc = {}): Api.HudRe
|
|
|
131
131
|
const mag = desc.magazine ?? 0;
|
|
132
132
|
const ammo = desc.ammo ?? 0;
|
|
133
133
|
kit.row("Rounds", { gap: t.xs, cross: "end" }, () => {
|
|
134
|
-
kit.label("Ammo", `${ammo}`, { step: "xl5", font: "mono", color: token(mag > 0 && ammo <= Math.floor(mag / 4) ? "warn" : "
|
|
135
|
-
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 });
|
|
136
136
|
});
|
|
137
|
-
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 });
|
|
138
138
|
});
|
|
139
139
|
const abilities = desc.abilities ?? [];
|
|
140
140
|
if (abilities.length > 0) {
|
|
@@ -158,20 +158,20 @@ export function puzzleHud(id: string, desc: Api.PuzzleHudDesc = {}): Api.HudResu
|
|
|
158
158
|
const score = desc.score ?? 0;
|
|
159
159
|
const marks = desc.stars && desc.stars.length > 0 ? desc.stars : [Math.round(target / 3), Math.round((2 * target) / 3), target];
|
|
160
160
|
kit.anchor(`${id}.left`, { anchor: "topLeft", direction: "column", gap: t.xs }, () => {
|
|
161
|
-
if (desc.level) kit.label("Level", desc.level, { step: "meta", color: token("
|
|
161
|
+
if (desc.level) kit.label("Level", desc.level, { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
162
162
|
if (desc.moves !== null && desc.moves !== undefined) stat("Moves", `${desc.moves}`, desc.movesLabel ?? "moves left", "xl3");
|
|
163
163
|
});
|
|
164
164
|
kit.anchor(`${id}.score`, { anchor: "top", direction: "column", cross: "center", gap: t.xs, width: px(260) }, () => {
|
|
165
165
|
kit.label("Score", fmt(score), { step: "displaySm", font: "display", align: "center", width: grow() });
|
|
166
|
-
kit.meter("Progress", score, target, { kind: "
|
|
166
|
+
kit.meter("Progress", score, target, { kind: "primary", width: grow(), segments: 3 });
|
|
167
167
|
kit.row("Stars", { gap: t.sm, cross: "center" }, () => {
|
|
168
|
-
marks.slice(0, 3).forEach((m, i) => iconOr(kit.idi("Star", i), "star", 20, token(score >= m ? "
|
|
169
|
-
kit.label("Target", (desc.targetLabel ?? "Target {target}").replaceAll("{target}", fmt(target)), { step: "meta", color: token("
|
|
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 });
|
|
170
170
|
});
|
|
171
171
|
});
|
|
172
172
|
kit.anchor(`${id}.right`, { anchor: "topRight", gap: t.sm, cross: "center" }, () => {
|
|
173
173
|
if (desc.timeLeft !== null && desc.timeLeft !== undefined) {
|
|
174
|
-
|
|
174
|
+
hudBox("Timer", () => kit.label("Time", clock(desc.timeLeft!), { font: "mono", color: token(desc.timeLeft! <= 10 ? "danger" : "ink"), nowrap: true }));
|
|
175
175
|
}
|
|
176
176
|
pauseButton(out, desc.pauseLabel ?? "Pause");
|
|
177
177
|
});
|
|
@@ -185,8 +185,8 @@ export function idleHud(id: string, desc: Api.IdleHudDesc = {}): Api.HudResult {
|
|
|
185
185
|
const generators = desc.generators ?? [];
|
|
186
186
|
kit.anchor(`${id}.wallet`, { anchor: "top", gap: t.sm, cross: "center" }, () => {
|
|
187
187
|
currencies.forEach((c, i) => {
|
|
188
|
-
|
|
189
|
-
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"));
|
|
190
190
|
kit.label("Amount", fmt(c.amount ?? 0), { step: i === 0 ? "lg" : "body", font: "mono", nowrap: true });
|
|
191
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 });
|
|
192
192
|
}, { height: i === 0 ? t.controlLg : t.controlMd });
|
|
@@ -204,13 +204,13 @@ export function idleHud(id: string, desc: Api.IdleHudDesc = {}): Api.HudResult {
|
|
|
204
204
|
kit.panel("Generators", { width: grow(), padding: all(t.sm), gap: t.xs }, () => {
|
|
205
205
|
kit.list("List", { count: generators.length, rowHeight: 64, gap: t.xs, height: fit(0, listHeight), width: grow() }, (i) => {
|
|
206
206
|
const g = generators[i]!;
|
|
207
|
-
if (g.icon) iconOr("Icon", g.icon, 32, token("
|
|
207
|
+
if (g.icon) iconOr("Icon", g.icon, 32, token("ink"));
|
|
208
208
|
kit.column("Text", { width: grow(), gap: t.xxs }, () => {
|
|
209
209
|
kit.row("Head", { gap: t.sm, cross: "center", width: grow() }, () => {
|
|
210
210
|
kit.label("Name", g.name ?? g.id ?? "", { step: "lg", nowrap: true, width: grow() });
|
|
211
211
|
kit.badge("Level", (desc.levelLabel ?? "Lv {level}").replaceAll("{level}", `${g.level ?? 0}`), "neutral");
|
|
212
212
|
});
|
|
213
|
-
kit.meter("Cycle", g.progress ?? 0, 1, { kind: "
|
|
213
|
+
kit.meter("Cycle", g.progress ?? 0, 1, { kind: "primary", height: 4, width: grow() });
|
|
214
214
|
});
|
|
215
215
|
const buy = kit.button("Buy", fmt(g.cost ?? 0), { size: "small", variant: g.affordable ? "primary" : "secondary", disabled: !g.affordable }) as Api.Response;
|
|
216
216
|
if (buy.clicked) out.action = `buy:${g.id ?? i}`;
|
|
@@ -229,7 +229,7 @@ function deltaText(seconds: number): string {
|
|
|
229
229
|
|
|
230
230
|
/** A delta's ink: ahead (negative) success, behind danger, even muted. */
|
|
231
231
|
function deltaInk(seconds: number): Api.Paint {
|
|
232
|
-
return token(seconds < 0 ? "success" : seconds > 0 ? "danger" : "
|
|
232
|
+
return token(seconds < 0 ? "success" : seconds > 0 ? "danger" : "inkMuted");
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResult {
|
|
@@ -244,7 +244,7 @@ export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResu
|
|
|
244
244
|
if (racers > 0) {
|
|
245
245
|
kit.row("Position", { gap: t.xxs, cross: "end" }, () => {
|
|
246
246
|
kit.label("Place", `${desc.position ?? 1}`, { step: "displayXl", font: "display", nowrap: true });
|
|
247
|
-
kit.label("Of", `/${racers}`, { step: "xl", font: "mono", color: token("
|
|
247
|
+
kit.label("Of", `/${racers}`, { step: "xl", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
248
248
|
});
|
|
249
249
|
}
|
|
250
250
|
stat("Lap", `${Math.min(desc.lap ?? 1, desc.laps ?? 3)}/${desc.laps ?? 3}`, desc.lapLabel ?? "lap", "xl3");
|
|
@@ -258,16 +258,16 @@ export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResu
|
|
|
258
258
|
if (delta !== null) kit.label("Delta", deltaText(delta), { step: "lg", font: "mono", color: deltaInk(delta), nowrap: true });
|
|
259
259
|
if (desc.best !== null && desc.best !== undefined) {
|
|
260
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("
|
|
261
|
+
kit.label("Best", (desc.bestLabel ?? "Best {best}").replaceAll("{best}", lapTime(desc.best)), { step: "meta", font: "mono", color: token("inkMuted"), nowrap: true });
|
|
262
262
|
}
|
|
263
263
|
const splits = desc.splits ?? [];
|
|
264
264
|
if (splits.length > 0) {
|
|
265
265
|
kit.column("Splits", { gap: 0, cross: "end" }, () => {
|
|
266
266
|
splits.forEach((sp, i) => {
|
|
267
267
|
kit.row(kit.idi("Split", i), { gap: t.sm, cross: "center" }, () => {
|
|
268
|
-
kit.label("Label", sp.label ?? "", { step: "meta", color: token("
|
|
268
|
+
kit.label("Label", sp.label ?? "", { step: "meta", color: token("inkMuted"), nowrap: true });
|
|
269
269
|
const time = sp.time ?? null;
|
|
270
|
-
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 });
|
|
271
271
|
const d = sp.delta ?? null;
|
|
272
272
|
if (d !== null) kit.label("Delta", deltaText(d), { step: "meta", font: "mono", color: deltaInk(d), nowrap: true });
|
|
273
273
|
});
|
|
@@ -280,11 +280,11 @@ export function racingHud(id: string, desc: Api.RacingHudDesc = {}): Api.HudResu
|
|
|
280
280
|
kit.row("Speed", { gap: t.sm, cross: "end", width: grow(), align: "end" }, () => {
|
|
281
281
|
kit.label("Value", `${Math.round(desc.speed ?? 0)}`, { step: "displayXl2", font: "display", nowrap: true });
|
|
282
282
|
kit.column("Side", { cross: "end" }, () => {
|
|
283
|
-
kit.label("Gear", desc.gear ?? "N", { step: "xl", font: "mono", color: token("
|
|
284
|
-
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 });
|
|
285
285
|
});
|
|
286
286
|
});
|
|
287
|
-
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 });
|
|
288
288
|
if (boost !== null) kit.meter("Boost", boost, 1, { color: token("info"), label: boostLabel, width: grow(), segments: 4 });
|
|
289
289
|
});
|
|
290
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
|
|