@jgengine/react 0.10.0 → 0.12.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 +39 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/actionHud.d.ts +174 -0
- package/dist/actionHud.js +294 -0
- package/dist/bars.d.ts +103 -0
- package/dist/bars.js +180 -0
- package/dist/barsPreview.d.ts +4 -0
- package/dist/barsPreview.js +47 -0
- package/dist/cards.d.ts +59 -0
- package/dist/cards.js +116 -0
- package/dist/chat.d.ts +3 -1
- package/dist/chat.js +2 -2
- package/dist/components.d.ts +28 -1
- package/dist/components.js +36 -1
- package/dist/display.d.ts +1 -0
- package/dist/display.js +1 -0
- package/dist/engineStore.d.ts +3 -0
- package/dist/engineStore.js +3 -0
- package/dist/fogOverlay.d.ts +3 -0
- package/dist/fogOverlay.js +3 -0
- package/dist/gameViewport.js +16 -0
- package/dist/hooks.d.ts +40 -0
- package/dist/hooks.js +69 -1
- package/dist/hud.d.ts +104 -0
- package/dist/hud.js +155 -0
- package/dist/hudFrame.d.ts +47 -0
- package/dist/hudFrame.js +80 -0
- package/dist/hudLayout.d.ts +31 -2
- package/dist/hudLayout.js +127 -10
- package/dist/hudLayoutPersist.d.ts +23 -0
- package/dist/hudLayoutPersist.js +19 -0
- package/dist/hudTheme.d.ts +76 -0
- package/dist/hudTheme.js +156 -0
- package/dist/hudThemePreview.d.ts +4 -0
- package/dist/hudThemePreview.js +37 -0
- package/dist/iconTreatment.d.ts +50 -0
- package/dist/iconTreatment.js +84 -0
- package/dist/iconsPreview.d.ts +4 -0
- package/dist/iconsPreview.js +29 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/keyHint.d.ts +30 -0
- package/dist/keyHint.js +31 -0
- package/dist/map.d.ts +141 -11
- package/dist/map.js +138 -16
- package/dist/save.d.ts +25 -0
- package/dist/save.js +37 -0
- package/dist/selectSnapshot.d.ts +2 -0
- package/dist/selectSnapshot.js +2 -0
- package/dist/selectionHud.d.ts +139 -0
- package/dist/selectionHud.js +188 -0
- package/dist/skillCheckPaint.d.ts +2 -0
- package/dist/skillCheckPaint.js +2 -0
- package/dist/slots.d.ts +44 -0
- package/dist/slots.js +37 -0
- package/dist/startScreen.d.ts +62 -0
- package/dist/startScreen.js +62 -0
- package/dist/store.d.ts +24 -0
- package/dist/store.js +9 -0
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +54 -0
- package/skills/jgengine/api.md +527 -0
- package/skills/jgengine/capabilities.md +131 -0
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-ui/SKILL.md +41 -0
- package/skills/jgengine-ui/api.md +2007 -0
- package/skills/jgengine-ui/capabilities.md +211 -0
- package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
- package/skills/jgengine-ui/reference.md +513 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/llms.txt +0 -1724
package/dist/hudLayout.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CSSProperties, type ReactNode } from "react";
|
|
2
2
|
import { type HudAnchor, type HudLayoutStore } from "@jgengine/core/ui/hudLayout";
|
|
3
|
+
import { type EditorUiDocument } from "@jgengine/core/ui/hudDocument";
|
|
3
4
|
import { type HudPriority, type MobileHudBehavior } from "@jgengine/core/ui/gameLayout";
|
|
4
5
|
import type { GamePhase } from "@jgengine/core/game/gamePhase";
|
|
5
6
|
/** Whether a HUD element opted into `showDuring` is visible in the current phase; `undefined` = always visible (default). */
|
|
@@ -14,10 +15,25 @@ export interface HudEditChord {
|
|
|
14
15
|
hold: string;
|
|
15
16
|
press: string;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Layout state for `HudCanvas` — panel placements, edit-mode drag/resize, and per-game persistence.
|
|
20
|
+
*
|
|
21
|
+
* @capability hud-layout persistent, live-editable HUD panel layout state for `HudCanvas`
|
|
22
|
+
*/
|
|
17
23
|
export declare function useHudLayout(options?: {
|
|
18
24
|
storageKey?: string;
|
|
19
25
|
snap?: number;
|
|
20
26
|
locked?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Scene-document `ui` section — source of truth for panel placement/size.
|
|
29
|
+
* When provided, hydrates the layout store (and wins over legacy localStorage).
|
|
30
|
+
*/
|
|
31
|
+
documentUi?: EditorUiDocument;
|
|
32
|
+
/**
|
|
33
|
+
* When true (default), canvas moves/resizes invoke the injected {@link HudLayoutPersist}
|
|
34
|
+
* port (`HudLayoutPersistProvider` / `onPanelCommit`). No-op when no host has provided one.
|
|
35
|
+
*/
|
|
36
|
+
documentPatches?: boolean;
|
|
21
37
|
}): HudLayoutStore;
|
|
22
38
|
/**
|
|
23
39
|
* Full-viewport HUD surface. Panels declared with `HudPanel` flow into nine
|
|
@@ -25,6 +41,8 @@ export declare function useHudLayout(options?: {
|
|
|
25
41
|
* offsets, no manual clearance for sibling panels, the touch-control dock
|
|
26
42
|
* (`--jg-hud-dock-clearance`), or device safe areas. On compact displays the
|
|
27
43
|
* whole surface scales down and each panel applies its `compact` behavior.
|
|
44
|
+
*
|
|
45
|
+
* @capability hud-canvas the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
|
|
28
46
|
*/
|
|
29
47
|
export declare function HudCanvas({ layout, editChord, compactScale, showDuring, className, style, children, }: {
|
|
30
48
|
layout: HudLayoutStore;
|
|
@@ -43,8 +61,10 @@ export declare function HudCanvas({ layout, editChord, compactScale, showDuring,
|
|
|
43
61
|
* pointers panels stay draggable through the edit chord; a dragged panel
|
|
44
62
|
* leaves the flow and keeps its custom placement. On compact displays custom
|
|
45
63
|
* placements are ignored and the `compact` behavior applies.
|
|
64
|
+
*
|
|
65
|
+
* @capability hud-panel anchored HUD slot — drop any widget in a named, player-movable panel
|
|
46
66
|
*/
|
|
47
|
-
export declare function HudPanel({ id, anchor, order, compact: compactMode, chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region, className, style, children, }: {
|
|
67
|
+
export declare function HudPanel({ id, anchor, order, compact: compactMode, chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region, width, height, type, className, style, children, }: {
|
|
48
68
|
id: string;
|
|
49
69
|
anchor?: HudAnchor;
|
|
50
70
|
/** Stack position within the region, ascending outward from the screen edge. Default 0. */
|
|
@@ -55,7 +75,10 @@ export declare function HudPanel({ id, anchor, order, compact: compactMode, chip
|
|
|
55
75
|
chip?: string;
|
|
56
76
|
/** `false` lets pointer events pass through to the game (read-only panels). Default true. */
|
|
57
77
|
interactive?: boolean;
|
|
58
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Fallback pixel inset from the anchor when the scene document has no `ui.panels[id]`.
|
|
80
|
+
* Document layout is the source of truth once authored; TSX props are fallback-only.
|
|
81
|
+
*/
|
|
59
82
|
inset?: {
|
|
60
83
|
x: number;
|
|
61
84
|
y: number;
|
|
@@ -73,6 +96,12 @@ export declare function HudPanel({ id, anchor, order, compact: compactMode, chip
|
|
|
73
96
|
collisionGroup?: string;
|
|
74
97
|
/** Register this panel as a collision-tracked layout region. Default true. */
|
|
75
98
|
region?: boolean;
|
|
99
|
+
/** Fallback width when the document has not authored a size. */
|
|
100
|
+
width?: number;
|
|
101
|
+
/** Fallback height when the document has not authored a size. */
|
|
102
|
+
height?: number;
|
|
103
|
+
/** Panel type id ({@link registerHudPanelType}) — drives resize axes + ParamSchema. */
|
|
104
|
+
type?: string;
|
|
76
105
|
className?: string;
|
|
77
106
|
style?: CSSProperties;
|
|
78
107
|
children?: ReactNode;
|
package/dist/hudLayout.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
|
-
import { HUD_ANCHOR_FRACTIONS, anchoredPlacement, createHudLayout, isPanelDraggable, } from "@jgengine/core/ui/hudLayout";
|
|
4
|
+
import { HUD_ANCHOR_FRACTIONS, anchoredPlacement, createHudLayout, isPanelDraggable, registerActiveHudLayout, } from "@jgengine/core/ui/hudLayout";
|
|
5
|
+
import { resizePanelSize, resolvePanelResize, } from "@jgengine/core/ui/hudDocument";
|
|
5
6
|
import { hudScaleForViewport, overflowingPanels, resolveHudFit } from "@jgengine/core/ui/hudScale";
|
|
6
7
|
import { isMobileMode, } from "@jgengine/core/ui/gameLayout";
|
|
7
8
|
import { useDisplayProfile } from "./display.js";
|
|
8
9
|
import { useEngineState } from "./engineStore.js";
|
|
9
10
|
import { useGameLayoutMode, useRegisterLayoutRegion } from "./gameViewport.js";
|
|
10
11
|
import { useOptionalGamePhase } from "./hooks.js";
|
|
12
|
+
import { useHudLayoutPersist } from "./hudLayoutPersist.js";
|
|
11
13
|
import { useHudViewport } from "./hudViewport.js";
|
|
12
14
|
/** Whether a HUD element opted into `showDuring` is visible in the current phase; `undefined` = always visible (default). */
|
|
13
15
|
export function hudVisibleInPhase(showDuring, phase) {
|
|
@@ -20,18 +22,74 @@ const EDIT_BAR_Z = 100000;
|
|
|
20
22
|
const REGION_GAP = 10;
|
|
21
23
|
const COMPACT_HUD_SCALE = 0.85;
|
|
22
24
|
const HUD_ANCHORS = Object.keys(HUD_ANCHOR_FRACTIONS);
|
|
25
|
+
function sizeStyle(panel) {
|
|
26
|
+
if (panel === undefined)
|
|
27
|
+
return {};
|
|
28
|
+
return {
|
|
29
|
+
...(panel.width === undefined ? {} : { width: panel.width }),
|
|
30
|
+
...(panel.height === undefined ? {} : { height: panel.height }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function ResizeHandles({ axes, onResizeStart, }) {
|
|
34
|
+
if (axes === "none")
|
|
35
|
+
return null;
|
|
36
|
+
const handle = (edge, cursor, style) => (_jsx("div", { "data-hud-resize": edge, onPointerDown: (event) => {
|
|
37
|
+
event.stopPropagation();
|
|
38
|
+
onResizeStart(edge, event);
|
|
39
|
+
}, style: {
|
|
40
|
+
position: "absolute",
|
|
41
|
+
zIndex: 3,
|
|
42
|
+
pointerEvents: "auto",
|
|
43
|
+
background: "rgba(255, 255, 255, 0.85)",
|
|
44
|
+
cursor,
|
|
45
|
+
...style,
|
|
46
|
+
} }, edge));
|
|
47
|
+
return (_jsxs(_Fragment, { children: [axes === "x" || axes === "both"
|
|
48
|
+
? handle("e", "ew-resize", { top: 4, bottom: 4, right: -3, width: 6 })
|
|
49
|
+
: null, axes === "y" || axes === "both"
|
|
50
|
+
? handle("s", "ns-resize", { left: 4, right: 4, bottom: -3, height: 6 })
|
|
51
|
+
: null, axes === "both" ? handle("se", "nwse-resize", { right: -4, bottom: -4, width: 10, height: 10 }) : null] }));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Layout state for `HudCanvas` — panel placements, edit-mode drag/resize, and per-game persistence.
|
|
55
|
+
*
|
|
56
|
+
* @capability hud-layout persistent, live-editable HUD panel layout state for `HudCanvas`
|
|
57
|
+
*/
|
|
23
58
|
export function useHudLayout(options) {
|
|
24
59
|
const storageKey = options?.storageKey;
|
|
25
60
|
const snap = options?.snap;
|
|
26
61
|
const locked = options?.locked;
|
|
62
|
+
const documentUi = options?.documentUi;
|
|
63
|
+
const documentPatches = options?.documentPatches !== false;
|
|
64
|
+
const onPanelCommit = useHudLayoutPersist();
|
|
65
|
+
const onPanelCommitRef = useRef(onPanelCommit);
|
|
66
|
+
onPanelCommitRef.current = onPanelCommit;
|
|
27
67
|
const layout = useMemo(() => {
|
|
28
|
-
const store = createHudLayout({
|
|
29
|
-
|
|
68
|
+
const store = createHudLayout({
|
|
69
|
+
snap,
|
|
70
|
+
locked,
|
|
71
|
+
onDocumentPatch: documentPatches
|
|
72
|
+
? (id, panel) => {
|
|
73
|
+
onPanelCommitRef.current?.(id, panel);
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
});
|
|
77
|
+
if (documentUi !== undefined) {
|
|
78
|
+
store.applyDocumentUi(documentUi);
|
|
79
|
+
}
|
|
80
|
+
else if (storageKey !== undefined && typeof localStorage !== "undefined") {
|
|
30
81
|
store.hydrate(localStorage.getItem(STORAGE_PREFIX + storageKey));
|
|
31
82
|
}
|
|
32
83
|
return store;
|
|
33
|
-
}, [storageKey, snap, locked]);
|
|
84
|
+
}, [storageKey, snap, locked, documentPatches]);
|
|
34
85
|
useEffect(() => {
|
|
86
|
+
if (documentUi !== undefined) {
|
|
87
|
+
layout.applyDocumentUi(documentUi);
|
|
88
|
+
}
|
|
89
|
+
}, [layout, documentUi]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (documentUi !== undefined)
|
|
92
|
+
return;
|
|
35
93
|
if (storageKey === undefined || typeof localStorage === "undefined")
|
|
36
94
|
return;
|
|
37
95
|
let timer = null;
|
|
@@ -50,7 +108,7 @@ export function useHudLayout(options) {
|
|
|
50
108
|
localStorage.setItem(STORAGE_PREFIX + storageKey, layout.serialize());
|
|
51
109
|
}
|
|
52
110
|
};
|
|
53
|
-
}, [layout, storageKey]);
|
|
111
|
+
}, [layout, storageKey, documentUi]);
|
|
54
112
|
return layout;
|
|
55
113
|
}
|
|
56
114
|
const HudCanvasContext = createContext(null);
|
|
@@ -131,6 +189,8 @@ function edgePad(envInset, extra, basePx, scale) {
|
|
|
131
189
|
* offsets, no manual clearance for sibling panels, the touch-control dock
|
|
132
190
|
* (`--jg-hud-dock-clearance`), or device safe areas. On compact displays the
|
|
133
191
|
* whole surface scales down and each panel applies its `compact` behavior.
|
|
192
|
+
*
|
|
193
|
+
* @capability hud-canvas the HUD root surface — design-resolution scaling plus live panel-placement editing (F2+C)
|
|
134
194
|
*/
|
|
135
195
|
export function HudCanvas({ layout, editChord, compactScale, showDuring, className, style, children, }) {
|
|
136
196
|
const canvasRef = useRef(null);
|
|
@@ -160,6 +220,7 @@ export function HudCanvas({ layout, editChord, compactScale, showDuring, classNa
|
|
|
160
220
|
}, []);
|
|
161
221
|
const value = useMemo(() => ({ layout, canvasRef, regions, compact }), [layout, regions, compact]);
|
|
162
222
|
const editing = useEngineState(layout).editing;
|
|
223
|
+
useEffect(() => registerActiveHudLayout(layout), [layout]);
|
|
163
224
|
const chordEnabled = editChord !== false;
|
|
164
225
|
const hold = editChord === false ? undefined : (editChord?.hold ?? "F2");
|
|
165
226
|
const press = editChord === false ? undefined : (editChord?.press ?? "KeyC");
|
|
@@ -343,8 +404,10 @@ function HudChip({ label, anchor, children, }) {
|
|
|
343
404
|
* pointers panels stay draggable through the edit chord; a dragged panel
|
|
344
405
|
* leaves the flow and keeps its custom placement. On compact displays custom
|
|
345
406
|
* placements are ignored and the `compact` behavior applies.
|
|
407
|
+
*
|
|
408
|
+
* @capability hud-panel anchored HUD slot — drop any widget in a named, player-movable panel
|
|
346
409
|
*/
|
|
347
|
-
export function HudPanel({ id, anchor = "top-left", order, compact: compactMode = "keep", chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, className, style, children, }) {
|
|
410
|
+
export function HudPanel({ id, anchor = "top-left", order, compact: compactMode = "keep", chip, interactive, inset, locked, showDuring, priority, mobileBehavior, allowOverlapWith, collisionGroup, region = true, width, height, type, className, style, children, }) {
|
|
348
411
|
const ctx = useContext(HudCanvasContext);
|
|
349
412
|
const phase = useOptionalGamePhase();
|
|
350
413
|
const layoutMode = useGameLayoutMode();
|
|
@@ -355,12 +418,18 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
355
418
|
const registeredOnRef = useRef(null);
|
|
356
419
|
if (registeredOnRef.current !== layout) {
|
|
357
420
|
registeredOnRef.current = layout;
|
|
358
|
-
layout.register(id, anchoredPlacement(anchor, inset ?? { x: 16, y: 16 })
|
|
421
|
+
layout.register(id, anchoredPlacement(anchor, inset ?? { x: 16, y: 16 }), {
|
|
422
|
+
width,
|
|
423
|
+
height,
|
|
424
|
+
type,
|
|
425
|
+
});
|
|
359
426
|
}
|
|
360
427
|
const layoutState = useEngineState(layout);
|
|
361
428
|
const panel = layoutState.panels[id];
|
|
362
429
|
const draggable = !compact && locked !== true && isPanelDraggable(layoutState, id);
|
|
363
430
|
const editing = layoutState.editing && draggable;
|
|
431
|
+
const resizeSpec = resolvePanelResize(panel?.type ?? type);
|
|
432
|
+
const resizable = editing && resizeSpec.axes !== "none";
|
|
364
433
|
const rootRef = useRef(null);
|
|
365
434
|
const dragRef = useRef(null);
|
|
366
435
|
const suppressClickRef = useRef(false);
|
|
@@ -368,6 +437,7 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
368
437
|
const [dragging, setDragging] = useState(false);
|
|
369
438
|
useEffect(() => () => detachRef.current?.(), []);
|
|
370
439
|
const regionHidden = panel === undefined ||
|
|
440
|
+
panel.visible === false ||
|
|
371
441
|
(compact && compactMode === "hide") ||
|
|
372
442
|
(onMobile && mobileBehavior === "hidden") ||
|
|
373
443
|
!hudVisibleInPhase(showDuring, phase);
|
|
@@ -383,6 +453,8 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
383
453
|
const onPointerDown = (event) => {
|
|
384
454
|
if (!draggable || event.button !== 0)
|
|
385
455
|
return;
|
|
456
|
+
if (event.target?.closest?.("[data-hud-resize]") != null)
|
|
457
|
+
return;
|
|
386
458
|
const root = rootRef.current;
|
|
387
459
|
const canvas = canvasRef.current;
|
|
388
460
|
if (root === null || canvas === null)
|
|
@@ -439,6 +511,44 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
439
511
|
window.addEventListener("pointerup", onEnd);
|
|
440
512
|
window.addEventListener("pointercancel", onEnd);
|
|
441
513
|
};
|
|
514
|
+
const onResizeStart = (edge, event) => {
|
|
515
|
+
if (!resizable || event.button !== 0)
|
|
516
|
+
return;
|
|
517
|
+
const root = rootRef.current;
|
|
518
|
+
if (root === null)
|
|
519
|
+
return;
|
|
520
|
+
const startWidth = panel?.width ?? root.getBoundingClientRect().width;
|
|
521
|
+
const startHeight = panel?.height ?? root.getBoundingClientRect().height;
|
|
522
|
+
const pointerId = event.pointerId;
|
|
523
|
+
const startX = event.clientX;
|
|
524
|
+
const startY = event.clientY;
|
|
525
|
+
suppressClickRef.current = true;
|
|
526
|
+
layout.bringToFront(id);
|
|
527
|
+
const onMove = (moveEvent) => {
|
|
528
|
+
if (moveEvent.pointerId !== pointerId)
|
|
529
|
+
return;
|
|
530
|
+
const dw = edge === "s" ? 0 : moveEvent.clientX - startX;
|
|
531
|
+
const dh = edge === "e" ? 0 : moveEvent.clientY - startY;
|
|
532
|
+
const next = resizePanelSize({ width: startWidth, height: startHeight }, { dw, dh }, resizeSpec.axes, resizeSpec);
|
|
533
|
+
layout.resize(id, next);
|
|
534
|
+
};
|
|
535
|
+
const onEnd = (endEvent) => {
|
|
536
|
+
if (endEvent.pointerId !== pointerId)
|
|
537
|
+
return;
|
|
538
|
+
detachRef.current?.();
|
|
539
|
+
};
|
|
540
|
+
detachRef.current?.();
|
|
541
|
+
const detach = () => {
|
|
542
|
+
window.removeEventListener("pointermove", onMove);
|
|
543
|
+
window.removeEventListener("pointerup", onEnd);
|
|
544
|
+
window.removeEventListener("pointercancel", onEnd);
|
|
545
|
+
detachRef.current = null;
|
|
546
|
+
};
|
|
547
|
+
detachRef.current = detach;
|
|
548
|
+
window.addEventListener("pointermove", onMove);
|
|
549
|
+
window.addEventListener("pointerup", onEnd);
|
|
550
|
+
window.addEventListener("pointercancel", onEnd);
|
|
551
|
+
};
|
|
442
552
|
const onClickCapture = (event) => {
|
|
443
553
|
if (!suppressClickRef.current)
|
|
444
554
|
return;
|
|
@@ -458,15 +568,18 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
458
568
|
color: "rgba(255, 255, 255, 0.9)",
|
|
459
569
|
font: "10px/1.5 system-ui, sans-serif",
|
|
460
570
|
whiteSpace: "nowrap",
|
|
461
|
-
}, children: id })] })) : null;
|
|
571
|
+
}, children: id }), resizable ? _jsx(ResizeHandles, { axes: resizeSpec.axes, onResizeStart: onResizeStart }) : null] })) : null;
|
|
462
572
|
if (panel === undefined)
|
|
463
573
|
return null;
|
|
574
|
+
if (panel.visible === false)
|
|
575
|
+
return null;
|
|
464
576
|
if (compact && compactMode === "hide")
|
|
465
577
|
return null;
|
|
466
578
|
if (onMobile && mobileBehavior === "hidden")
|
|
467
579
|
return null;
|
|
468
580
|
if (!hudVisibleInPhase(showDuring, phase))
|
|
469
581
|
return null;
|
|
582
|
+
const authoredSize = sizeStyle(panel);
|
|
470
583
|
const flow = compact || !panel.moved;
|
|
471
584
|
if (flow) {
|
|
472
585
|
const regionEl = regions[compact ? anchor : panel.placement.anchor];
|
|
@@ -474,7 +587,7 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
474
587
|
return null;
|
|
475
588
|
const chipped = compact && compactMode === "chip";
|
|
476
589
|
const passThrough = interactive === false && !editing && !chipped;
|
|
477
|
-
return createPortal(_jsxs("div", { ref: rootRef, "data-hud-panel": id, "data-dragging": dragging ? "" : undefined, "data-hud-priority": priority, "data-hud-mobile-behavior": mobileBehavior, className: className, onPointerDown: onPointerDown, onClickCapture: onClickCapture, style: {
|
|
590
|
+
return createPortal(_jsxs("div", { ref: rootRef, "data-hud-panel": id, "data-dragging": dragging ? "" : undefined, "data-hud-priority": priority, "data-hud-mobile-behavior": mobileBehavior, "data-hud-type": panel.type ?? type, className: className, onPointerDown: onPointerDown, onClickCapture: onClickCapture, style: {
|
|
478
591
|
position: "relative",
|
|
479
592
|
order: order ?? 0,
|
|
480
593
|
pointerEvents: passThrough ? "none" : "auto",
|
|
@@ -483,6 +596,8 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
483
596
|
cursor: dragging ? "grabbing" : editing ? "grab" : undefined,
|
|
484
597
|
outline: editing ? "1px dashed rgba(255, 255, 255, 0.6)" : undefined,
|
|
485
598
|
outlineOffset: editing ? 2 : undefined,
|
|
599
|
+
boxSizing: "border-box",
|
|
600
|
+
...authoredSize,
|
|
486
601
|
...style,
|
|
487
602
|
}, children: [chipped ? (_jsx(HudChip, { label: chip ?? id, anchor: anchor, children: children })) : (children), editingChrome] }), regionEl);
|
|
488
603
|
}
|
|
@@ -503,7 +618,7 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
503
618
|
if (fx === 0.5 || fy === 0.5) {
|
|
504
619
|
position.transform = `translate(${fx === 0.5 ? "-50%" : "0"}, ${fy === 0.5 ? "-50%" : "0"})`;
|
|
505
620
|
}
|
|
506
|
-
return (_jsxs("div", { ref: rootRef, "data-hud-panel": id, "data-dragging": dragging ? "" : undefined, className: className, onPointerDown: onPointerDown, onClickCapture: onClickCapture, style: {
|
|
621
|
+
return (_jsxs("div", { ref: rootRef, "data-hud-panel": id, "data-dragging": dragging ? "" : undefined, "data-hud-type": panel.type ?? type, className: className, onPointerDown: onPointerDown, onClickCapture: onClickCapture, style: {
|
|
507
622
|
position: "absolute",
|
|
508
623
|
...position,
|
|
509
624
|
zIndex: panel.z,
|
|
@@ -513,6 +628,8 @@ export function HudPanel({ id, anchor = "top-left", order, compact: compactMode
|
|
|
513
628
|
cursor: dragging ? "grabbing" : editing ? "grab" : undefined,
|
|
514
629
|
outline: editing ? "1px dashed rgba(255, 255, 255, 0.6)" : undefined,
|
|
515
630
|
outlineOffset: editing ? 2 : undefined,
|
|
631
|
+
boxSizing: "border-box",
|
|
632
|
+
...authoredSize,
|
|
516
633
|
...style,
|
|
517
634
|
}, children: [children, editingChrome] }));
|
|
518
635
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { EditorUiPanelLayout } from "@jgengine/core/ui/hudDocument";
|
|
3
|
+
/**
|
|
4
|
+
* Host-owned callback that commits one HUD panel layout into the scene document
|
|
5
|
+
* (typically an undoable `setUiPanel` dispatch). Injected by the editor so product
|
|
6
|
+
* UI never reaches for editor globals.
|
|
7
|
+
*/
|
|
8
|
+
export type HudLayoutPersist = (id: string, panel: EditorUiPanelLayout) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Provides {@link HudLayoutPersist} / `onPanelCommit` to descendant `useHudLayout`
|
|
11
|
+
* calls. Mount around game UI when a host can accept document UI patches (editor).
|
|
12
|
+
* Absent provider → canvas moves/resizes are no-ops for document writes.
|
|
13
|
+
* @capability hud-layout-persist inject a host callback so canvas HUD edits commit into the scene document
|
|
14
|
+
*/
|
|
15
|
+
export declare function HudLayoutPersistProvider({ onPanelCommit, children, }: {
|
|
16
|
+
onPanelCommit: HudLayoutPersist;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* Injected panel-commit port, or `null` when no host has provided one.
|
|
21
|
+
* @capability hud-layout-persist-hook read the host panel-commit port from a HUD layout widget
|
|
22
|
+
*/
|
|
23
|
+
export declare function useHudLayoutPersist(): HudLayoutPersist | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
const HudLayoutPersistContext = createContext(null);
|
|
4
|
+
/**
|
|
5
|
+
* Provides {@link HudLayoutPersist} / `onPanelCommit` to descendant `useHudLayout`
|
|
6
|
+
* calls. Mount around game UI when a host can accept document UI patches (editor).
|
|
7
|
+
* Absent provider → canvas moves/resizes are no-ops for document writes.
|
|
8
|
+
* @capability hud-layout-persist inject a host callback so canvas HUD edits commit into the scene document
|
|
9
|
+
*/
|
|
10
|
+
export function HudLayoutPersistProvider({ onPanelCommit, children, }) {
|
|
11
|
+
return (_jsx(HudLayoutPersistContext.Provider, { value: onPanelCommit, children: children }));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Injected panel-commit port, or `null` when no host has provided one.
|
|
15
|
+
* @capability hud-layout-persist-hook read the host panel-commit port from a HUD layout widget
|
|
16
|
+
*/
|
|
17
|
+
export function useHudLayoutPersist() {
|
|
18
|
+
return useContext(HudLayoutPersistContext);
|
|
19
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* `HudTheme` (#1034): one token object that restyles shared token-driven building blocks at once.
|
|
4
|
+
* It is the superset of the atomic-bar tokens (#1033) — `hudThemeVars(theme)` emits the same
|
|
5
|
+
* `--jg-*` custom properties the bars already read, plus frame / slot / minimap-ring tokens — so
|
|
6
|
+
* setting a **game-authored** theme on any HUD ancestor re-skins those primitives. Purely CSS-token
|
|
7
|
+
* driven (no image assets). Tokens are not a finished UI: every game still owns composition, skin,
|
|
8
|
+
* and art direction (see AGENTS.md). Built-in presets are demo/scaffold starting points only.
|
|
9
|
+
*
|
|
10
|
+
* @capability hud-theme game-authored token object for bars, frames, slots, and minimap ring
|
|
11
|
+
*/
|
|
12
|
+
/** Per-readout fill colors plus a shared accent/glow. */
|
|
13
|
+
export interface HudThemePalette {
|
|
14
|
+
health: string;
|
|
15
|
+
healthLow: string;
|
|
16
|
+
mana: string;
|
|
17
|
+
stamina: string;
|
|
18
|
+
shield: string;
|
|
19
|
+
xp: string;
|
|
20
|
+
soul: string;
|
|
21
|
+
ammo: string;
|
|
22
|
+
boss: string;
|
|
23
|
+
/** Accent used for glow/keycap/active states. */
|
|
24
|
+
accent: string;
|
|
25
|
+
}
|
|
26
|
+
/** Frame material + shape tokens (panels, `HudFrame variation="themed"`). */
|
|
27
|
+
export interface HudThemeFrame {
|
|
28
|
+
/** Panel background (gradient or solid). */
|
|
29
|
+
bg: string;
|
|
30
|
+
border: string;
|
|
31
|
+
/** Corner radius (any CSS length, e.g. `"10px"`, `"0"`). */
|
|
32
|
+
radius: string;
|
|
33
|
+
/** Outer glow / drop shadow. */
|
|
34
|
+
glow: string;
|
|
35
|
+
}
|
|
36
|
+
/** Framed-trough tokens shared by every atomic bar. */
|
|
37
|
+
export interface HudThemeBar {
|
|
38
|
+
track: string;
|
|
39
|
+
frame: string;
|
|
40
|
+
height: string;
|
|
41
|
+
radius: string;
|
|
42
|
+
bevel: string;
|
|
43
|
+
text: string;
|
|
44
|
+
}
|
|
45
|
+
/** Action/inventory slot tokens. */
|
|
46
|
+
export interface HudThemeSlot {
|
|
47
|
+
bg: string;
|
|
48
|
+
border: string;
|
|
49
|
+
radius: string;
|
|
50
|
+
}
|
|
51
|
+
/** A full HUD theme. */
|
|
52
|
+
export interface HudTheme {
|
|
53
|
+
palette: HudThemePalette;
|
|
54
|
+
frame: HudThemeFrame;
|
|
55
|
+
bar: HudThemeBar;
|
|
56
|
+
slot: HudThemeSlot;
|
|
57
|
+
/** Minimap chrome ring color. */
|
|
58
|
+
ring: string;
|
|
59
|
+
}
|
|
60
|
+
/** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
|
|
61
|
+
export declare function hudThemeVars(theme: HudTheme): CSSProperties;
|
|
62
|
+
/** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
|
|
63
|
+
export declare const defaultHudTheme: HudTheme;
|
|
64
|
+
/** Built-in theme presets for demos/scaffold only — not product identity; games author their own theme. */
|
|
65
|
+
export declare const HUD_THEME_PRESETS: {
|
|
66
|
+
"arcane-stone": HudTheme;
|
|
67
|
+
"survival-wood": HudTheme;
|
|
68
|
+
"military-flat": HudTheme;
|
|
69
|
+
"sleek-hex": HudTheme;
|
|
70
|
+
"elemental-circle": HudTheme;
|
|
71
|
+
"sandbox-slot": HudTheme;
|
|
72
|
+
};
|
|
73
|
+
/** Built-in demo/scaffold theme preset name — not a game identity. */
|
|
74
|
+
export type HudThemePreset = keyof typeof HUD_THEME_PRESETS;
|
|
75
|
+
/** Resolves a preset name (or a full theme) to a `HudTheme`; falls back to the default theme. */
|
|
76
|
+
export declare function resolveHudTheme(theme?: HudThemePreset | HudTheme): HudTheme;
|
package/dist/hudTheme.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {} from "react";
|
|
2
|
+
/** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
|
|
3
|
+
export function hudThemeVars(theme) {
|
|
4
|
+
const { palette, frame, bar, slot } = theme;
|
|
5
|
+
return {
|
|
6
|
+
// atomic-bar tokens (same names the bars read — #1033)
|
|
7
|
+
"--jg-health": palette.health,
|
|
8
|
+
"--jg-health-low": palette.healthLow,
|
|
9
|
+
"--jg-mana": palette.mana,
|
|
10
|
+
"--jg-stamina": palette.stamina,
|
|
11
|
+
"--jg-shield": palette.shield,
|
|
12
|
+
"--jg-xp": palette.xp,
|
|
13
|
+
"--jg-soul": palette.soul,
|
|
14
|
+
"--jg-ammo": palette.ammo,
|
|
15
|
+
"--jg-boss": palette.boss,
|
|
16
|
+
"--jg-bar-track": bar.track,
|
|
17
|
+
"--jg-bar-frame": bar.frame,
|
|
18
|
+
"--jg-bar-height": bar.height,
|
|
19
|
+
"--jg-bar-radius": bar.radius,
|
|
20
|
+
"--jg-bar-bevel": bar.bevel,
|
|
21
|
+
"--jg-bar-text": bar.text,
|
|
22
|
+
// shared chrome tokens
|
|
23
|
+
"--jg-accent": palette.accent,
|
|
24
|
+
"--jg-frame-bg": frame.bg,
|
|
25
|
+
"--jg-frame-border": frame.border,
|
|
26
|
+
"--jg-frame-radius": frame.radius,
|
|
27
|
+
"--jg-frame-glow": frame.glow,
|
|
28
|
+
"--jg-slot-bg": slot.bg,
|
|
29
|
+
"--jg-slot-border": slot.border,
|
|
30
|
+
"--jg-slot-radius": slot.radius,
|
|
31
|
+
"--jg-ring": theme.ring,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
|
|
35
|
+
export const defaultHudTheme = {
|
|
36
|
+
palette: {
|
|
37
|
+
health: "#e5484d",
|
|
38
|
+
healthLow: "#b21e23",
|
|
39
|
+
mana: "#3b82f6",
|
|
40
|
+
stamina: "#84cc16",
|
|
41
|
+
shield: "#22d3ee",
|
|
42
|
+
xp: "#a855f7",
|
|
43
|
+
soul: "#c084fc",
|
|
44
|
+
ammo: "#f8b84e",
|
|
45
|
+
boss: "#ef4444",
|
|
46
|
+
accent: "#38bdf8",
|
|
47
|
+
},
|
|
48
|
+
frame: {
|
|
49
|
+
bg: "linear-gradient(180deg, rgba(20,24,32,0.86), rgba(10,12,16,0.9))",
|
|
50
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
51
|
+
radius: "10px",
|
|
52
|
+
glow: "0 6px 20px rgba(0,0,0,0.45)",
|
|
53
|
+
},
|
|
54
|
+
bar: {
|
|
55
|
+
track: "rgba(0,0,0,0.55)",
|
|
56
|
+
frame: "rgba(0,0,0,0.82)",
|
|
57
|
+
height: "18px",
|
|
58
|
+
radius: "4px",
|
|
59
|
+
bevel: "inset 0 2px 4px rgba(0,0,0,0.55), inset 0 -1px 2px rgba(255,255,255,0.08)",
|
|
60
|
+
text: "#f5f7fa",
|
|
61
|
+
},
|
|
62
|
+
slot: { bg: "rgba(12,14,20,0.7)", border: "1px solid rgba(255,255,255,0.12)", radius: "8px" },
|
|
63
|
+
ring: "rgba(140,170,200,0.35)",
|
|
64
|
+
};
|
|
65
|
+
function theme(overrides) {
|
|
66
|
+
return {
|
|
67
|
+
palette: { ...defaultHudTheme.palette, ...overrides.palette },
|
|
68
|
+
frame: { ...defaultHudTheme.frame, ...overrides.frame },
|
|
69
|
+
bar: { ...defaultHudTheme.bar, ...overrides.bar },
|
|
70
|
+
slot: { ...defaultHudTheme.slot, ...overrides.slot },
|
|
71
|
+
ring: overrides.ring ?? defaultHudTheme.ring,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Built-in theme presets for demos/scaffold only — not product identity; games author their own theme. */
|
|
75
|
+
export const HUD_THEME_PRESETS = {
|
|
76
|
+
"arcane-stone": theme({
|
|
77
|
+
palette: { health: "#c0392b", mana: "#7c5cff", xp: "#d4a017", accent: "#b483ff", shield: "#8e7bd6" },
|
|
78
|
+
frame: {
|
|
79
|
+
bg: "linear-gradient(180deg, #3a3550, #211d33)",
|
|
80
|
+
border: "2px solid #6b5b95",
|
|
81
|
+
radius: "6px",
|
|
82
|
+
glow: "0 0 18px rgba(124,92,255,0.35)",
|
|
83
|
+
},
|
|
84
|
+
bar: { frame: "#160f28", radius: "3px", height: "20px", track: "rgba(0,0,0,0.6)", bevel: "inset 0 2px 5px rgba(0,0,0,0.6)", text: "#efe7ff" },
|
|
85
|
+
slot: { bg: "#241f38", border: "2px solid #6b5b95", radius: "5px" },
|
|
86
|
+
ring: "rgba(124,92,255,0.5)",
|
|
87
|
+
}),
|
|
88
|
+
"survival-wood": theme({
|
|
89
|
+
palette: { health: "#c0553b", stamina: "#a3b545", xp: "#e0a94b", accent: "#d9a441", shield: "#7bb0a0" },
|
|
90
|
+
frame: {
|
|
91
|
+
bg: "linear-gradient(180deg, #4a3b28, #2e2117)",
|
|
92
|
+
border: "3px solid #6b4a2c",
|
|
93
|
+
radius: "4px",
|
|
94
|
+
glow: "0 4px 10px rgba(0,0,0,0.5)",
|
|
95
|
+
},
|
|
96
|
+
bar: { frame: "#2a1c10", radius: "2px", height: "18px", track: "rgba(30,20,10,0.7)", bevel: "inset 0 2px 4px rgba(0,0,0,0.6)", text: "#f3e6cf" },
|
|
97
|
+
slot: { bg: "#3a2a1a", border: "3px solid #6b4a2c", radius: "3px" },
|
|
98
|
+
ring: "rgba(107,74,44,0.6)",
|
|
99
|
+
}),
|
|
100
|
+
"military-flat": theme({
|
|
101
|
+
palette: { health: "#3fa34d", stamina: "#a8b545", xp: "#c9b458", accent: "#7d9b4e", shield: "#5f8fae" },
|
|
102
|
+
frame: {
|
|
103
|
+
bg: "linear-gradient(180deg, #2b3126, #1c2019)",
|
|
104
|
+
border: "1px solid #4a5340",
|
|
105
|
+
radius: "2px",
|
|
106
|
+
glow: "0 2px 6px rgba(0,0,0,0.5)",
|
|
107
|
+
},
|
|
108
|
+
bar: { frame: "#161a12", radius: "0px", height: "16px", track: "rgba(0,0,0,0.6)", bevel: "inset 0 1px 2px rgba(0,0,0,0.7)", text: "#e8efdc" },
|
|
109
|
+
slot: { bg: "#232a1e", border: "1px solid #4a5340", radius: "0px" },
|
|
110
|
+
ring: "rgba(125,155,78,0.5)",
|
|
111
|
+
}),
|
|
112
|
+
"sleek-hex": theme({
|
|
113
|
+
palette: { health: "#ff5a7a", mana: "#33c9ff", xp: "#38e8c0", accent: "#33c9ff", shield: "#33c9ff" },
|
|
114
|
+
frame: {
|
|
115
|
+
bg: "linear-gradient(180deg, rgba(18,24,34,0.9), rgba(8,12,18,0.94))",
|
|
116
|
+
border: "1px solid rgba(51,201,255,0.4)",
|
|
117
|
+
radius: "3px",
|
|
118
|
+
glow: "0 0 16px rgba(51,201,255,0.3)",
|
|
119
|
+
},
|
|
120
|
+
bar: { frame: "rgba(51,201,255,0.5)", radius: "2px", height: "14px", track: "rgba(0,0,0,0.5)", bevel: "inset 0 1px 3px rgba(0,0,0,0.5)", text: "#eafaff" },
|
|
121
|
+
slot: { bg: "rgba(12,18,26,0.8)", border: "1px solid rgba(51,201,255,0.4)", radius: "3px" },
|
|
122
|
+
ring: "rgba(51,201,255,0.55)",
|
|
123
|
+
}),
|
|
124
|
+
"elemental-circle": theme({
|
|
125
|
+
palette: { health: "#ff6b3d", mana: "#4aa8ff", stamina: "#7bd451", xp: "#ffcf40", accent: "#ffcf40", shield: "#4aa8ff" },
|
|
126
|
+
frame: {
|
|
127
|
+
bg: "radial-gradient(circle at 50% 40%, #2c2a3a, #16141f)",
|
|
128
|
+
border: "2px solid #d9a441",
|
|
129
|
+
radius: "9999px",
|
|
130
|
+
glow: "0 0 20px rgba(217,164,65,0.35)",
|
|
131
|
+
},
|
|
132
|
+
bar: { frame: "#1a1626", radius: "9999px", height: "16px", track: "rgba(0,0,0,0.55)", bevel: "inset 0 2px 4px rgba(0,0,0,0.6)", text: "#fff3d6" },
|
|
133
|
+
slot: { bg: "#221f30", border: "2px solid #d9a441", radius: "9999px" },
|
|
134
|
+
ring: "rgba(217,164,65,0.6)",
|
|
135
|
+
}),
|
|
136
|
+
"sandbox-slot": theme({
|
|
137
|
+
palette: { health: "#e0524a", stamina: "#77c043", xp: "#5ac0e0", accent: "#f0c040", shield: "#5ac0e0" },
|
|
138
|
+
frame: {
|
|
139
|
+
bg: "linear-gradient(180deg, #5a5a5a, #3a3a3a)",
|
|
140
|
+
border: "3px solid #2a2a2a",
|
|
141
|
+
radius: "2px",
|
|
142
|
+
glow: "3px 3px 0 rgba(0,0,0,0.5)",
|
|
143
|
+
},
|
|
144
|
+
bar: { frame: "#1e1e1e", radius: "0px", height: "18px", track: "rgba(0,0,0,0.6)", bevel: "inset 0 2px 3px rgba(0,0,0,0.6)", text: "#f4f4f4" },
|
|
145
|
+
slot: { bg: "#4a4a4a", border: "3px solid #2a2a2a", radius: "0px" },
|
|
146
|
+
ring: "rgba(240,192,64,0.5)",
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
/** Resolves a preset name (or a full theme) to a `HudTheme`; falls back to the default theme. */
|
|
150
|
+
export function resolveHudTheme(theme) {
|
|
151
|
+
if (theme === undefined)
|
|
152
|
+
return defaultHudTheme;
|
|
153
|
+
if (typeof theme === "string")
|
|
154
|
+
return HUD_THEME_PRESETS[theme] ?? defaultHudTheme;
|
|
155
|
+
return theme;
|
|
156
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {} from "react";
|
|
3
|
+
import { AmmoCounter, ExperienceBar, HealthBar, ManaBar, ShieldBar } from "./bars.js";
|
|
4
|
+
import { HudFrame } from "./hudFrame.js";
|
|
5
|
+
import { HUD_THEME_PRESETS, defaultHudTheme, hudThemeVars } from "./hudTheme.js";
|
|
6
|
+
/**
|
|
7
|
+
* A deterministic preset matrix for {@link HudTheme} (#1034): every genre preset × (bars + a themed
|
|
8
|
+
* frame + a slot row), rendered from static values. One `hudThemeVars(theme)` block per cell drives
|
|
9
|
+
* the atomic bars, the `HudFrame variation="themed"`, and the slot chrome together — proof that a
|
|
10
|
+
* single theme restyles all the shared chrome at once. Renders identically every time (screenshots).
|
|
11
|
+
*/
|
|
12
|
+
function Slot({ index }) {
|
|
13
|
+
return (_jsx("div", { style: {
|
|
14
|
+
width: 34,
|
|
15
|
+
height: 34,
|
|
16
|
+
background: "var(--jg-slot-bg, rgba(12,14,20,0.7))",
|
|
17
|
+
border: "var(--jg-slot-border, 1px solid rgba(255,255,255,0.12))",
|
|
18
|
+
borderRadius: "var(--jg-slot-radius, 8px)",
|
|
19
|
+
display: "flex",
|
|
20
|
+
alignItems: "center",
|
|
21
|
+
justifyContent: "center",
|
|
22
|
+
fontSize: 10,
|
|
23
|
+
color: "var(--jg-bar-text, #f4f6fb)",
|
|
24
|
+
opacity: index === 0 ? 1 : 0.6,
|
|
25
|
+
}, children: index + 1 }));
|
|
26
|
+
}
|
|
27
|
+
function ThemeCell({ name, theme }) {
|
|
28
|
+
return (_jsx("div", { style: { ...hudThemeVars(theme), width: 260 }, children: _jsx(HudFrame, { variation: "themed", title: name, padding: 12, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 7 }, children: [_jsx(HealthBar, { value: 78, max: 100, label: "HP", width: "100%" }), _jsx(ManaBar, { value: 50, max: 80, label: "MP", width: "100%" }), _jsx(ShieldBar, { value: 30, max: 60, label: "SH", width: "100%" }), _jsx(ExperienceBar, { value: 4, max: 10, segments: 10, showValue: false, label: "XP", width: "100%" }), _jsx(AmmoCounter, { loaded: 17, reserve: 90, magazine: 30, width: "100%" }), _jsx("div", { style: { display: "flex", gap: 6, marginTop: 2 }, children: [0, 1, 2, 3].map((index) => (_jsx(Slot, { index: index }, index))) })] }) }) }));
|
|
29
|
+
}
|
|
30
|
+
/** Renders the default theme plus every genre preset as a deterministic matrix. */
|
|
31
|
+
export function HudThemePreview({ className }) {
|
|
32
|
+
const cells = [
|
|
33
|
+
["default", defaultHudTheme],
|
|
34
|
+
...Object.entries(HUD_THEME_PRESETS),
|
|
35
|
+
];
|
|
36
|
+
return (_jsx("div", { "data-hud-theme-preview": true, className: className, style: { display: "flex", gap: 18, flexWrap: "wrap", padding: 22, fontFamily: "system-ui, sans-serif", background: "#0e1216" }, children: cells.map(([name, theme]) => (_jsx(ThemeCell, { name: name, theme: theme }, name))) }));
|
|
37
|
+
}
|