@jgengine/react 0.22.0 → 0.23.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/accessibility.d.ts +5 -0
  3. package/dist/accessibility.js +9 -0
  4. package/dist/achievements.js +4 -3
  5. package/dist/actionHud.d.ts +6 -1
  6. package/dist/actionHud.js +37 -17
  7. package/dist/bars.js +5 -2
  8. package/dist/barsPreview.js +2 -1
  9. package/dist/characterSheet.js +5 -4
  10. package/dist/coachMarks.js +3 -2
  11. package/dist/codex.js +4 -3
  12. package/dist/components.js +9 -2
  13. package/dist/creditsScreen.js +2 -1
  14. package/dist/engineStyles.d.ts +18 -0
  15. package/dist/engineStyles.js +119 -0
  16. package/dist/fastTravel.js +4 -3
  17. package/dist/hud.js +7 -6
  18. package/dist/hudButton.d.ts +29 -0
  19. package/dist/hudButton.js +31 -0
  20. package/dist/hudChromePreview.d.ts +8 -0
  21. package/dist/hudChromePreview.js +27 -0
  22. package/dist/hudFrame.js +15 -11
  23. package/dist/hudInteract.d.ts +26 -0
  24. package/dist/hudInteract.js +28 -0
  25. package/dist/hudLayout.js +4 -3
  26. package/dist/hudPanel.d.ts +31 -0
  27. package/dist/hudPanel.js +32 -0
  28. package/dist/hudTheme.d.ts +88 -1
  29. package/dist/hudTheme.js +123 -8
  30. package/dist/hudThemePreview.js +2 -1
  31. package/dist/hudThemeRoot.d.ts +15 -0
  32. package/dist/hudThemeRoot.js +20 -0
  33. package/dist/hudTokens.d.ts +39 -0
  34. package/dist/hudTokens.js +56 -0
  35. package/dist/iconTreatment.js +3 -2
  36. package/dist/iconsPreview.js +2 -1
  37. package/dist/keybindingMenu.js +3 -2
  38. package/dist/map.js +9 -8
  39. package/dist/modals.d.ts +6 -3
  40. package/dist/modals.js +42 -52
  41. package/dist/notices.d.ts +27 -0
  42. package/dist/notices.js +145 -0
  43. package/dist/notifications.js +4 -3
  44. package/dist/panels.d.ts +9 -2
  45. package/dist/panels.js +78 -35
  46. package/dist/photoMode.js +2 -1
  47. package/dist/presence.d.ts +9 -0
  48. package/dist/presence.js +25 -0
  49. package/dist/previewFixtures.js +6 -0
  50. package/dist/questTracker.js +3 -2
  51. package/dist/quickMenu.js +6 -5
  52. package/dist/saveSlots.js +4 -3
  53. package/dist/selectionHud.js +4 -3
  54. package/dist/shopGrid.js +2 -1
  55. package/dist/statusEffectBar.js +2 -1
  56. package/dist/streetGeometryPreview.js +3 -2
  57. package/dist/styles.css +72 -0
  58. package/dist/talentTree.js +3 -2
  59. package/dist/tweenNumber.d.ts +37 -0
  60. package/dist/tweenNumber.js +66 -0
  61. package/dist/uiCues.d.ts +20 -0
  62. package/dist/uiCues.js +37 -0
  63. package/dist/waveHud.js +2 -1
  64. package/dist/waypointMarkers.js +2 -1
  65. package/package.json +10 -6
  66. package/skills/jgengine/api.md +18 -18
  67. package/skills/jgengine-ui/SKILL.md +4 -0
  68. package/skills/jgengine-ui/api.md +233 -37
  69. package/skills/jgengine-ui/capabilities.md +52 -0
  70. package/skills/jgengine-ui/reference-primitives.md +4 -4
@@ -96,6 +96,73 @@ export interface HudThemeSlot {
96
96
  border: string;
97
97
  radius: string;
98
98
  }
99
+ /**
100
+ * Type scale (any CSS length). `min` is the readability floor every engine block clamps to;
101
+ * `xs` is the smallest step a block uses, so it never drops below `min`.
102
+ */
103
+ export interface HudThemeType {
104
+ min: string;
105
+ xs: string;
106
+ sm: string;
107
+ md: string;
108
+ lg: string;
109
+ xl: string;
110
+ xxl: string;
111
+ display: string;
112
+ /** Body line height (unitless). */
113
+ leading: string;
114
+ /** Letter spacing for uppercase labels and headers. */
115
+ tracking: string;
116
+ }
117
+ /** Spacing steps (any CSS length). */
118
+ export interface HudThemeSpace {
119
+ xs: string;
120
+ sm: string;
121
+ md: string;
122
+ lg: string;
123
+ xl: string;
124
+ }
125
+ /**
126
+ * Motion durations and easings. Blocks multiply durations by `--jg-motion-scale`, which the engine
127
+ * stylesheet sets to 0 under `prefers-reduced-motion` or `[data-reduced-motion="true"]`.
128
+ */
129
+ export interface HudThemeMotion {
130
+ fast: string;
131
+ base: string;
132
+ slow: string;
133
+ /** Standard ease for state changes. */
134
+ ease: string;
135
+ /** Decelerating ease for entrances. */
136
+ easeOut: string;
137
+ /** Slight overshoot for pops and presses releasing. */
138
+ spring: string;
139
+ }
140
+ /** Layered `box-shadow` stacks by height, plus the inner top highlight a lit panel edge gets. */
141
+ export interface HudThemeElevation {
142
+ /** Resting panels and buttons. */
143
+ raised: string;
144
+ /** Hovered buttons, popovers, tooltips. */
145
+ overlay: string;
146
+ /** Windows and modal dialogs. */
147
+ modal: string;
148
+ /** Inset top-edge highlight, layered into the stacks above by blocks that want a lit rim. */
149
+ highlight: string;
150
+ }
151
+ /** Interactive-state tokens shared by every engine button, slot, and window. */
152
+ export interface HudThemeState {
153
+ /** Overlay color washed over a hovered control. */
154
+ hover: string;
155
+ /** Hover lift as a `translate` Y length (e.g. `"-1px"`). */
156
+ hoverLift: string;
157
+ /** Overlay color washed over a pressed control. */
158
+ press: string;
159
+ /** Pressed `scale` factor (e.g. `"0.97"`). */
160
+ pressScale: string;
161
+ /** Focus ring as a `box-shadow` value. Unset derives a two-tone ring from the accent. */
162
+ focusRing?: string;
163
+ /** Opacity of a disabled control. */
164
+ disabledOpacity: string;
165
+ }
99
166
  /** A full HUD theme. Author one with {@link hudTheme} or {@link deriveHudTheme}. */
