@jgengine/react 0.13.0 → 0.15.0-next.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 (101) hide show
  1. package/CHANGELOG.md +529 -1
  2. package/dist/accessibility.d.ts +39 -0
  3. package/dist/accessibility.js +61 -0
  4. package/dist/achievements.d.ts +50 -0
  5. package/dist/achievements.js +94 -0
  6. package/dist/cameraShake.d.ts +52 -0
  7. package/dist/cameraShake.js +53 -0
  8. package/dist/coachMarks.d.ts +72 -0
  9. package/dist/coachMarks.js +136 -0
  10. package/dist/codex.d.ts +25 -0
  11. package/dist/codex.js +62 -0
  12. package/dist/comboMeter.d.ts +63 -0
  13. package/dist/comboMeter.js +93 -0
  14. package/dist/components.js +4 -3
  15. package/dist/creditsScreen.d.ts +19 -0
  16. package/dist/creditsScreen.js +31 -0
  17. package/dist/cutscene.d.ts +66 -0
  18. package/dist/cutscene.js +132 -0
  19. package/dist/damageDirection.d.ts +51 -0
  20. package/dist/damageDirection.js +103 -0
  21. package/dist/dialogueView.d.ts +69 -0
  22. package/dist/dialogueView.js +111 -0
  23. package/dist/entityFrames.d.ts +114 -0
  24. package/dist/entityFrames.js +55 -0
  25. package/dist/fastTravel.d.ts +36 -0
  26. package/dist/fastTravel.js +103 -0
  27. package/dist/floatingText.d.ts +84 -0
  28. package/dist/floatingText.js +61 -0
  29. package/dist/gameViewport.js +8 -12
  30. package/dist/hooks.d.ts +42 -0
  31. package/dist/hooks.js +85 -0
  32. package/dist/hudLayout.js +138 -9
  33. package/dist/hudTheme.d.ts +104 -7
  34. package/dist/hudTheme.js +300 -2
  35. package/dist/i18n.d.ts +29 -0
  36. package/dist/i18n.js +44 -0
  37. package/dist/index.d.ts +34 -0
  38. package/dist/index.js +34 -0
  39. package/dist/interactionPrompt.d.ts +80 -0
  40. package/dist/interactionPrompt.js +112 -0
  41. package/dist/keybindingMenu.d.ts +59 -0
  42. package/dist/keybindingMenu.js +146 -0
  43. package/dist/killFeed.d.ts +63 -0
  44. package/dist/killFeed.js +88 -0
  45. package/dist/map.d.ts +123 -2
  46. package/dist/map.js +239 -36
  47. package/dist/menuRouter.d.ts +51 -0
  48. package/dist/menuRouter.js +49 -0
  49. package/dist/modals.d.ts +131 -0
  50. package/dist/modals.js +168 -0
  51. package/dist/notifications.d.ts +39 -0
  52. package/dist/notifications.js +111 -0
  53. package/dist/objectiveBanner.d.ts +62 -0
  54. package/dist/objectiveBanner.js +129 -0
  55. package/dist/panels.d.ts +10 -2
  56. package/dist/panels.js +9 -3
  57. package/dist/photoMode.d.ts +21 -0
  58. package/dist/photoMode.js +39 -0
  59. package/dist/previewFixtures.d.ts +30 -0
  60. package/dist/previewFixtures.js +46 -0
  61. package/dist/provider.d.ts +15 -0
  62. package/dist/provider.js +17 -1
  63. package/dist/questTracker.d.ts +24 -0
  64. package/dist/questTracker.js +31 -0
  65. package/dist/quickMenu.d.ts +39 -0
  66. package/dist/quickMenu.js +79 -0
  67. package/dist/radialMenu.d.ts +50 -0
  68. package/dist/radialMenu.js +69 -0
  69. package/dist/saveSlots.d.ts +81 -0
  70. package/dist/saveSlots.js +138 -0
  71. package/dist/scoreboard.d.ts +67 -0
  72. package/dist/scoreboard.js +71 -0
  73. package/dist/settings.d.ts +9 -3
  74. package/dist/settings.js +10 -4
  75. package/dist/shopGrid.d.ts +70 -0
  76. package/dist/shopGrid.js +129 -0
  77. package/dist/statusEffectBar.d.ts +52 -0
  78. package/dist/statusEffectBar.js +57 -0
  79. package/dist/streetGeometryPreview.d.ts +7 -0
  80. package/dist/streetGeometryPreview.js +119 -0
  81. package/dist/talentTree.d.ts +60 -0
  82. package/dist/talentTree.js +217 -0
  83. package/dist/timerReadout.d.ts +90 -0
  84. package/dist/timerReadout.js +111 -0
  85. package/dist/useDebouncedCommit.d.ts +54 -0
  86. package/dist/useDebouncedCommit.js +81 -0
  87. package/dist/waveHud.d.ts +49 -0
  88. package/dist/waveHud.js +53 -0
  89. package/dist/waypointMarkers.d.ts +80 -0
  90. package/dist/waypointMarkers.js +116 -0
  91. package/package.json +3 -3
  92. package/skills/ce-handoff/SKILL.md +149 -0
  93. package/skills/jgengine/SKILL.md +7 -3
  94. package/skills/jgengine/api.md +17 -7
  95. package/skills/jgengine/capabilities.md +12 -0
  96. package/skills/jgengine/recipes/minimal-game.md +7 -4
  97. package/skills/jgengine-ui/SKILL.md +8 -3
  98. package/skills/jgengine-ui/api.md +732 -23
  99. package/skills/jgengine-ui/capabilities.md +371 -2
  100. package/skills/jgengine-ui/recipes/overhead-entity-frames.md +136 -0
  101. package/skills/jgengine-ui/reference.md +7 -4
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
- function theme(overrides) {
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)
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { type ReactNode } from "react";
2
+ import type { I18n, Locale, TParams } from "@jgengine/core/i18n/i18n";
3
+ /** Provide an {@link I18n} instance to the tree so `useT`/`useLocale` can read it. */
4
+ export declare function I18nProvider({ i18n, children }: {
5
+ i18n: I18n;
6
+ children: ReactNode;
7
+ }): ReactNode;
8
+ /** Read the current {@link I18n} from context; throws when no {@link I18nProvider} is above. */
9
+ export declare function useI18n(): I18n;
10
+ /** Subscribe to the active locale — re-renders on `setLocale`. */
11
+ export declare function useLocale(): Locale;
12
+ /**
13
+ * Return the translator bound to the current locale; the component re-renders
14
+ * when `setLocale` is called, so returned strings stay live.
15
+ *
16
+ * @capability use-translate live `t(key, params)` translator bound to the active locale, re-rendering on locale change
17
+ */
18
+ export declare function useT(): (key: string, params?: TParams) => string;
19
+ /** Return the plural-aware translator bound to the current locale. */
20
+ export declare function usePlural(): (key: string, count: number, params?: TParams) => string;
21
+ /**
22
+ * Render a translated message inline: `<Trans k="hud.score" params={{ value }} />`.
23
+ *
24
+ * @capability trans inline translated-message component bound to the active locale
25
+ */
26
+ export declare function Trans({ k, params }: {
27
+ k: string;
28
+ params?: TParams;
29
+ }): ReactNode;
package/dist/i18n.js ADDED
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useSyncExternalStore } from "react";
3
+ const I18nContext = createContext(null);
4
+ /** Provide an {@link I18n} instance to the tree so `useT`/`useLocale` can read it. */
5
+ export function I18nProvider({ i18n, children }) {
6
+ return _jsx(I18nContext.Provider, { value: i18n, children: children });
7
+ }
8
+ /** Read the current {@link I18n} from context; throws when no {@link I18nProvider} is above. */
9
+ export function useI18n() {
10
+ const i18n = useContext(I18nContext);
11
+ if (i18n === null)
12
+ throw new Error("useI18n must be used within an <I18nProvider>");
13
+ return i18n;
14
+ }
15
+ /** Subscribe to the active locale — re-renders on `setLocale`. */
16
+ export function useLocale() {
17
+ const i18n = useI18n();
18
+ return useSyncExternalStore(i18n.subscribe, i18n.locale, i18n.locale);
19
+ }
20
+ /**
21
+ * Return the translator bound to the current locale; the component re-renders
22
+ * when `setLocale` is called, so returned strings stay live.
23
+ *
24
+ * @capability use-translate live `t(key, params)` translator bound to the active locale, re-rendering on locale change
25
+ */
26
+ export function useT() {
27
+ const i18n = useI18n();
28
+ useLocale(); // re-render on locale change
29
+ return i18n.t;
30
+ }
31
+ /** Return the plural-aware translator bound to the current locale. */
32
+ export function usePlural() {
33
+ const i18n = useI18n();
34
+ useLocale();
35
+ return i18n.plural;
36
+ }
37
+ /**
38
+ * Render a translated message inline: `<Trans k="hud.score" params={{ value }} />`.
39
+ *
40
+ * @capability trans inline translated-message component bound to the active locale
41
+ */
42
+ export function Trans({ k, params }) {
43
+ return useT()(k, params);
44
+ }
package/dist/index.d.ts CHANGED
@@ -5,23 +5,53 @@ export * from "./chatBubbles.js";
5
5
  export * from "./voice.js";
