@jgengine/react 0.14.0 → 0.15.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/CHANGELOG.md +500 -1
- package/dist/accessibility.d.ts +39 -0
- package/dist/accessibility.js +61 -0
- package/dist/achievements.d.ts +50 -0
- package/dist/achievements.js +94 -0
- package/dist/cameraShake.d.ts +52 -0
- package/dist/cameraShake.js +53 -0
- package/dist/coachMarks.d.ts +72 -0
- package/dist/coachMarks.js +136 -0
- package/dist/codex.d.ts +25 -0
- package/dist/codex.js +62 -0
- package/dist/comboMeter.d.ts +63 -0
- package/dist/comboMeter.js +93 -0
- package/dist/components.js +4 -3
- package/dist/creditsScreen.d.ts +19 -0
- package/dist/creditsScreen.js +31 -0
- package/dist/cutscene.d.ts +66 -0
- package/dist/cutscene.js +132 -0
- package/dist/damageDirection.d.ts +51 -0
- package/dist/damageDirection.js +103 -0
- package/dist/dialogueView.d.ts +69 -0
- package/dist/dialogueView.js +111 -0
- package/dist/entityFrames.d.ts +114 -0
- package/dist/entityFrames.js +55 -0
- package/dist/fastTravel.d.ts +36 -0
- package/dist/fastTravel.js +103 -0
- package/dist/floatingText.d.ts +84 -0
- package/dist/floatingText.js +61 -0
- package/dist/gameViewport.js +8 -12
- package/dist/hooks.d.ts +42 -0
- package/dist/hooks.js +85 -0
- package/dist/hudTheme.d.ts +104 -7
- package/dist/hudTheme.js +300 -2
- package/dist/i18n.d.ts +29 -0
- package/dist/i18n.js +44 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +34 -0
- package/dist/interactionPrompt.d.ts +80 -0
- package/dist/interactionPrompt.js +112 -0
- package/dist/keybindingMenu.d.ts +59 -0
- package/dist/keybindingMenu.js +146 -0
- package/dist/killFeed.d.ts +63 -0
- package/dist/killFeed.js +88 -0
- package/dist/map.d.ts +123 -2
- package/dist/map.js +239 -36
- package/dist/menuRouter.d.ts +51 -0
- package/dist/menuRouter.js +49 -0
- package/dist/modals.d.ts +131 -0
- package/dist/modals.js +168 -0
- package/dist/notifications.d.ts +39 -0
- package/dist/notifications.js +111 -0
- package/dist/objectiveBanner.d.ts +62 -0
- package/dist/objectiveBanner.js +129 -0
- package/dist/photoMode.d.ts +21 -0
- package/dist/photoMode.js +39 -0
- package/dist/previewFixtures.d.ts +30 -0
- package/dist/previewFixtures.js +46 -0
- package/dist/questTracker.d.ts +24 -0
- package/dist/questTracker.js +31 -0
- package/dist/quickMenu.d.ts +39 -0
- package/dist/quickMenu.js +79 -0
- package/dist/radialMenu.d.ts +50 -0
- package/dist/radialMenu.js +69 -0
- package/dist/saveSlots.d.ts +81 -0
- package/dist/saveSlots.js +138 -0
- package/dist/scoreboard.d.ts +67 -0
- package/dist/scoreboard.js +71 -0
- package/dist/settings.d.ts +9 -3
- package/dist/settings.js +10 -4
- package/dist/shopGrid.d.ts +70 -0
- package/dist/shopGrid.js +129 -0
- package/dist/statusEffectBar.d.ts +52 -0
- package/dist/statusEffectBar.js +57 -0
- package/dist/streetGeometryPreview.d.ts +7 -0
- package/dist/streetGeometryPreview.js +119 -0
- package/dist/talentTree.d.ts +60 -0
- package/dist/talentTree.js +217 -0
- package/dist/timerReadout.d.ts +90 -0
- package/dist/timerReadout.js +111 -0
- package/dist/useDebouncedCommit.d.ts +54 -0
- package/dist/useDebouncedCommit.js +81 -0
- package/dist/waveHud.d.ts +49 -0
- package/dist/waveHud.js +53 -0
- package/dist/waypointMarkers.d.ts +80 -0
- package/dist/waypointMarkers.js +116 -0
- package/package.json +3 -3
- package/skills/ce-handoff/SKILL.md +149 -0
- package/skills/jgengine/SKILL.md +3 -1
- package/skills/jgengine/api.md +17 -7
- package/skills/jgengine/capabilities.md +12 -0
- package/skills/jgengine/recipes/minimal-game.md +7 -4
- package/skills/jgengine-ui/SKILL.md +5 -3
- package/skills/jgengine-ui/api.md +708 -19
- package/skills/jgengine-ui/capabilities.md +362 -1
- package/skills/jgengine-ui/recipes/overhead-entity-frames.md +136 -0
- package/skills/jgengine-ui/reference.md +7 -4
package/dist/gameViewport.js
CHANGED
|
@@ -219,18 +219,14 @@ export function GameViewportProvider({ platforms, className, style, children, })
|
|
|
219
219
|
}, [regions, metrics.layout, visual, vw, vh, coarsePointer, mobileSupported, safeArea]);
|
|
220
220
|
const collisions = useMemo(() => detectLayoutCollisions(regions), [regions]);
|
|
221
221
|
const forbidden = useMemo(() => collisions.filter((c) => c.severity === "forbid"), [collisions]);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
return;
|
|
222
|
+
const collisionReport = forbidden.length === 0 ? undefined : JSON.stringify(forbidden);
|
|
223
|
+
const lastWarnedRef = useRef(undefined);
|
|
224
|
+
if (collisionReport !== lastWarnedRef.current) {
|
|
225
|
+
lastWarnedRef.current = collisionReport;
|
|
226
|
+
if (collisionReport !== undefined) {
|
|
227
|
+
console.warn(`[jgengine] ${formatLayoutCollisions(forbidden)}\n mode=${layout.mode} viewport=${Math.round(vw)}x${Math.round(vh)} safe=${safeArea.top}/${safeArea.right}/${safeArea.bottom}/${safeArea.left}`);
|
|
229
228
|
}
|
|
230
|
-
|
|
231
|
-
root.setAttribute("data-jg-layout-collision", report);
|
|
232
|
-
console.warn(`[jgengine] ${formatLayoutCollisions(forbidden)}\n mode=${layout.mode} viewport=${Math.round(vw)}x${Math.round(vh)} safe=${safeArea.top}/${safeArea.right}/${safeArea.bottom}/${safeArea.left}`);
|
|
233
|
-
}, [forbidden, layout.mode, vw, vh, safeArea]);
|
|
229
|
+
}
|
|
234
230
|
useEffect(() => {
|
|
235
231
|
const ids = new Set();
|
|
236
232
|
for (const collision of forbidden) {
|
|
@@ -265,7 +261,7 @@ export function GameViewportProvider({ platforms, className, style, children, })
|
|
|
265
261
|
"--jg-safe-left": `${safeArea.left}px`,
|
|
266
262
|
...style,
|
|
267
263
|
};
|
|
268
|
-
return (_jsx(RegionRegistryContext.Provider, { value: registry, children: _jsx(GameLayoutContext.Provider, { value: layout, children: _jsxs("div", { ref: rootRef, "data-jg-viewport": "", "data-jg-layout-mode": layout.mode, "data-jg-orientation": layout.orientation, className: className, style: rootStyle, children: [_jsx("div", { ref: probeRef, "aria-hidden": true, style: SAFE_AREA_PROBE_STYLE }), children] }) }) }));
|
|
264
|
+
return (_jsx(RegionRegistryContext.Provider, { value: registry, children: _jsx(GameLayoutContext.Provider, { value: layout, children: _jsxs("div", { ref: rootRef, "data-jg-viewport": "", "data-jg-layout-mode": layout.mode, "data-jg-orientation": layout.orientation, "data-jg-layout-collision": collisionReport, className: className, style: rootStyle, children: [_jsx("div", { ref: probeRef, "aria-hidden": true, style: SAFE_AREA_PROBE_STYLE }), children] }) }) }));
|
|
269
265
|
}
|
|
270
266
|
/** The live shared viewport layout. Returns a neutral default outside a `GameViewportProvider` so it never throws in previews. */
|
|
271
267
|
export function useGameViewportLayout() {
|
package/dist/hooks.d.ts
CHANGED
|
@@ -175,4 +175,46 @@ export interface UseAxisChannelResult {
|
|
|
175
175
|
* constant at the call site) unless a rebind is intended.
|
|
176
176
|
*/
|
|
177
177
|
export declare function useAxisChannel(config: AxisChannelConfig): UseAxisChannelResult;
|
|
178
|
+
/**
|
|
179
|
+
* Re-render at a steady rate. Returns a monotonically increasing tick count driven by a
|
|
180
|
+
* setInterval, for HUD elements that display wall-clock-derived values (cooldowns, cast bars,
|
|
181
|
+
* swing timers) without an engine subscription to hang off. `hz <= 0` disables the ticker.
|
|
182
|
+
*
|
|
183
|
+
* @capability hud-ticker re-render a HUD element at a steady hz for time-derived readouts (cooldowns, cast/swing bars) — no hand-rolled setInterval effects
|
|
184
|
+
*/
|
|
185
|
+
export declare function useTicker(hz?: number): number;
|
|
186
|
+
/** Minimal add/removeEventListener surface accepted by useDomEvent (window, document, elements). */
|
|
187
|
+
export interface DomEventTarget {
|
|
188
|
+
addEventListener(type: string, listener: (event: never) => void, options?: unknown): void;
|
|
189
|
+
removeEventListener(type: string, listener: (event: never) => void, options?: unknown): void;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Attach a DOM event listener with automatic cleanup. `resolveTarget` runs inside the effect, so
|
|
193
|
+
* `() => window` and ref-reading resolvers are SSR-safe; return null to skip attaching. The
|
|
194
|
+
* handler is kept in a ref, so a fresh closure per render never re-binds the listener.
|
|
195
|
+
* Re-binds only when `type` or `options.capture`/`options.passive` change.
|
|
196
|
+
*
|
|
197
|
+
* @capability dom-event attach a window/document/element event listener with automatic cleanup and a stable handler ref — no hand-rolled addEventListener effects
|
|
198
|
+
*/
|
|
199
|
+
export declare function useDomEvent<E>(resolveTarget: () => DomEventTarget | null, type: string, handler: (event: E) => void, options?: {
|
|
200
|
+
capture?: boolean;
|
|
201
|
+
passive?: boolean;
|
|
202
|
+
}): void;
|
|
203
|
+
/**
|
|
204
|
+
* Run a requestAnimationFrame loop while `active`, calling `onFrame(deltaSeconds)` each frame.
|
|
205
|
+
* The callback is kept in a ref so re-renders never restart the loop; cleanup cancels the
|
|
206
|
+
* pending frame. For scene-graph work inside a canvas prefer R3F's useFrame — this is for
|
|
207
|
+
* DOM-side animation outside the renderer.
|
|
208
|
+
*
|
|
209
|
+
* @capability raf-loop run a cancellable requestAnimationFrame loop for DOM-side animation with delta seconds — no hand-rolled RAF effects
|
|
210
|
+
*/
|
|
211
|
+
export declare function useRafLoop(onFrame: (deltaSeconds: number) => void, active?: boolean): void;
|
|
212
|
+
/**
|
|
213
|
+
* Pin a scrollable element to its bottom whenever `dep` changes (typically a length or the list
|
|
214
|
+
* itself). Attach the returned ref to the scroll container. Owns the log/chat/console
|
|
215
|
+
* scroll-to-bottom effect so panels don't hand-roll it.
|
|
216
|
+
*
|
|
217
|
+
* @capability auto-scroll pin a log/chat/console panel to its newest line as entries arrive — no hand-rolled scrollTop effects
|
|
218
|
+
*/
|
|
219
|
+
export declare function useAutoScroll<T extends HTMLElement>(dep: unknown): import("react").RefObject<T | null>;
|
|
178
220
|
export {};
|
package/dist/hooks.js
CHANGED
|
@@ -375,3 +375,88 @@ export function useAxisChannel(config) {
|
|
|
375
375
|
const channel = useMemo(() => new AxisChannel(config), [config]);
|
|
376
376
|
return { channel, isDown };
|
|
377
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Re-render at a steady rate. Returns a monotonically increasing tick count driven by a
|
|
380
|
+
* setInterval, for HUD elements that display wall-clock-derived values (cooldowns, cast bars,
|
|
381
|
+
* swing timers) without an engine subscription to hang off. `hz <= 0` disables the ticker.
|
|
382
|
+
*
|
|
383
|
+
* @capability hud-ticker re-render a HUD element at a steady hz for time-derived readouts (cooldowns, cast/swing bars) — no hand-rolled setInterval effects
|
|
384
|
+
*/
|
|
385
|
+
export function useTicker(hz = 10) {
|
|
386
|
+
const [tick, setTick] = useState(0);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (!Number.isFinite(hz) || hz <= 0)
|
|
389
|
+
return undefined;
|
|
390
|
+
const timer = setInterval(() => setTick((value) => value + 1), 1000 / hz);
|
|
391
|
+
return () => clearInterval(timer);
|
|
392
|
+
}, [hz]);
|
|
393
|
+
return tick;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Attach a DOM event listener with automatic cleanup. `resolveTarget` runs inside the effect, so
|
|
397
|
+
* `() => window` and ref-reading resolvers are SSR-safe; return null to skip attaching. The
|
|
398
|
+
* handler is kept in a ref, so a fresh closure per render never re-binds the listener.
|
|
399
|
+
* Re-binds only when `type` or `options.capture`/`options.passive` change.
|
|
400
|
+
*
|
|
401
|
+
* @capability dom-event attach a window/document/element event listener with automatic cleanup and a stable handler ref — no hand-rolled addEventListener effects
|
|
402
|
+
*/
|
|
403
|
+
export function useDomEvent(resolveTarget, type, handler, options) {
|
|
404
|
+
const handlerRef = useRef(handler);
|
|
405
|
+
handlerRef.current = handler;
|
|
406
|
+
const capture = options?.capture ?? false;
|
|
407
|
+
const passive = options?.passive;
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
const target = resolveTarget();
|
|
410
|
+
if (target === null)
|
|
411
|
+
return undefined;
|
|
412
|
+
const listener = (event) => handlerRef.current(event);
|
|
413
|
+
const listenerOptions = passive === undefined ? { capture } : { capture, passive };
|
|
414
|
+
target.addEventListener(type, listener, listenerOptions);
|
|
415
|
+
return () => target.removeEventListener(type, listener, listenerOptions);
|
|
416
|
+
// resolveTarget is intentionally excluded: it is expected to be an inline closure over a
|
|
417
|
+
// stable target (window, document, a ref) and re-running on its identity would re-bind
|
|
418
|
+
// every render.
|
|
419
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
420
|
+
}, [type, capture, passive]);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Run a requestAnimationFrame loop while `active`, calling `onFrame(deltaSeconds)` each frame.
|
|
424
|
+
* The callback is kept in a ref so re-renders never restart the loop; cleanup cancels the
|
|
425
|
+
* pending frame. For scene-graph work inside a canvas prefer R3F's useFrame — this is for
|
|
426
|
+
* DOM-side animation outside the renderer.
|
|
427
|
+
*
|
|
428
|
+
* @capability raf-loop run a cancellable requestAnimationFrame loop for DOM-side animation with delta seconds — no hand-rolled RAF effects
|
|
429
|
+
*/
|
|
430
|
+
export function useRafLoop(onFrame, active = true) {
|
|
431
|
+
const frameRef = useRef(onFrame);
|
|
432
|
+
frameRef.current = onFrame;
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (!active || typeof requestAnimationFrame !== "function")
|
|
435
|
+
return undefined;
|
|
436
|
+
let handle = 0;
|
|
437
|
+
let last = performance.now();
|
|
438
|
+
const step = (now) => {
|
|
439
|
+
frameRef.current(Math.max(0, now - last) / 1000);
|
|
440
|
+
last = now;
|
|
441
|
+
handle = requestAnimationFrame(step);
|
|
442
|
+
};
|
|
443
|
+
handle = requestAnimationFrame(step);
|
|
444
|
+
return () => cancelAnimationFrame(handle);
|
|
445
|
+
}, [active]);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Pin a scrollable element to its bottom whenever `dep` changes (typically a length or the list
|
|
449
|
+
* itself). Attach the returned ref to the scroll container. Owns the log/chat/console
|
|
450
|
+
* scroll-to-bottom effect so panels don't hand-roll it.
|
|
451
|
+
*
|
|
452
|
+
* @capability auto-scroll pin a log/chat/console panel to its newest line as entries arrive — no hand-rolled scrollTop effects
|
|
453
|
+
*/
|
|
454
|
+
export function useAutoScroll(dep) {
|
|
455
|
+
const ref = useRef(null);
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
const node = ref.current;
|
|
458
|
+
if (node !== null)
|
|
459
|
+
node.scrollTop = node.scrollHeight;
|
|
460
|
+
}, [dep]);
|
|
461
|
+
return ref;
|
|
462
|
+
}
|
package/dist/hudTheme.d.ts
CHANGED
|
@@ -1,27 +1,73 @@
|
|
|
1
1
|
import { type CSSProperties } from "react";
|
|
2
2
|
/**
|
|
3
|
-
* `HudTheme` (#1034): one token object that restyles shared token-driven building
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* setting a **game-authored** theme on any HUD ancestor
|
|
3
|
+
* `HudTheme` (#1034, #1573): one token object that restyles every shared token-driven building
|
|
4
|
+
* block at once — the atomic bars (#1033), frames, slots, the minimap ring, and the chrome the
|
|
5
|
+
* registry blocks read (surfaces, edges, text, status colors, fonts, rarity). `hudThemeVars(theme)`
|
|
6
|
+
* emits the whole `--jg-*` vocabulary, so setting a **game-authored** theme on any HUD ancestor
|
|
7
|
+
* re-skins `packages/react` primitives and `registry/jgengine` components together. Purely CSS-token
|
|
7
8
|
* driven (no image assets). Tokens are not a finished UI: every game still owns composition, skin,
|
|
8
9
|
* and art direction (see AGENTS.md). Built-in presets are demo/scaffold starting points only.
|
|
9
10
|
*
|
|
10
|
-
* @capability hud-theme game-authored token object
|
|
11
|
+
* @capability hud-theme game-authored token object covering bars, frames, slots, chrome, text, status colors, fonts, and rarity
|
|
11
12
|
*/
|
|
12
|
-
/** Per-readout fill colors plus a shared accent/glow. */
|
|
13
|
+
/** Per-readout fill colors plus a shared accent/glow. Each `*Deep` is the shaded companion fill. */
|
|
13
14
|
export interface HudThemePalette {
|
|
14
15
|
health: string;
|
|
15
16
|
healthLow: string;
|
|
17
|
+
healthDeep: string;
|
|
16
18
|
mana: string;
|
|
19
|
+
manaDeep: string;
|
|
17
20
|
stamina: string;
|
|
21
|
+
staminaDeep: string;
|
|
18
22
|
shield: string;
|
|
23
|
+
shieldDeep: string;
|
|
19
24
|
xp: string;
|
|
25
|
+
xpDeep: string;
|
|
20
26
|
soul: string;
|
|
21
27
|
ammo: string;
|
|
22
28
|
boss: string;
|
|
23
29
|
/** Accent used for glow/keycap/active states. */
|
|
24
30
|
accent: string;
|
|
31
|
+
/** Shaded accent for gradients and pressed states. */
|
|
32
|
+
accentDeep: string;
|
|
33
|
+
/** Translucent accent for outer glows. */
|
|
34
|
+
accentGlow: string;
|
|
35
|
+
/** Soft accent wash for large background gradients. */
|
|
36
|
+
accentDim: string;
|
|
37
|
+
}
|
|
38
|
+
/** Chrome surfaces, edges, and text — what panels, overlays, and menus are built out of. */
|
|
39
|
+
export interface HudThemeSurface {
|
|
40
|
+
surface: string;
|
|
41
|
+
surfaceDeep: string;
|
|
42
|
+
edge: string;
|
|
43
|
+
edgeBright: string;
|
|
44
|
+
text: string;
|
|
45
|
+
textDim: string;
|
|
46
|
+
/** Full-screen dim behind modals and overlays. */
|
|
47
|
+
backdrop: string;
|
|
48
|
+
}
|
|
49
|
+
/** Semantic status colors — outcome, threat, and relationship coloring. */
|
|
50
|
+
export interface HudThemeStatus {
|
|
51
|
+
danger: string;
|
|
52
|
+
warning: string;
|
|
53
|
+
success: string;
|
|
54
|
+
hostile: string;
|
|
55
|
+
friendly: string;
|
|
56
|
+
neutral: string;
|
|
57
|
+
}
|
|
58
|
+
/** Typography roles. Any CSS `font-family` list. */
|
|
59
|
+
export interface HudThemeFont {
|
|
60
|
+
display: string;
|
|
61
|
+
numeric: string;
|
|
62
|
+
body: string;
|
|
63
|
+
}
|
|
64
|
+
/** Item-rarity coloring shared by loot cards, tooltips, and inventory slots. */
|
|
65
|
+
export interface HudThemeRarity {
|
|
66
|
+
common: string;
|
|
67
|
+
uncommon: string;
|
|
68
|
+
rare: string;
|
|
69
|
+
epic: string;
|
|
70
|
+
legendary: string;
|
|
25
71
|
}
|
|
26
72
|
/** Frame material + shape tokens (panels, `HudFrame variation="themed"`). */
|
|
27
73
|
export interface HudThemeFrame {
|
|
@@ -48,9 +94,13 @@ export interface HudThemeSlot {
|
|
|
48
94
|
border: string;
|
|
49
95
|
radius: string;
|
|
50
96
|
}
|
|
51
|
-
/** A full HUD theme. */
|
|
97
|
+
/** A full HUD theme. Author one with {@link hudTheme} or {@link deriveHudTheme}. */
|
|
52
98
|
export interface HudTheme {
|
|
53
99
|
palette: HudThemePalette;
|
|
100
|
+
surface: HudThemeSurface;
|
|
101
|
+
status: HudThemeStatus;
|
|
102
|
+
font: HudThemeFont;
|
|
103
|
+
rarity: HudThemeRarity;
|
|
54
104
|
frame: HudThemeFrame;
|
|
55
105
|
bar: HudThemeBar;
|
|
56
106
|
slot: HudThemeSlot;
|
|
@@ -61,6 +111,26 @@ export interface HudTheme {
|
|
|
61
111
|
export declare function hudThemeVars(theme: HudTheme): CSSProperties;
|
|
62
112
|
/** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
|
|
63
113
|
export declare const defaultHudTheme: HudTheme;
|
|
114
|
+
/** Per-group partial overrides accepted by {@link hudTheme}. */
|
|
115
|
+
export interface HudThemeOverrides {
|
|
116
|
+
palette?: Partial<HudThemePalette>;
|
|
117
|
+
surface?: Partial<HudThemeSurface>;
|
|
118
|
+
status?: Partial<HudThemeStatus>;
|
|
119
|
+
font?: Partial<HudThemeFont>;
|
|
120
|
+
rarity?: Partial<HudThemeRarity>;
|
|
121
|
+
frame?: Partial<HudThemeFrame>;
|
|
122
|
+
bar?: Partial<HudThemeBar>;
|
|
123
|
+
slot?: Partial<HudThemeSlot>;
|
|
124
|
+
ring?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds a full {@link HudTheme} from per-group partials, filling the rest from
|
|
128
|
+
* {@link defaultHudTheme}. Reach for this when a game wants to name exact values; use
|
|
129
|
+
* {@link deriveHudTheme} when a seed color pair is enough.
|
|
130
|
+
*
|
|
131
|
+
* @capability hud-theme-author fill a full HudTheme from per-group partial overrides
|
|
132
|
+
*/
|
|
133
|
+
export declare function hudTheme(overrides: HudThemeOverrides): HudTheme;
|
|
64
134
|
/** Built-in theme presets for demos/scaffold only — not product identity; games author their own theme. */
|
|
65
135
|
export declare const HUD_THEME_PRESETS: {
|
|
66
136
|
"arcane-stone": HudTheme;
|
|
@@ -69,8 +139,35 @@ export declare const HUD_THEME_PRESETS: {
|
|
|
69
139
|
"sleek-hex": HudTheme;
|
|
70
140
|
"elemental-circle": HudTheme;
|
|
71
141
|
"sandbox-slot": HudTheme;
|
|
142
|
+
ember: HudTheme;
|
|
143
|
+
synthwave: HudTheme;
|
|
144
|
+
fieldkit: HudTheme;
|
|
72
145
|
};
|
|
73
146
|
/** Built-in demo/scaffold theme preset name — not a game identity. */
|
|
74
147
|
export type HudThemePreset = keyof typeof HUD_THEME_PRESETS;
|
|
148
|
+
/** Seed colors for {@link deriveHudTheme}. Only `accent` and `surface` are required. */
|
|
149
|
+
export interface HudThemeSeed {
|
|
150
|
+
accent: string;
|
|
151
|
+
surface: string;
|
|
152
|
+
text?: string;
|
|
153
|
+
health?: string;
|
|
154
|
+
mana?: string;
|
|
155
|
+
stamina?: string;
|
|
156
|
+
xp?: string;
|
|
157
|
+
shield?: string;
|
|
158
|
+
danger?: string;
|
|
159
|
+
warning?: string;
|
|
160
|
+
friendly?: string;
|
|
161
|
+
fontDisplay?: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Derives a coherent full theme from an accent/surface pair — shades, edges, dim text, frames,
|
|
165
|
+
* bar troughs, and slots all fall out of the seed, so a game gets one consistent look across the
|
|
166
|
+
* `packages/react` primitives and the `registry/jgengine` blocks without naming 50 tokens.
|
|
167
|
+
* Hex seeds only (`#rgb` or `#rrggbb`).
|
|
168
|
+
*
|
|
169
|
+
* @capability hud-theme-derive build a full HudTheme from an accent + surface seed pair
|
|
170
|
+
*/
|
|
171
|
+
export declare function deriveHudTheme(seed: HudThemeSeed): HudTheme;
|
|
75
172
|
/** Resolves a preset name (or a full theme) to a `HudTheme`; falls back to the default theme. */
|
|
76
173
|
export declare function resolveHudTheme(theme?: HudThemePreset | HudTheme): HudTheme;
|
package/dist/hudTheme.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import {} from "react";
|
|
2
2
|
/** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
|
|
3
3
|
export function hudThemeVars(theme) {
|
|
4
|
-
const { palette, frame, bar, slot } = theme;
|
|
4
|
+
const { palette, surface, status, font, rarity, frame, bar, slot } = theme;
|
|
5
5
|
return {
|
|
6
6
|
// atomic-bar tokens (same names the bars read — #1033)
|
|
7
7
|
"--jg-health": palette.health,
|
|
8
8
|
"--jg-health-low": palette.healthLow,
|
|
9
|
+
"--jg-health-deep": palette.healthDeep,
|
|
9
10
|
"--jg-mana": palette.mana,
|
|
11
|
+
"--jg-mana-deep": palette.manaDeep,
|
|
10
12
|
"--jg-stamina": palette.stamina,
|
|
13
|
+
"--jg-stamina-deep": palette.staminaDeep,
|
|
11
14
|
"--jg-shield": palette.shield,
|
|
15
|
+
"--jg-shield-deep": palette.shieldDeep,
|
|
12
16
|
"--jg-xp": palette.xp,
|
|
17
|
+
"--jg-xp-deep": palette.xpDeep,
|
|
13
18
|
"--jg-soul": palette.soul,
|
|
14
19
|
"--jg-ammo": palette.ammo,
|
|
15
20
|
"--jg-boss": palette.boss,
|
|
@@ -21,6 +26,30 @@ export function hudThemeVars(theme) {
|
|
|
21
26
|
"--jg-bar-text": bar.text,
|
|
22
27
|
// shared chrome tokens
|
|
23
28
|
"--jg-accent": palette.accent,
|
|
29
|
+
"--jg-accent-deep": palette.accentDeep,
|
|
30
|
+
"--jg-accent-glow": palette.accentGlow,
|
|
31
|
+
"--jg-accent-dim": palette.accentDim,
|
|
32
|
+
"--jg-surface": surface.surface,
|
|
33
|
+
"--jg-surface-deep": surface.surfaceDeep,
|
|
34
|
+
"--jg-edge": surface.edge,
|
|
35
|
+
"--jg-edge-bright": surface.edgeBright,
|
|
36
|
+
"--jg-text": surface.text,
|
|
37
|
+
"--jg-text-dim": surface.textDim,
|
|
38
|
+
"--jg-backdrop": surface.backdrop,
|
|
39
|
+
"--jg-danger": status.danger,
|
|
40
|
+
"--jg-warning": status.warning,
|
|
41
|
+
"--jg-success": status.success,
|
|
42
|
+
"--jg-hostile": status.hostile,
|
|
43
|
+
"--jg-friendly": status.friendly,
|
|
44
|
+
"--jg-neutral": status.neutral,
|
|
45
|
+
"--jg-font-display": font.display,
|
|
46
|
+
"--jg-font-numeric": font.numeric,
|
|
47
|
+
"--jg-font-body": font.body,
|
|
48
|
+
"--jg-rarity-common": rarity.common,
|
|
49
|
+
"--jg-rarity-uncommon": rarity.uncommon,
|
|
50
|
+
"--jg-rarity-rare": rarity.rare,
|
|
51
|
+
"--jg-rarity-epic": rarity.epic,
|
|
52
|
+
"--jg-rarity-legendary": rarity.legendary,
|
|
24
53
|
"--jg-frame-bg": frame.bg,
|
|
25
54
|
"--jg-frame-border": frame.border,
|
|
26
55
|
"--jg-frame-radius": frame.radius,
|
|
@@ -44,6 +73,43 @@ export const defaultHudTheme = {
|
|
|
44
73
|
ammo: "#f8b84e",
|
|
45
74
|
boss: "#ef4444",
|
|
46
75
|
accent: "#38bdf8",
|
|
76
|
+
healthDeep: "#7f1d1f",
|
|
77
|
+
manaDeep: "#1d4ed8",
|
|
78
|
+
staminaDeep: "#3f6212",
|
|
79
|
+
shieldDeep: "#0e7490",
|
|
80
|
+
xpDeep: "#6b21a8",
|
|
81
|
+
accentDeep: "#0b5f88",
|
|
82
|
+
accentGlow: "rgba(56, 189, 248, 0.5)",
|
|
83
|
+
accentDim: "rgba(56, 189, 248, 0.18)",
|
|
84
|
+
},
|
|
85
|
+
surface: {
|
|
86
|
+
surface: "#141820",
|
|
87
|
+
surfaceDeep: "#0a0c10",
|
|
88
|
+
edge: "#2c3340",
|
|
89
|
+
edgeBright: "#4a5566",
|
|
90
|
+
text: "#f5f7fa",
|
|
91
|
+
textDim: "#94a3b8",
|
|
92
|
+
backdrop: "rgba(4,7,12,0.62)",
|
|
93
|
+
},
|
|
94
|
+
status: {
|
|
95
|
+
danger: "#e5484d",
|
|
96
|
+
warning: "#f8b84e",
|
|
97
|
+
success: "#3fa34d",
|
|
98
|
+
hostile: "#e5484d",
|
|
99
|
+
friendly: "#38bdf8",
|
|
100
|
+
neutral: "#c9b458",
|
|
101
|
+
},
|
|
102
|
+
font: {
|
|
103
|
+
display: '"Segoe UI", system-ui, sans-serif',
|
|
104
|
+
numeric: 'Consolas, "Cascadia Mono", "SF Mono", "Roboto Mono", monospace',
|
|
105
|
+
body: '"Segoe UI", system-ui, sans-serif',
|
|
106
|
+
},
|
|
107
|
+
rarity: {
|
|
108
|
+
common: "#b4b2a8",
|
|
109
|
+
uncommon: "#57c14f",
|
|
110
|
+
rare: "#4a86d8",
|
|
111
|
+
epic: "#a04fd0",
|
|
112
|
+
legendary: "#e0862e",
|
|
47
113
|
},
|
|
48
114
|
frame: {
|
|
49
115
|
bg: "linear-gradient(180deg, rgba(20,24,32,0.86), rgba(10,12,16,0.9))",
|
|
@@ -62,15 +128,73 @@ export const defaultHudTheme = {
|
|
|
62
128
|
slot: { bg: "rgba(12,14,20,0.7)", border: "1px solid rgba(255,255,255,0.12)", radius: "8px" },
|
|
63
129
|
ring: "rgba(140,170,200,0.35)",
|
|
64
130
|
};
|
|
65
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Builds a full {@link HudTheme} from per-group partials, filling the rest from
|
|
133
|
+
* {@link defaultHudTheme}. Reach for this when a game wants to name exact values; use
|
|
134
|
+
* {@link deriveHudTheme} when a seed color pair is enough.
|
|
135
|
+
*
|
|
136
|
+
* @capability hud-theme-author fill a full HudTheme from per-group partial overrides
|
|
137
|
+
*/
|
|
138
|
+
export function hudTheme(overrides) {
|
|
66
139
|
return {
|
|
67
140
|
palette: { ...defaultHudTheme.palette, ...overrides.palette },
|
|
141
|
+
surface: { ...defaultHudTheme.surface, ...overrides.surface },
|
|
142
|
+
status: { ...defaultHudTheme.status, ...overrides.status },
|
|
143
|
+
font: { ...defaultHudTheme.font, ...overrides.font },
|
|
144
|
+
rarity: { ...defaultHudTheme.rarity, ...overrides.rarity },
|
|
68
145
|
frame: { ...defaultHudTheme.frame, ...overrides.frame },
|
|
69
146
|
bar: { ...defaultHudTheme.bar, ...overrides.bar },
|
|
70
147
|
slot: { ...defaultHudTheme.slot, ...overrides.slot },
|
|
71
148
|
ring: overrides.ring ?? defaultHudTheme.ring,
|
|
72
149
|
};
|
|
73
150
|
}
|
|
151
|
+
const theme = hudTheme;
|
|
152
|
+
/** Builds a chrome-led preset: the named surface/edge values drive frames, troughs, and slots. */
|
|
153
|
+
function chrome(spec) {
|
|
154
|
+
return theme({
|
|
155
|
+
palette: {
|
|
156
|
+
health: spec.fills.health,
|
|
157
|
+
healthLow: spec.deeps.health,
|
|
158
|
+
healthDeep: spec.deeps.health,
|
|
159
|
+
mana: spec.fills.mana,
|
|
160
|
+
manaDeep: spec.deeps.mana,
|
|
161
|
+
stamina: spec.fills.stamina,
|
|
162
|
+
staminaDeep: spec.deeps.stamina,
|
|
163
|
+
shield: spec.fills.shield,
|
|
164
|
+
shieldDeep: spec.deeps.shield,
|
|
165
|
+
xp: spec.fills.xp,
|
|
166
|
+
xpDeep: spec.deeps.xp,
|
|
167
|
+
soul: mix(spec.fills.xp, "#ffffff", 0.25),
|
|
168
|
+
ammo: spec.status.warning,
|
|
169
|
+
boss: spec.status.danger,
|
|
170
|
+
accent: spec.accent,
|
|
171
|
+
accentDeep: spec.accentDeep,
|
|
172
|
+
accentGlow: spec.accentGlow,
|
|
173
|
+
accentDim: alpha(spec.accent, 0.18),
|
|
174
|
+
},
|
|
175
|
+
surface: {
|
|
176
|
+
surface: spec.surface,
|
|
177
|
+
surfaceDeep: spec.surfaceDeep,
|
|
178
|
+
edge: spec.edge,
|
|
179
|
+
edgeBright: spec.edgeBright,
|
|
180
|
+
text: spec.text,
|
|
181
|
+
textDim: spec.textDim,
|
|
182
|
+
backdrop: alpha(spec.surfaceDeep, 0.72),
|
|
183
|
+
},
|
|
184
|
+
status: spec.status,
|
|
185
|
+
font: { display: spec.display },
|
|
186
|
+
rarity: spec.rarity,
|
|
187
|
+
frame: {
|
|
188
|
+
bg: `linear-gradient(180deg, ${alpha(spec.surface, 0.92)}, ${alpha(spec.surfaceDeep, 0.94)})`,
|
|
189
|
+
border: `1px solid ${spec.edge}`,
|
|
190
|
+
radius: "6px",
|
|
191
|
+
glow: `0 6px 20px ${alpha(spec.surfaceDeep, 0.7)}`,
|
|
192
|
+
},
|
|
193
|
+
bar: { track: alpha(spec.surfaceDeep, 0.8), frame: spec.surfaceDeep, text: spec.text },
|
|
194
|
+
slot: { bg: alpha(spec.surface, 0.8), border: `1px solid ${spec.edge}`, radius: "6px" },
|
|
195
|
+
ring: alpha(spec.edgeBright, 0.55),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
74
198
|
/** Built-in theme presets for demos/scaffold only — not product identity; games author their own theme. */
|
|
75
199
|
export const HUD_THEME_PRESETS = {
|
|
76
200
|
"arcane-stone": theme({
|
|
@@ -145,7 +269,181 @@ export const HUD_THEME_PRESETS = {
|
|
|
145
269
|
slot: { bg: "#4a4a4a", border: "3px solid #2a2a2a", radius: "0px" },
|
|
146
270
|
ring: "rgba(240,192,64,0.5)",
|
|
147
271
|
}),
|
|
272
|
+
ember: chrome({
|
|
273
|
+
accent: "#e3b054",
|
|
274
|
+
accentGlow: "rgba(227, 176, 84, 0.55)",
|
|
275
|
+
accentDeep: "#8a6425",
|
|
276
|
+
surface: "#17120d",
|
|
277
|
+
surfaceDeep: "#0b0906",
|
|
278
|
+
edge: "#57452c",
|
|
279
|
+
edgeBright: "#93794c",
|
|
280
|
+
text: "#f2e7d0",
|
|
281
|
+
textDim: "#a3947a",
|
|
282
|
+
fills: { health: "#5cc35f", mana: "#4a86d8", stamina: "#d9c33f", xp: "#a566d9", shield: "#9fb9c9" },
|
|
283
|
+
deeps: { health: "#26662c", mana: "#20406f", stamina: "#6e621c", xp: "#4e2c6e", shield: "#48606e" },
|
|
284
|
+
status: {
|
|
285
|
+
danger: "#e0483e",
|
|
286
|
+
warning: "#e8a33d",
|
|
287
|
+
success: "#6fce6f",
|
|
288
|
+
hostile: "#d84a3a",
|
|
289
|
+
friendly: "#4fa9e0",
|
|
290
|
+
neutral: "#d9d04f",
|
|
291
|
+
},
|
|
292
|
+
display: '"Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif',
|
|
293
|
+
rarity: {
|
|
294
|
+
common: "#b4b2a8",
|
|
295
|
+
uncommon: "#57c14f",
|
|
296
|
+
rare: "#4a86d8",
|
|
297
|
+
epic: "#a04fd0",
|
|
298
|
+
legendary: "#e0862e",
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
synthwave: chrome({
|
|
302
|
+
accent: "#41e6f0",
|
|
303
|
+
accentGlow: "rgba(65, 230, 240, 0.55)",
|
|
304
|
+
accentDeep: "#136b74",
|
|
305
|
+
surface: "#12101f",
|
|
306
|
+
surfaceDeep: "#080711",
|
|
307
|
+
edge: "#3c3467",
|
|
308
|
+
edgeBright: "#6a5daa",
|
|
309
|
+
text: "#e9ecff",
|
|
310
|
+
textDim: "#8d8bb3",
|
|
311
|
+
fills: { health: "#3ee08a", mana: "#5a7bff", stamina: "#f0e341", xp: "#e050d8", shield: "#8ad4f0" },
|
|
312
|
+
deeps: { health: "#136b44", mana: "#243487", stamina: "#77701a", xp: "#6e2069", shield: "#2f6d85" },
|
|
313
|
+
status: {
|
|
314
|
+
danger: "#ff4d6a",
|
|
315
|
+
warning: "#ffb347",
|
|
316
|
+
success: "#3ee08a",
|
|
317
|
+
hostile: "#ff4d6a",
|
|
318
|
+
friendly: "#41e6f0",
|
|
319
|
+
neutral: "#f0e341",
|
|
320
|
+
},
|
|
321
|
+
display: '"Segoe UI", system-ui, sans-serif',
|
|
322
|
+
rarity: {
|
|
323
|
+
common: "#9fa4bd",
|
|
324
|
+
uncommon: "#3ee08a",
|
|
325
|
+
rare: "#5a7bff",
|
|
326
|
+
epic: "#e050d8",
|
|
327
|
+
legendary: "#ffb347",
|
|
328
|
+
},
|
|
329
|
+
}),
|
|
330
|
+
fieldkit: chrome({
|
|
331
|
+
accent: "#d8c169",
|
|
332
|
+
accentGlow: "rgba(216, 193, 105, 0.45)",
|
|
333
|
+
accentDeep: "#6e6230",
|
|
334
|
+
surface: "#14160f",
|
|
335
|
+
surfaceDeep: "#0a0b07",
|
|
336
|
+
edge: "#454a35",
|
|
337
|
+
edgeBright: "#6f7657",
|
|
338
|
+
text: "#e6e8d8",
|
|
339
|
+
textDim: "#95997f",
|
|
340
|
+
fills: { health: "#7fb84a", mana: "#57a8b8", stamina: "#c9b73e", xp: "#b88f4a", shield: "#a8b4a0" },
|
|
341
|
+
deeps: { health: "#3d5c1f", mana: "#265059", stamina: "#645a1c", xp: "#5c451f", shield: "#4e594a" },
|
|
342
|
+
status: {
|
|
343
|
+
danger: "#d84f35",
|
|
344
|
+
warning: "#d8952f",
|
|
345
|
+
success: "#7fb84a",
|
|
346
|
+
hostile: "#d84f35",
|
|
347
|
+
friendly: "#57a8b8",
|
|
348
|
+
neutral: "#c9b73e",
|
|
349
|
+
},
|
|
350
|
+
display: '"Arial Narrow", "Roboto Condensed", "Segoe UI", sans-serif',
|
|
351
|
+
rarity: {
|
|
352
|
+
common: "#a8ab99",
|
|
353
|
+
uncommon: "#7fb84a",
|
|
354
|
+
rare: "#57a8b8",
|
|
355
|
+
epic: "#b06fc9",
|
|
356
|
+
legendary: "#d8952f",
|
|
357
|
+
},
|
|
358
|
+
}),
|
|
148
359
|
};
|
|
360
|
+
function hexToRgb(hex) {
|
|
361
|
+
const raw = hex.replace("#", "");
|
|
362
|
+
const full = raw.length === 3 ? raw.split("").map((c) => c + c).join("") : raw;
|
|
363
|
+
return {
|
|
364
|
+
r: parseInt(full.slice(0, 2), 16),
|
|
365
|
+
g: parseInt(full.slice(2, 4), 16),
|
|
366
|
+
b: parseInt(full.slice(4, 6), 16),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function rgbToHex({ r, g, b }) {
|
|
370
|
+
const channel = (v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0");
|
|
371
|
+
return `#${channel(r)}${channel(g)}${channel(b)}`;
|
|
372
|
+
}
|
|
373
|
+
function mix(from, to, amount) {
|
|
374
|
+
const a = hexToRgb(from);
|
|
375
|
+
const b = hexToRgb(to);
|
|
376
|
+
return rgbToHex({
|
|
377
|
+
r: a.r + (b.r - a.r) * amount,
|
|
378
|
+
g: a.g + (b.g - a.g) * amount,
|
|
379
|
+
b: a.b + (b.b - a.b) * amount,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function alpha(hex, value) {
|
|
383
|
+
const { r, g, b } = hexToRgb(hex);
|
|
384
|
+
return `rgba(${r}, ${g}, ${b}, ${value})`;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Derives a coherent full theme from an accent/surface pair — shades, edges, dim text, frames,
|
|
388
|
+
* bar troughs, and slots all fall out of the seed, so a game gets one consistent look across the
|
|
389
|
+
* `packages/react` primitives and the `registry/jgengine` blocks without naming 50 tokens.
|
|
390
|
+
* Hex seeds only (`#rgb` or `#rrggbb`).
|
|
391
|
+
*
|
|
392
|
+
* @capability hud-theme-derive build a full HudTheme from an accent + surface seed pair
|
|
393
|
+
*/
|
|
394
|
+
export function deriveHudTheme(seed) {
|
|
395
|
+
const { accent, surface } = seed;
|
|
396
|
+
const text = seed.text ?? mix("#f4f4f0", accent, 0.12);
|
|
397
|
+
const health = seed.health ?? "#e0483e";
|
|
398
|
+
const mana = seed.mana ?? "#4a86d8";
|
|
399
|
+
const stamina = seed.stamina ?? "#d9c33f";
|
|
400
|
+
const xp = seed.xp ?? "#a566d9";
|
|
401
|
+
const shield = seed.shield ?? "#9fb9c9";
|
|
402
|
+
const danger = seed.danger ?? "#e0483e";
|
|
403
|
+
const surfaceDeep = mix(surface, "#000000", 0.5);
|
|
404
|
+
const edge = mix(surface, accent, 0.22);
|
|
405
|
+
const edgeBright = mix(surface, accent, 0.42);
|
|
406
|
+
return hudTheme({
|
|
407
|
+
palette: {
|
|
408
|
+
health,
|
|
409
|
+
healthLow: mix(health, "#000000", 0.3),
|
|
410
|
+
healthDeep: mix(health, "#000000", 0.5),
|
|
411
|
+
mana,
|
|
412
|
+
manaDeep: mix(mana, "#000000", 0.5),
|
|
413
|
+
stamina,
|
|
414
|
+
staminaDeep: mix(stamina, "#000000", 0.5),
|
|
415
|
+
shield,
|
|
416
|
+
shieldDeep: mix(shield, "#000000", 0.5),
|
|
417
|
+
xp,
|
|
418
|
+
xpDeep: mix(xp, "#000000", 0.5),
|
|
419
|
+
soul: mix(xp, "#ffffff", 0.25),
|
|
420
|
+
ammo: seed.warning ?? "#e8a33d",
|
|
421
|
+
boss: danger,
|
|
422
|
+
accent,
|
|
423
|
+
accentDeep: mix(accent, "#000000", 0.55),
|
|
424
|
+
accentGlow: alpha(accent, 0.5),
|
|
425
|
+
accentDim: alpha(accent, 0.18),
|
|
426
|
+
},
|
|
427
|
+
surface: { surface, surfaceDeep, edge, edgeBright, text, textDim: mix(text, surface, 0.42), backdrop: alpha(surfaceDeep, 0.72) },
|
|
428
|
+
status: {
|
|
429
|
+
danger,
|
|
430
|
+
warning: seed.warning ?? "#e8a33d",
|
|
431
|
+
success: accent,
|
|
432
|
+
hostile: danger,
|
|
433
|
+
friendly: seed.friendly ?? mana,
|
|
434
|
+
neutral: stamina,
|
|
435
|
+
},
|
|
436
|
+
font: { display: seed.fontDisplay ?? defaultHudTheme.font.display },
|
|
437
|
+
frame: {
|
|
438
|
+
bg: `linear-gradient(180deg, ${alpha(surface, 0.92)}, ${alpha(surfaceDeep, 0.94)})`,
|
|
439
|
+
border: `1px solid ${edge}`,
|
|
440
|
+
glow: `0 6px 20px ${alpha(surfaceDeep, 0.7)}`,
|
|
441
|
+
},
|
|
442
|
+
bar: { track: alpha(surfaceDeep, 0.8), frame: surfaceDeep, text },
|
|
443
|
+
slot: { bg: alpha(surface, 0.8), border: `1px solid ${edge}`, radius: "6px" },
|
|
444
|
+
ring: alpha(edgeBright, 0.55),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
149
447
|
/** Resolves a preset name (or a full theme) to a `HudTheme`; falls back to the default theme. */
|
|
150
448
|
export function resolveHudTheme(theme) {
|
|
151
449
|
if (theme === undefined)
|