100
167
  export interface HudTheme {
101
168
  palette: HudThemePalette;
@@ -109,10 +176,25 @@ export interface HudTheme {
109
176
  /** Minimap chrome ring color. */
110
177
  ring: string;
111
178
  skin?: HudSkin;
179
+ /** Type scale; unset uses {@link defaultHudTheme}'s. */
180
+ type?: HudThemeType;
181
+ /** Spacing steps; unset uses {@link defaultHudTheme}'s. */
182
+ space?: HudThemeSpace;
183
+ /** Durations and easings; unset uses {@link defaultHudTheme}'s. */
184
+ motion?: HudThemeMotion;
185
+ /** Shadow stacks; unset uses {@link defaultHudTheme}'s. */
186
+ elevation?: HudThemeElevation;
187
+ /** Hover, press, focus, and disabled tokens; unset uses {@link defaultHudTheme}'s. */
188
+ state?: HudThemeState;
112
189
  }
113
190
  /** Emits theme CSS properties, resetting inherited bar fills when absent. Spread onto a HUD ancestor. */
114
191
  export declare function hudThemeVars(theme: HudTheme): CSSProperties;
115
- /** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
192
+ /**
193
+ * The engine's default HUD look, applied by the shell at the HUD root of every game that does not
194
+ * opt out: a strong system UI font stack, layered translucent panels with a lit top rim and crisp
195
+ * hairline borders, a 12px type floor, and motion that honours reduced-motion. Games override any
196
+ * part with their own theme on a HUD ancestor.
197
+ */
116
198
  export declare const defaultHudTheme: HudTheme;
117
199
  /** Per-group partial overrides accepted by {@link hudTheme}. */
118
200
  export interface HudThemeOverrides {
@@ -126,6 +208,11 @@ export interface HudThemeOverrides {
126
208
  slot?: Partial<HudThemeSlot>;
127
209
  ring?: string;
128
210
  skin?: HudSkin;
211
+ type?: Partial<HudThemeType>;
212
+ space?: Partial<HudThemeSpace>;
213
+ motion?: Partial<HudThemeMotion>;
214
+ elevation?: Partial<HudThemeElevation>;
215
+ state?: Partial<HudThemeState>;
129
216
  }
130
217
  /**
131
218
  * Builds a full {@link HudTheme} from per-group partials, filling the rest from
package/dist/hudTheme.js CHANGED
@@ -1,6 +1,43 @@
1
1
  import {} from "react";
2
2
  import { hudSkinVars } from "./hudSkin.js";
3
3
  export { hudSkinVars } from "./hudSkin.js";
4
+ const DEFAULT_TYPE = {
5
+ min: "12px",
6
+ xs: "12px",
7
+ sm: "13px",
8
+ md: "14px",
9
+ lg: "16px",
10
+ xl: "20px",
11
+ xxl: "26px",
12
+ display: "34px",
13
+ leading: "1.4",
14
+ tracking: "0.08em",
15
+ };
16
+ const DEFAULT_SPACE = { xs: "4px", sm: "8px", md: "12px", lg: "16px", xl: "24px" };
17
+ const DEFAULT_MOTION = {
18
+ fast: "120ms",
19
+ base: "180ms",
20
+ slow: "320ms",
21
+ ease: "cubic-bezier(0.2, 0, 0, 1)",
22
+ easeOut: "cubic-bezier(0.16, 1, 0.3, 1)",
23
+ spring: "cubic-bezier(0.34, 1.56, 0.64, 1)",
24
+ };
25
+ const DEFAULT_ELEVATION = {
26
+ raised: "0 1px 2px rgba(0,0,0,0.45), 0 4px 12px -2px rgba(0,0,0,0.4)",
27
+ overlay: "0 2px 4px rgba(0,0,0,0.4), 0 10px 24px -4px rgba(0,0,0,0.5)",
28
+ modal: "0 4px 8px rgba(0,0,0,0.35), 0 24px 56px -12px rgba(0,0,0,0.7)",
29
+ highlight: "inset 0 1px 0 rgba(255,255,255,0.08)",
30
+ };
31
+ const DEFAULT_STATE = {
32
+ hover: "rgba(255,255,255,0.07)",
33
+ hoverLift: "-1px",
34
+ press: "rgba(0,0,0,0.14)",
35
+ pressScale: "0.97",
36
+ disabledOpacity: "0.45",
37
+ };
38
+ function focusRing(theme) {
39
+ return theme.state?.focusRing ?? `0 0 0 2px ${theme.surface.surfaceDeep}, 0 0 0 4px ${theme.palette.accent}`;
40
+ }
4
41
  /** Emits theme CSS properties, resetting inherited bar fills when absent. Spread onto a HUD ancestor. */
5
42
  export function hudThemeVars(theme) {
6
43
  const { palette, surface, status, font, rarity, frame, bar, slot } = theme;
@@ -72,9 +109,73 @@ export function hudThemeVars(theme) {
72
109
  "--jg-slot-border": slot.border,
73
110
  "--jg-slot-radius": slot.radius,
74
111
  "--jg-ring": theme.ring,
112
+ ...typeVars(theme.type ?? DEFAULT_TYPE),
113
+ ...spaceVars(theme.space ?? DEFAULT_SPACE),
114
+ ...motionVars(theme.motion ?? DEFAULT_MOTION),
115
+ ...elevationVars(theme.elevation ?? DEFAULT_ELEVATION),
116
+ ...stateVars(theme.state ?? DEFAULT_STATE),
117
+ "--jg-focus-ring": focusRing(theme),
118
+ };
119
+ }
120
+ function typeVars(type) {
121
+ const floor = (size) => `max(${type.min}, ${size})`;
122
+ return {
123
+ "--jg-type-min": type.min,
124
+ "--jg-type-xs": floor(type.xs),
125
+ "--jg-type-sm": floor(type.sm),
126
+ "--jg-type-md": floor(type.md),
127
+ "--jg-type-lg": floor(type.lg),
128
+ "--jg-type-xl": floor(type.xl),
129
+ "--jg-type-xxl": floor(type.xxl),
130
+ "--jg-type-display": floor(type.display),
131
+ "--jg-leading": type.leading,
132
+ "--jg-tracking-label": type.tracking,
133
+ };
134
+ }
135
+ function spaceVars(space) {
136
+ return {
137
+ "--jg-space-xs": space.xs,
138
+ "--jg-space-sm": space.sm,
139
+ "--jg-space-md": space.md,
140
+ "--jg-space-lg": space.lg,
141
+ "--jg-space-xl": space.xl,
142
+ };
143
+ }
144
+ function motionVars(motion) {
145
+ return {
146
+ "--jg-motion-fast": motion.fast,
147
+ "--jg-motion-base": motion.base,
148
+ "--jg-motion-slow": motion.slow,
149
+ "--jg-ease": motion.ease,
150
+ "--jg-ease-out": motion.easeOut,
151
+ "--jg-ease-spring": motion.spring,
152
+ };
153
+ }
154
+ function elevationVars(elevation) {
155
+ return {
156
+ "--jg-elevation-raised": elevation.raised,
157
+ "--jg-elevation-overlay": elevation.overlay,
158
+ "--jg-elevation-modal": elevation.modal,
159
+ "--jg-highlight": elevation.highlight,
160
+ };
161
+ }
162
+ function stateVars(state) {
163
+ return {
164
+ "--jg-hover-overlay": state.hover,
165
+ "--jg-hover-lift": state.hoverLift,
166
+ "--jg-press-overlay": state.press,
167
+ "--jg-press-scale": state.pressScale,
168
+ "--jg-disabled-opacity": state.disabledOpacity,
75
169
  };
76
170
  }
77
- /** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
171
+ const UI_FONT = '"Inter Variable", Inter, "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
172
+ const DISPLAY_FONT = '"Inter Display", "Inter Variable", Inter, "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
173
+ /**
174
+ * The engine's default HUD look, applied by the shell at the HUD root of every game that does not
175
+ * opt out: a strong system UI font stack, layered translucent panels with a lit top rim and crisp
176
+ * hairline borders, a 12px type floor, and motion that honours reduced-motion. Games override any
177
+ * part with their own theme on a HUD ancestor.
178
+ */
78
179
  export const defaultHudTheme = {
79
180
  palette: {
80
181
  health: "#e5484d",
@@ -114,9 +215,9 @@ export const defaultHudTheme = {
114
215
  neutral: "#c9b458",
115
216
  },
116
217
  font: {
117
- display: '"Segoe UI", system-ui, sans-serif',
118
- numeric: 'Consolas, "Cascadia Mono", "SF Mono", "Roboto Mono", monospace',
119
- body: '"Segoe UI", system-ui, sans-serif',
218
+ display: DISPLAY_FONT,
219
+ numeric: UI_FONT,
220
+ body: UI_FONT,
120
221
  },
121
222
  rarity: {
122
223
  common: "#b4b2a8",
@@ -126,10 +227,10 @@ export const defaultHudTheme = {
126
227
  legendary: "#e0862e",
127
228
  },
128
229
  frame: {
129
- bg: "linear-gradient(180deg, rgba(20,24,32,0.86), rgba(10,12,16,0.9))",
130
- border: "1px solid rgba(255,255,255,0.12)",
230
+ bg: "linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0) 42%), linear-gradient(180deg, rgba(22,27,36,0.9), rgba(10,13,18,0.94))",
231
+ border: "1px solid rgba(255,255,255,0.13)",
131
232
  radius: "10px",
132
- glow: "0 6px 20px rgba(0,0,0,0.45)",
233
+ glow: `${DEFAULT_ELEVATION.highlight}, ${DEFAULT_ELEVATION.raised}`,
133
234
  },
134
235
  bar: {
135
236
  track: "rgba(0,0,0,0.55)",
@@ -139,8 +240,17 @@ export const defaultHudTheme = {
139
240
  bevel: "inset 0 2px 4px rgba(0,0,0,0.55), inset 0 -1px 2px rgba(255,255,255,0.08)",
140
241
  text: "#f5f7fa",
141
242
  },
142
- slot: { bg: "rgba(12,14,20,0.7)", border: "1px solid rgba(255,255,255,0.12)", radius: "8px" },
243
+ slot: {
244
+ bg: "linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0) 50%), rgba(12,15,21,0.78)",
245
+ border: "1px solid rgba(255,255,255,0.13)",
246
+ radius: "8px",
247
+ },
143
248
  ring: "rgba(140,170,200,0.35)",
249
+ type: DEFAULT_TYPE,
250
+ space: DEFAULT_SPACE,
251
+ motion: DEFAULT_MOTION,
252
+ elevation: DEFAULT_ELEVATION,
253
+ state: DEFAULT_STATE,
144
254
  };
145
255
  /**
146
256
  * Builds a full {@link HudTheme} from per-group partials, filling the rest from
@@ -161,6 +271,11 @@ export function hudTheme(overrides) {
161
271
  slot: { ...defaultHudTheme.slot, ...overrides.slot },
162
272
  ring: overrides.ring ?? defaultHudTheme.ring,
163
273
  skin: overrides.skin,
274
+ type: { ...DEFAULT_TYPE, ...overrides.type },
275
+ space: { ...DEFAULT_SPACE, ...overrides.space },
276
+ motion: { ...DEFAULT_MOTION, ...overrides.motion },
277
+ elevation: { ...DEFAULT_ELEVATION, ...overrides.elevation },
278
+ state: { ...DEFAULT_STATE, ...overrides.state },
164
279
  };
165
280
  }
166
281
  const theme = hudTheme;
@@ -3,6 +3,7 @@ import {} from "react";
3
3
  import { AmmoCounter, ExperienceBar, HealthBar, ManaBar, ShieldBar } from "./bars.js";
4
4
  import { HudFrame } from "./hudFrame.js";
5
5
  import { HUD_THEME_PRESETS, defaultHudTheme, hudThemeVars } from "./hudTheme.js";
6
+ import { hudType } from "./hudTokens.js";
6
7
  /**
7
8
  * A deterministic preset matrix for {@link HudTheme} (#1034): every genre preset × (bars + a themed
8
9
  * frame + a slot row), rendered from static values. One `hudThemeVars(theme)` block per cell drives
@@ -19,7 +20,7 @@ function Slot({ index }) {
19
20
  display: "flex",
20
21
  alignItems: "center",
21
22
  justifyContent: "center",
22
- fontSize: 10,
23
+ fontSize: hudType("xs"),
23
24
  color: "var(--jg-bar-text, #f4f6fb)",
24
25
  opacity: index === 0 ? 1 : 0.6,
25
26
  }, children: index + 1 }));
@@ -0,0 +1,15 @@
1
+ import { type ReactNode } from "react";
2
+ import { type HudTheme } from "./hudTheme.js";
3
+ /**
4
+ * The HUD root the shell mounts around every `GameUI`: a layout-free (`display: contents`)
5
+ * `[data-jg-hud]` element carrying the theme's `--jg-*` variables, plus the engine stylesheet. Unset
6
+ * `theme` applies {@link defaultHudTheme}; a game's own theme on any descendant still wins by
7
+ * inheritance. `false` opts out of the default theme and the generic HUD control defaults; the
8
+ * keyframes stay so engine blocks still animate.
9
+ *
10
+ * @capability hud-theme-root apply the default (or a game) HudTheme and the engine stylesheet at the HUD root
11
+ */
12
+ export declare function HudThemeRoot({ theme, children }: {
13
+ theme?: HudTheme | false;
14
+ children?: ReactNode;
15
+ }): ReactNode;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { useEngineStyles } from "./engineStyles.js";
4
+ import { defaultHudTheme, hudThemeVars } from "./hudTheme.js";
5
+ /**
6
+ * The HUD root the shell mounts around every `GameUI`: a layout-free (`display: contents`)
7
+ * `[data-jg-hud]` element carrying the theme's `--jg-*` variables, plus the engine stylesheet. Unset
8
+ * `theme` applies {@link defaultHudTheme}; a game's own theme on any descendant still wins by
9
+ * inheritance. `false` opts out of the default theme and the generic HUD control defaults; the
10
+ * keyframes stay so engine blocks still animate.
11
+ *
12
+ * @capability hud-theme-root apply the default (or a game) HudTheme and the engine stylesheet at the HUD root
13
+ */
14
+ export function HudThemeRoot({ theme, children }) {
15
+ useEngineStyles();
16
+ const vars = useMemo(() => (theme === false ? null : hudThemeVars(theme ?? defaultHudTheme)), [theme]);
17
+ if (vars === null)
18
+ return children;
19
+ return (_jsx("div", { "data-jg-hud": "", style: { display: "contents", ...vars }, children: children }));
20
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Inline-style accessors for the `HudTheme` scale tokens (#1034): each returns a `var(--jg-*)`
3
+ * reference with the default theme's value as fallback, so a block reads the active theme when one
4
+ * is applied and still renders the default look outside the shell.
5
+ *
6
+ * @capability hud-tokens inline-style accessors for the HudTheme type scale, spacing, motion, elevation, and focus ring
7
+ */
8
+ /** Type-scale step. `xs` is the smallest step and never renders below the theme's 12px floor. */
9
+ export type HudTypeStep = "xs" | "sm" | "md" | "lg" | "xl" | "xxl" | "display";
10
+ /** Spacing step. */
11
+ export type HudSpaceStep = "xs" | "sm" | "md" | "lg" | "xl";
12
+ /** Motion duration step. */
13
+ export type HudMotionSpeed = "fast" | "base" | "slow";
14
+ /** Motion easing. */
15
+ export type HudEase = "ease" | "easeOut" | "spring";
16
+ /** Shadow stack height. */
17
+ export type HudElevation = "raised" | "overlay" | "modal";
18
+ /** UI sans stack used when no theme supplies `--jg-font-body`. */
19
+ export declare const HUD_FONT_STACK = "\"Inter Variable\", Inter, \"SF Pro Text\", -apple-system, BlinkMacSystemFont, \"Segoe UI Variable Text\", \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
20
+ /** Font-size reference for a type-scale step, e.g. `fontSize: hudType("xs")`. */
21
+ export declare function hudType(step: HudTypeStep): string;
22
+ /** Spacing reference for a step, e.g. `padding: hudSpace("sm")`. */
23
+ export declare function hudSpace(step: HudSpaceStep): string;
24
+ /** Duration reference scaled by `--jg-motion-scale` (0 under reduced motion). */
25
+ export declare function hudDuration(speed?: HudMotionSpeed): string;
26
+ /** Easing reference. */
27
+ export declare function hudEase(ease?: HudEase): string;
28
+ /** `transition` value for the given properties at one speed and easing, honouring reduced motion. */
29
+ export declare function hudTransition(properties: string | readonly string[], speed?: HudMotionSpeed, ease?: HudEase): string;
30
+ /** `box-shadow` reference for a height, with the lit top rim layered in. */
31
+ export declare function hudElevation(level?: HudElevation): string;
32
+ /** Font-family references for each theme role. */
33
+ export declare const hudFont: {
34
+ readonly body: "var(--jg-font-body, \"Inter Variable\", Inter, \"SF Pro Text\", -apple-system, BlinkMacSystemFont, \"Segoe UI Variable Text\", \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)";
35
+ readonly display: "var(--jg-font-display, \"Inter Variable\", Inter, \"SF Pro Text\", -apple-system, BlinkMacSystemFont, \"Segoe UI Variable Text\", \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)";
36
+ readonly numeric: "var(--jg-font-numeric, \"Inter Variable\", Inter, \"SF Pro Text\", -apple-system, BlinkMacSystemFont, \"Segoe UI Variable Text\", \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif)";
37
+ };
38
+ /** Focus ring `box-shadow` reference. */
39
+ export declare const HUD_FOCUS_RING = "var(--jg-focus-ring, 0 0 0 2px #0a0c10, 0 0 0 4px var(--jg-accent, #38bdf8))";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Inline-style accessors for the `HudTheme` scale tokens (#1034): each returns a `var(--jg-*)`
3
+ * reference with the default theme's value as fallback, so a block reads the active theme when one
4
+ * is applied and still renders the default look outside the shell.
5
+ *
6
+ * @capability hud-tokens inline-style accessors for the HudTheme type scale, spacing, motion, elevation, and focus ring
7
+ */
8
+ const TYPE_PX = { xs: 12, sm: 13, md: 14, lg: 16, xl: 20, xxl: 26, display: 34 };
9
+ const SPACE_PX = { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 };
10
+ const MOTION_MS = { fast: 120, base: 180, slow: 320 };
11
+ const EASE = {
12
+ ease: ["--jg-ease", "cubic-bezier(0.2, 0, 0, 1)"],
13
+ easeOut: ["--jg-ease-out", "cubic-bezier(0.16, 1, 0.3, 1)"],
14
+ spring: ["--jg-ease-spring", "cubic-bezier(0.34, 1.56, 0.64, 1)"],
15
+ };
16
+ const ELEVATION = {
17
+ raised: "0 1px 2px rgba(0,0,0,0.45), 0 4px 12px -2px rgba(0,0,0,0.4)",
18
+ overlay: "0 2px 4px rgba(0,0,0,0.4), 0 10px 24px -4px rgba(0,0,0,0.5)",
19
+ modal: "0 4px 8px rgba(0,0,0,0.35), 0 24px 56px -12px rgba(0,0,0,0.7)",
20
+ };
21
+ /** UI sans stack used when no theme supplies `--jg-font-body`. */
22
+ export const HUD_FONT_STACK = '"Inter Variable", Inter, "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
23
+ /** Font-size reference for a type-scale step, e.g. `fontSize: hudType("xs")`. */
24
+ export function hudType(step) {
25
+ return `var(--jg-type-${step}, ${TYPE_PX[step]}px)`;
26
+ }
27
+ /** Spacing reference for a step, e.g. `padding: hudSpace("sm")`. */
28
+ export function hudSpace(step) {
29
+ return `var(--jg-space-${step}, ${SPACE_PX[step]}px)`;
30
+ }
31
+ /** Duration reference scaled by `--jg-motion-scale` (0 under reduced motion). */
32
+ export function hudDuration(speed = "fast") {
33
+ return `calc(var(--jg-motion-${speed}, ${MOTION_MS[speed]}ms) * var(--jg-motion-scale, 1))`;
34
+ }
35
+ /** Easing reference. */
36
+ export function hudEase(ease = "ease") {
37
+ const [name, fallback] = EASE[ease];
38
+ return `var(${name}, ${fallback})`;
39
+ }
40
+ /** `transition` value for the given properties at one speed and easing, honouring reduced motion. */
41
+ export function hudTransition(properties, speed = "fast", ease = "ease") {
42
+ const list = typeof properties === "string" ? [properties] : properties;
43
+ return list.map((property) => `${property} ${hudDuration(speed)} ${hudEase(ease)}`).join(", ");
44
+ }
45
+ /** `box-shadow` reference for a height, with the lit top rim layered in. */
46
+ export function hudElevation(level = "raised") {
47
+ return `var(--jg-highlight, inset 0 1px 0 rgba(255,255,255,0.08)), var(--jg-elevation-${level}, ${ELEVATION[level]})`;
48
+ }
49
+ /** Font-family references for each theme role. */
50
+ export const hudFont = {
51
+ body: `var(--jg-font-body, ${HUD_FONT_STACK})`,
52
+ display: `var(--jg-font-display, ${HUD_FONT_STACK})`,
53
+ numeric: `var(--jg-font-numeric, ${HUD_FONT_STACK})`,
54
+ };
55
+ /** Focus ring `box-shadow` reference. */
56
+ export const HUD_FOCUS_RING = "var(--jg-focus-ring, 0 0 0 2px #0a0c10, 0 0 0 4px var(--jg-accent, #38bdf8))";
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import {} from "react";
3
3
  import { GameIcon, iconForAction, iconForItemId } from "./gameIcons.js";
4
+ import { hudType } from "./hudTokens.js";
4
5
  const SCHOOL_GRADIENT = {
5
6
  fire: ["#ff8a3d", "#661805"],
6
7
  frost: ["#8fe3ff", "#0e3357"],
@@ -58,11 +59,11 @@ export function IconTreatment({ icon, glyph, school = "neutral", size = 44, coun
58
59
  justifyContent: "center",
59
60
  boxSizing: "border-box",
60
61
  ...style,
61
- }, children: [_jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(255,255,255,0.3), rgba(255,255,255,0) 46%)", pointerEvents: "none" } }), _jsx("div", { style: { position: "absolute", inset: 0, borderRadius: "inherit", boxShadow: "inset 0 0 12px rgba(0,0,0,0.55)", pointerEvents: "none" } }), content, keycap !== undefined ? (_jsx("span", { style: { position: "absolute", top: 1, left: 4, fontSize: 9, fontWeight: 700, color: "#fff", opacity: 0.75, textShadow: "0 1px 2px rgba(0,0,0,0.9)" }, children: keycap })) : null, count !== undefined && count > 1 ? (_jsx("span", { "data-count": true, style: {
62
+ }, children: [_jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(255,255,255,0.3), rgba(255,255,255,0) 46%)", pointerEvents: "none" } }), _jsx("div", { style: { position: "absolute", inset: 0, borderRadius: "inherit", boxShadow: "inset 0 0 12px rgba(0,0,0,0.55)", pointerEvents: "none" } }), content, keycap !== undefined ? (_jsx("span", { style: { position: "absolute", top: 1, left: 4, fontSize: hudType("xs"), fontWeight: 700, color: "#fff", opacity: 0.75, textShadow: "0 1px 2px rgba(0,0,0,0.9)" }, children: keycap })) : null, count !== undefined && count > 1 ? (_jsx("span", { "data-count": true, style: {
62
63
  position: "absolute",
63
64
  bottom: 1,
64
65
  right: 3,
65
- fontSize: 11,
66
+ fontSize: hudType("xs"),
66
67
  fontWeight: 800,
67
68
  color: "#fff",
68
69
  fontVariantNumeric: "tabular-nums",
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconTreatment } from "./iconTreatment.js";
3
+ import { hudType } from "./hudTokens.js";
3
4
  /**
4
5
  * A deterministic showcase of the painted {@link IconTreatment} (#1035): a school row (one gradient
5
6
  * each) and a mock hotbar of treated item icons with count badges and an active slot. Static, so it
@@ -25,5 +26,5 @@ const HOTBAR = [
25
26
  ];
26
27
  /** Renders the school-gradient row and a treated-icon hotbar — the deterministic #1035 fixture. */
27
28
  export function IconsPreview({ className }) {
28
- return (_jsxs("div", { "data-icons-preview": true, className: className, style: { display: "flex", flexDirection: "column", gap: 22, padding: 26, background: "#0e1216", fontFamily: "system-ui, sans-serif", color: "#e8edf2" }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", opacity: 0.6, marginBottom: 8 }, children: "school gradients" }), _jsx("div", { style: { display: "flex", gap: 10 }, children: SCHOOLS.map(([school, icon]) => (_jsx(IconTreatment, { icon: icon, school: school, size: 52 }, school))) })] }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", opacity: 0.6, marginBottom: 8 }, children: "hotbar \u2014 treated icons + count badges (slot 1 active)" }), _jsx("div", { style: { display: "flex", gap: 6 }, children: HOTBAR.map((entry, index) => (_jsx(IconTreatment, { icon: entry.icon, school: entry.school, size: 50, keycap: String(index + 1), active: index === 0, ...(entry.count === undefined ? {} : { count: entry.count }) }, index))) })] })] }));
29
+ return (_jsxs("div", { "data-icons-preview": true, className: className, style: { display: "flex", flexDirection: "column", gap: 22, padding: 26, background: "#0e1216", fontFamily: "system-ui, sans-serif", color: "#e8edf2" }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: hudType("xs"), fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", opacity: 0.6, marginBottom: 8 }, children: "school gradients" }), _jsx("div", { style: { display: "flex", gap: 10 }, children: SCHOOLS.map(([school, icon]) => (_jsx(IconTreatment, { icon: icon, school: school, size: 52 }, school))) })] }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: hudType("xs"), fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase", opacity: 0.6, marginBottom: 8 }, children: "hotbar \u2014 treated icons + count badges (slot 1 active)" }), _jsx("div", { style: { display: "flex", gap: 6 }, children: HOTBAR.map((entry, index) => (_jsx(IconTreatment, { icon: entry.icon, school: entry.school, size: 50, keycap: String(index + 1), active: index === 0, ...(entry.count === undefined ? {} : { count: entry.count }) }, index))) })] })] }));
29
30
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useReducer } from "react";
3
3
  import { Keycap } from "./keyHint.js";