6
6
  export * from "./social.js";
7
7
  export * from "./hooks.js";
8
+ export * from "./useDebouncedCommit.js";
8
9
  export * from "./store.js";
9
10
  export * from "./settings.js";
10
11
  export * from "./save.js";
12
+ export * from "./achievements.js";
13
+ export * from "./i18n.js";
14
+ export * from "./accessibility.js";
15
+ export * from "./photoMode.js";
16
+ export * from "./radialMenu.js";
17
+ export * from "./quickMenu.js";
18
+ export * from "./questTracker.js";
19
+ export * from "./codex.js";
20
+ export * from "./notifications.js";
21
+ export * from "./coachMarks.js";
22
+ export * from "./objectiveBanner.js";
23
+ export * from "./keybindingMenu.js";
24
+ export * from "./shopGrid.js";
25
+ export * from "./killFeed.js";
26
+ export * from "./waveHud.js";
27
+ export * from "./comboMeter.js";
28
+ export * from "./waypointMarkers.js";
29
+ export * from "./scoreboard.js";
30
+ export * from "./timerReadout.js";
31
+ export * from "./dialogueView.js";
32
+ export * from "./cutscene.js";
33
+ export * from "./damageDirection.js";
34
+ export * from "./modals.js";
35
+ export * from "./fastTravel.js";
36
+ export * from "./floatingText.js";
11
37
  export * from "./components.js";
12
38
  export * from "./hud.js";
13
39
  export * from "./bars.js";
40
+ export * from "./entityFrames.js";
14
41
  export * from "./barsPreview.js";
15
42
  export * from "./iconTreatment.js";
43
+ export * from "./statusEffectBar.js";
16
44
  export * from "./iconsPreview.js";
17
45
  export * from "./slots.js";
18
46
  export * from "./inventoryGrid.js";
19
47
  export * from "./panels.js";
20
48
  export * from "./characterSheet.js";
49
+ export * from "./talentTree.js";
21
50
  export * from "./hudTheme.js";
22
51
  export * from "./hudThemePreview.js";
23
52
  export * from "./hudFrame.js";
24
53
  export * from "./keyHint.js";
54
+ export * from "./interactionPrompt.js";
25
55
  export * from "./startScreen.js";
26
56
  export * from "./engineStore.js";
27
57
  export * from "./dragLayer.js";
@@ -36,3 +66,7 @@ export * from "./rotateDevice.js";
36
66
  export * from "./display.js";
37
67
  export * from "./map.js";
38
68
  export * from "./preview.js";
69
+ export * from "./previewFixtures.js";
70
+ export * from "./streetGeometryPreview.js";
71
+ export * from "./saveSlots.js";
72
+ export * from "./cameraShake.js";
package/dist/index.js CHANGED
@@ -5,23 +5,53 @@ export * from "./chatBubbles.js";
5
5
  export * from "./voice.js";