4
+ import { hudType } from "./hudTokens.js";
4
5
  /**
5
6
  * Subscribe to a rebind session and re-render on every change (capture, reset,
6
7
  * arm/disarm, restore). Returns the session unchanged for convenient inline use.
@@ -98,7 +99,7 @@ function KeybindingRow({ row, armed, capturePrompt, theme, onArm, onReset, }) {
98
99
  marginLeft: 8,
99
100
  padding: "1px 7px",
100
101
  borderRadius: 999,
101
- fontSize: 10,
102
+ fontSize: hudType("xs"),
102
103
  fontWeight: 800,
103
104
  letterSpacing: "0.06em",
104
105
  textTransform: "uppercase",
@@ -128,7 +129,7 @@ function KeybindingRow({ row, armed, capturePrompt, theme, onArm, onReset, }) {
128
129
  opacity: row.isDefault ? 0.35 : 1,
129
130
  cursor: row.isDefault ? "default" : "pointer",
130
131
  padding: "4px 8px",
131
- fontSize: 11,
132
+ fontSize: hudType("xs"),
132
133
  }, children: "Reset" })] }));
133
134
  }
134
135
  function buttonStyle(color, border) {
package/dist/map.js CHANGED
@@ -4,6 +4,7 @@ import { DEFAULT_MARKER_KINDS, markerKindStyle, } from "@jgengine/core/world/mar
4
4
  import { bearingToCardinal, clampToMinimapEdge, compassBearing, headingToBearing, projectToMinimap, relativeBearing, unprojectFromMinimap, } from "@jgengine/core/world/minimap";
5
5
  import { mapLayerColor, } from "@jgengine/core/world/mapLayers";
6
6
  import { createFogDataUrl } from "./fogOverlay.js";
7
+ import { hudType } from "./hudTokens.js";
7
8
  function routeNodes(routes, project, keyPrefix) {
8
9
  if (routes === undefined)
9
10
  return [];
@@ -150,7 +151,7 @@ export function Minimap({ markers, center, worldRadius, fog, size = 176, facingY
150
151
  justifyContent: "space-between",
151
152
  alignItems: "center",
152
153
  marginBottom: 6,
153
- fontSize: 10,
154
+ fontSize: hudType("xs"),
154
155
  letterSpacing: 1.4,
155
156
  textTransform: "uppercase",
156
157
  color: "rgba(203,213,225,0.75)",
@@ -291,7 +292,7 @@ export function Compass({ facingYaw, center, markers, width = 340, fov = (Math.P
291
292
  left: x,
292
293
  bottom: 2,
293
294
  transform: "translateX(-50%)",
294
- fontSize: 11,
295
+ fontSize: hudType("xs"),
295
296
  color: style.color,
296
297
  fontWeight: 700,
297
298
  }, children: style.glyph }, marker.id));
@@ -317,7 +318,7 @@ export function MinimapPanel({ zoneLabel, clock, showCompass = true, compassProp
317
318
  width: minimapProps.size ?? 176,
318
319
  color: "#e2e8f0",
319
320
  fontFamily: "ui-sans-serif, system-ui, sans-serif",
320
- }, children: [zoneLabel !== undefined ? (_jsx("span", { className: zoneLabelClassName, "data-minimap-chrome-zone": true, style: { fontSize: 12, fontWeight: 700 }, children: zoneLabel })) : (_jsx("span", {})), clock !== undefined ? (_jsx("span", { className: clockClassName, "data-minimap-chrome-clock": true, style: { fontSize: 11, color: "rgba(226,232,240,0.75)" }, children: clock })) : null] })) : null, _jsx(Minimap, { ...minimapProps, children: children }), showCompass ? (_jsx("div", { className: compassClassName, "data-minimap-chrome-compass": true, style: { marginTop: 6 }, children: _jsx(Compass, { facingYaw: minimapProps.facingYaw ?? 0, center: minimapProps.center, markers: minimapProps.markers, width: minimapProps.size ?? 176, ...compassProps }) })) : null] }));
321
+ }, children: [zoneLabel !== undefined ? (_jsx("span", { className: zoneLabelClassName, "data-minimap-chrome-zone": true, style: { fontSize: 12, fontWeight: 700 }, children: zoneLabel })) : (_jsx("span", {})), clock !== undefined ? (_jsx("span", { className: clockClassName, "data-minimap-chrome-clock": true, style: { fontSize: hudType("xs"), color: "rgba(226,232,240,0.75)" }, children: clock })) : null] })) : null, _jsx(Minimap, { ...minimapProps, children: children }), showCompass ? (_jsx("div", { className: compassClassName, "data-minimap-chrome-compass": true, style: { marginTop: 6 }, children: _jsx(Compass, { facingYaw: minimapProps.facingYaw ?? 0, center: minimapProps.center, markers: minimapProps.markers, width: minimapProps.size ?? 176, ...compassProps }) })) : null] }));
321
322
  }
322
323
  const TRACK_PIP_DIAMETER = {
323
324
  dot: 6,
@@ -449,7 +450,7 @@ export function WorldMap({ markers, bounds, player, facingYaw = 0, fog, backgrou
449
450
  borderRadius: 6,
450
451
  background: "transparent",
451
452
  color: "rgba(226,232,240,0.85)",
452
- fontSize: 11,
453
+ fontSize: hudType("xs"),
453
454
  padding: "3px 9px",
454
455
  cursor: "pointer",
455
456
  }, children: "Close" })) : null] }), _jsx(WorldMapSurface, { markers: markers, bounds: bounds, player: player, facingYaw: facingYaw, fog: fog, background: background, width: width, height: height, kindStyles: kindStyles, routes: routes, zones: zones, cellStates: cellStates, onWorldClick: onWorldClick })] }));
@@ -605,7 +606,7 @@ export function FullscreenMap({ open = true, bounds, contentWidth = 900, minScal
605
606
  justifyContent: "space-between",
606
607
  padding: "10px 16px",
607
608
  borderBottom: "1px solid rgba(148,163,184,0.2)",
608
- }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 700, letterSpacing: 1.6, textTransform: "uppercase" }, children: title }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("button", { type: "button", "aria-label": "Zoom out", style: controlButton, onClick: () => zoomBy(1 / 1.2, contentWidth / 2, contentHeight / 2), children: "\u2212" }), _jsx("button", { type: "button", "aria-label": "Zoom in", style: controlButton, onClick: () => zoomBy(1.2, contentWidth / 2, contentHeight / 2), children: "+" }), _jsx("button", { type: "button", "aria-label": "Reset view", style: { ...controlButton, width: "auto", padding: "0 10px", fontSize: 11 }, onClick: () => setViewport(IDENTITY_VIEWPORT), children: "Reset" }), onClose !== undefined ? (_jsx("button", { type: "button", "aria-label": "Close map", style: { ...controlButton, width: "auto", padding: "0 12px", fontSize: 11 }, onClick: onClose, children: "Close" })) : null] })] }), _jsxs("div", { "data-fullscreen-map-viewport": true, "data-map-tool": tool, style: {
609
+ }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 700, letterSpacing: 1.6, textTransform: "uppercase" }, children: title }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("button", { type: "button", "aria-label": "Zoom out", style: controlButton, onClick: () => zoomBy(1 / 1.2, contentWidth / 2, contentHeight / 2), children: "\u2212" }), _jsx("button", { type: "button", "aria-label": "Zoom in", style: controlButton, onClick: () => zoomBy(1.2, contentWidth / 2, contentHeight / 2), children: "+" }), _jsx("button", { type: "button", "aria-label": "Reset view", style: { ...controlButton, width: "auto", padding: "0 10px", fontSize: hudType("xs") }, onClick: () => setViewport(IDENTITY_VIEWPORT), children: "Reset" }), onClose !== undefined ? (_jsx("button", { type: "button", "aria-label": "Close map", style: { ...controlButton, width: "auto", padding: "0 12px", fontSize: hudType("xs") }, onClick: onClose, children: "Close" })) : null] })] }), _jsxs("div", { "data-fullscreen-map-viewport": true, "data-map-tool": tool, style: {
609
610
  position: "relative",
610
611
  flex: 1,
611
612
  minHeight: 0,
@@ -680,9 +681,9 @@ export function MapLegend({ kinds, labels, kindStyles = DEFAULT_MARKER_KINDS, ti
680
681
  border: "1px solid rgba(148,163,184,0.28)",
681
682
  color: "#e2e8f0",
682
683
  fontFamily: "ui-sans-serif, system-ui, sans-serif",
683
- fontSize: 11,
684
+ fontSize: hudType("xs"),
684
685
  ...style,
685
- }, children: [title !== "" ? (_jsx("div", { style: { fontSize: 9, letterSpacing: 1.4, textTransform: "uppercase", color: "rgba(203,213,225,0.7)", marginBottom: 5 }, children: title })) : null, _jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 3 }, children: kinds.map((kind) => {
686
+ }, children: [title !== "" ? (_jsx("div", { style: { fontSize: hudType("xs"), letterSpacing: 1.4, textTransform: "uppercase", color: "rgba(203,213,225,0.7)", marginBottom: 5 }, children: title })) : null, _jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 3 }, children: kinds.map((kind) => {
686
687
  const kindStyle = markerKindStyle(kind, kindStyles);
687
688
  return (_jsxs("li", { "data-legend-kind": kind, style: { display: "flex", alignItems: "center", gap: 7 }, children: [_jsx("span", { "aria-hidden": true, style: { width: 14, textAlign: "center", color: kindStyle.color, fontWeight: 700 }, children: kindStyle.glyph }), _jsx("span", { children: labels?.[kind] ?? kind })] }, kind));
688
689
  }) })] }));
@@ -704,5 +705,5 @@ export function WaypointArrow({ relative, distance, label, formatDistance = (val
704
705
  fontFamily: "ui-sans-serif, system-ui, sans-serif",
705
706
  textShadow: "0 1px 2px rgba(0,0,0,0.9)",
706
707
  ...style,
707
- }, children: [_jsx("svg", { width: size, height: size, viewBox: "0 0 44 44", "aria-hidden": true, "data-waypoint-arrow-needle": true, children: _jsx("g", { transform: `rotate(${(relative * 180) / Math.PI} 22 22)`, children: _jsx("path", { d: "M22 6 L32 34 L22 27 L12 34 Z", fill: color, stroke: "rgba(0,0,0,0.7)", strokeWidth: 1.2, strokeLinejoin: "round" }) }) }), label !== undefined ? (_jsx("span", { style: { fontSize: 10, fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase" }, children: label })) : null, distance !== undefined ? (_jsx("span", { style: { fontSize: 11, fontVariantNumeric: "tabular-nums", color: "rgba(226,232,240,0.9)" }, children: formatDistance(distance) })) : null] }));
708
+ }, children: [_jsx("svg", { width: size, height: size, viewBox: "0 0 44 44", "aria-hidden": true, "data-waypoint-arrow-needle": true, children: _jsx("g", { transform: `rotate(${(relative * 180) / Math.PI} 22 22)`, children: _jsx("path", { d: "M22 6 L32 34 L22 27 L12 34 Z", fill: color, stroke: "rgba(0,0,0,0.7)", strokeWidth: 1.2, strokeLinejoin: "round" }) }) }), label !== undefined ? (_jsx("span", { style: { fontSize: hudType("xs"), fontWeight: 700, letterSpacing: 0.6, textTransform: "uppercase" }, children: label })) : null, distance !== undefined ? (_jsx("span", { style: { fontSize: hudType("xs"), fontVariantNumeric: "tabular-nums", color: "rgba(226,232,240,0.9)" }, children: formatDistance(distance) })) : null] }));
708
709
  }
package/dist/modals.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type CSSProperties, type ReactNode } from "react";
2
2
  import { type ModalRecord, type ModalStack } from "@jgengine/core/ui/modalStack";
3
+ import { type HudInteractListener } from "./hudInteract.js";
3
4
  /**
4
5
  * React chrome over the headless modal stack (`@jgengine/core/ui/modalStack`) — the blocking-overlay
5
6
  * layer a game raises over its scene: a pause menu, a "quit to menu?" confirm, a reward popup.
@@ -43,6 +44,8 @@ export interface ModalHostProps {
43
44
  closeOnEsc?: boolean;
44
45
  /** z-index of the host's stacking context — sits above the HUD/windows. Default 80. */