6
6
  export * from "./social.js";
7
7
  export * from "./hooks.js";
8
+ export * from "./useDebouncedCommit.js";
8
9
  export * from "./store.js";
9
10
  export * from "./settings.js";
10
11
  export * from "./save.js";
12
+ export * from "./achievements.js";
13
+ export * from "./i18n.js";
14
+ export * from "./accessibility.js";
15
+ export * from "./photoMode.js";
16
+ export * from "./radialMenu.js";
17
+ export * from "./quickMenu.js";
18
+ export * from "./questTracker.js";
19
+ export * from "./codex.js";
20
+ export * from "./notifications.js";
21
+ export * from "./coachMarks.js";
22
+ export * from "./objectiveBanner.js";
23
+ export * from "./keybindingMenu.js";
24
+ export * from "./shopGrid.js";
25
+ export * from "./killFeed.js";
26
+ export * from "./waveHud.js";
27
+ export * from "./comboMeter.js";
28
+ export * from "./waypointMarkers.js";
29
+ export * from "./scoreboard.js";
30
+ export * from "./timerReadout.js";
31
+ export * from "./dialogueView.js";
32
+ export * from "./cutscene.js";
33
+ export * from "./damageDirection.js";
34
+ export * from "./modals.js";
35
+ export * from "./fastTravel.js";
36
+ export * from "./floatingText.js";
11
37
  export * from "./components.js";
12
38
  export * from "./hud.js";
13
39
  export * from "./bars.js";
40
+ export * from "./entityFrames.js";
14
41
  export * from "./barsPreview.js";
15
42
  export * from "./iconTreatment.js";
43
+ export * from "./statusEffectBar.js";
16
44
  export * from "./iconsPreview.js";
17
45
  export * from "./slots.js";
18
46
  export * from "./inventoryGrid.js";
19
47
  export * from "./panels.js";
20
48
  export * from "./characterSheet.js";
49
+ export * from "./talentTree.js";
21
50
  export * from "./hudTheme.js";
22
51
  export * from "./hudThemePreview.js";
23
52
  export * from "./hudFrame.js";
24
53
  export * from "./keyHint.js";
54
+ export * from "./interactionPrompt.js";
25
55
  export * from "./startScreen.js";
26
56
  export * from "./engineStore.js";
27
57
  export * from "./dragLayer.js";
@@ -36,3 +66,7 @@ export * from "./rotateDevice.js";
36
66
  export * from "./display.js";
37
67
  export * from "./map.js";
38
68
  export * from "./preview.js";
69
+ export * from "./previewFixtures.js";
70
+ export * from "./streetGeometryPreview.js";
71
+ export * from "./saveSlots.js";
72
+ export * from "./cameraShake.js";
@@ -0,0 +1,80 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ import type { PositionedPrompt, PromptPoint } from "@jgengine/core/interaction/proximityPrompt";
3
+ import type { PromptRegistry } from "@jgengine/core/interaction/promptRegistry";
4
+ /**
5
+ * Subscribe to a {@link PromptRegistry} and re-`resolve` it whenever the player
6
+ * moves, returning the prompt to draw right now — or `null` when the player is
7
+ * out of range of every interactable. The registry notifies only on active-prompt
8
+ * *changes*, so this re-renders on transitions rather than every frame the hero
9
+ * moves. Feed it the player's `{ x, z }` position each frame; identical positions
10
+ * do no work.
11
+ *
12
+ * @capability use-interaction-prompt React hook binding a prompt registry to a moving player — resolves the nearest interactable prompt and returns the active one
13
+ */
14
+ export declare function useInteractionPrompt(registry: PromptRegistry, playerPosition: PromptPoint): PositionedPrompt | null;
15
+ /** Reskin tokens for {@link InteractionPrompt}. */
16
+ export interface InteractionPromptTheme {
17
+ /** Accent for the key cap and glow. Default reads `--jg-accent`. */
18
+ accent?: string;
19
+ /** Callout background. */
20
+ background?: string;
21
+ /** Callout border color. */
22
+ border?: string;
23
+ /** Label text color. */
24
+ text?: string;
25
+ /** Font family. */
26
+ fontFamily?: string;
27
+ }
28
+ /** Where the callout sits over the scene. */
29
+ export type InteractionPromptAnchor = "bottom-center" | "top-center" | "center";
30
+ /** Props for {@link InteractionPrompt}. */
31
+ export interface InteractionPromptProps {
32
+ /** The registry that owns the prompts and the active selection. */
33
+ registry: PromptRegistry;
34
+ /**
35
+ * The player's ground position. When supplied the host drives `resolve` on
36
+ * every position change; omit it if the game already calls `registry.resolve`
37
+ * from its own tick (the host still subscribes and renders the active prompt).
38
+ */
39
+ playerPosition?: PromptPoint;
40
+ /** Base reskin tokens. */
41
+ theme?: InteractionPromptTheme;
42
+ /**
43
+ * Per-prompt accent chooser. The registry never interprets a prompt's meaning,
44
+ * so the game decides the accent from the active prompt (its id, action id, or
45
+ * label) — e.g. a warm hue for a chest, cool for a door. Returning `undefined`
46
+ * falls back to the theme accent.
47
+ */
48
+ accentFor?: (prompt: PositionedPrompt) => string | undefined;
49
+ /**
50
+ * Key glyph shown on the cap for a `keybind` prompt, chosen from the prompt's
51
+ * free-string `actionId`. Default `"E"`.
52
+ */
53
+ keyFor?: (actionId: string) => string;
54
+ /**
55
+ * Hold progress `0..1` for a `gauge` prompt, by its free-string `gaugeId` —
56
+ * fills the ring/bar as the player holds. Default `0`.
57
+ */
58
+ gaugeProgress?: (gaugeId: string) => number;
59
+ /** Where the callout sits. Default `"bottom-center"`. */
60
+ anchor?: InteractionPromptAnchor;
61
+ /** Overlay `z-index`. Default `40`. */
62
+ zIndex?: number;
63
+ className?: string;
64
+ style?: CSSProperties;
65
+ }
66
+ /**
67
+ * A screen-anchored "Press E to …" interaction callout that renders the active
68
+ * prompt of a {@link PromptRegistry}. It subscribes to the registry and — when
69
+ * given `playerPosition` — re-resolves as the hero moves, showing the nearest
70
+ * in-range interactable and switching to a closer/higher-priority one as they
71
+ * cross radii. Each display kind renders as itself: a `keybind` shows a key cap
72
+ * plus its label ("Press [E] Open"), a `gauge` shows a hold bar filling with
73
+ * `gaugeProgress`, and a `label` shows plain text. Presentation only — the
74
+ * registry never branches on a prompt's meaning; the game colors each via
75
+ * `accentFor` and maps action ids to key glyphs via `keyFor`. HudTheme-skinnable
76
+ * through `--jg-accent` and {@link InteractionPromptTheme}.
77
+ *
78
+ * @capability interaction-prompt-host screen-anchored "press E to…" callout rendering a prompt registry's active interactable — keybind cap+label, gauge hold bar, or plain label, theme- and per-prompt-accent skinnable
79
+ */
80
+ export declare function InteractionPrompt({ registry, playerPosition, theme, accentFor, keyFor, gaugeProgress, anchor, zIndex, className, style, }: InteractionPromptProps): ReactNode;