45
46
  zIndexBase?: number;
47
+ /** Called with `open` / `close` as modals show and the stack empties (`cue` is the modal kind) — the UI sound seam. */
48
+ onInteract?: HudInteractListener;
46
49
  className?: string;
47
50
  style?: CSSProperties;
48
51
  }
@@ -64,7 +67,7 @@ export interface ModalControls {
64
67
  *
65
68
  * @capability modal-host full-screen modal/dialog host over a modal stack — dimmed backdrop, focus trap, Esc/backdrop-to-cancel, aria-modal dialog
66
69
  */
67
- export declare function ModalHost({ stack, children, showBackdrop, backdrop, closeOnBackdrop, closeOnEsc, zIndexBase, className, style, }: ModalHostProps): ReactNode;
70
+ export declare function ModalHost({ stack, children, showBackdrop, backdrop, closeOnBackdrop, closeOnEsc, zIndexBase, onInteract, className, style, }: ModalHostProps): ReactNode;
68
71
  /** Props for {@link ConfirmDialog}. */
69
72
  export interface ConfirmDialogProps {
70
73
  title: ReactNode;
@@ -85,7 +88,7 @@ export interface ConfirmDialogProps {
85
88
  }
86
89
  /**
87
90
  * A generic two-button confirmation dialog — title, optional body, and Cancel / Confirm buttons over
88
- * a `HudTheme`-token-driven {@link HudFrame}. Presentation-only: wire `onConfirm`/`onCancel` to a
91
+ * a `HudTheme`-token-driven {@link HudPanel}. Presentation-only: wire `onConfirm`/`onCancel` to a
89
92
  * modal stack's `resolve` (typically inside a {@link ModalHost} render callback) and reskin via
90
93
  * `HudTheme` tokens or `danger` for a destructive action. It interprets nothing — the game supplies
91
94
  * the copy.
@@ -121,7 +124,7 @@ export interface PauseMenuProps {
121
124
  }
122
125
  /**
123
126
  * A reskinnable pause-menu building block: a primary Resume button plus a game-filled list of slot
124
- * items (Settings, Save, Quit, …), stacked in a `HudTheme`-token-driven {@link HudFrame}. It ships
127
+ * items (Settings, Save, Quit, …), stacked in a `HudTheme`-token-driven {@link HudPanel}. It ships
125
128
  * the common shape every game's pause screen needs and nothing genre-specific — the game supplies the
126
129
  * title, item labels/order, and destructive styling, and reskins with `HudTheme` tokens. Drop it into
127
130
  * a {@link ModalHost} keyed on a `"pause"` modal, or render it standalone.