@jgengine/react 0.38.0 → 0.38.1

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 CHANGED
@@ -26,6 +26,25 @@ At publish, rename this heading to the new version and mirror the entries into
26
26
  `packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
27
27
  -->
28
28
 
29
+ ## 0.38.1
30
+
31
+ ### Migrate
32
+
33
+ - **Bump lockstep SDK packages to `^0.38.1`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.35.1`; `@jgengine/github` is `0.25.1`.
34
+ - Nothing to migrate. `variantLevels()` was renamed to `ModelVariantConfig.shadows` before any release shipped it, so no published version ever exported it and no consumer can be holding one. A level that wants to stop paying the near level's shadow cost writes `{ url, shadows: "none" }`; leaving `shadows` out keeps the model's own answer.
35
+
36
+ ### Added
37
+
38
+ - `ModelVariantConfig.shadows` (`@jgengine/core/world/modelLod`): a model variant level declares its own shadow participation as a plain field, `"inherit" | "cast" | "receive" | "both" | "none"`. `"inherit"`, the default, keeps the model's own `shadows`, so every existing model draws as before; a far level set to `"none"` leaves the shadow pass while still drawing its mesh.
39
+ - `ModelShadowMode` (`@jgengine/core/world/modelLod`) names the four-value shadow vocabulary, and `ModelConfig.shadows` uses it.
40
+ - `variantLevelModel` (`@jgengine/shell/render/singleVariantLod`) is the one place that resolves what a level draws and how it shadows; the single prop, batched prop and scatter paths all ask it, so a level's asset and its shadow answer cannot disagree.
41
+
42
+ ### Fixed
43
+
44
+ - `SaveSlotsPane` cards are one box each: equal tracks, `grid-auto-rows: 1fr`, a card edge that reads over a bright scene, and a name that ellipsises instead of widening the card. A save with a long name and one with none produce the same card box.
45
+ - `GameMenu` puts the primary verbs on their own scrim, so their labels stay legible over whatever the live scene is showing instead of relying on a text shadow.
46
+ - The theme emits three menu tokens the slot card and the verb surface read: `--jg-menu-card-height` (`clamp(244px, 27vh, 300px)`), `--jg-menu-card-thumb` (`clamp(88px, 14vh, 160px)`) and `--jg-menu-surface-radius` (`8px`). A game overrides them in its own `HudTheme` instead of patching the menu components.
47
+
29
48
  ## 0.38.0
30
49
 
31
50
  ### Migrate
package/dist/gameMenu.js CHANGED
@@ -266,7 +266,17 @@ export function GameMenu({ items, initialId, onSelect, onBack, onHighlight, deta
266
266
  nav.containerProps.onKeyDown(event);
267
267
  if (event.key === "ArrowDown" || event.key === "ArrowUp")
268
268
  event.stopPropagation();
269
- }, children: [_jsx("div", { "data-jg-game-menu-rail": "", "aria-hidden": "true", style: {
269
+ }, children: [_jsx("div", { "data-jg-game-menu-nav-surface": "", "aria-hidden": "true", style: {
270
+ position: "absolute",
271
+ inset: "-16px -22px",
272
+ borderRadius: "var(--jg-menu-surface-radius, 8px)",
273
+ background: "linear-gradient(96deg, rgba(4,7,11,0.6) 0%, rgba(4,7,11,0.34) 56%, rgba(4,7,11,0) 100%)",
274
+ maskImage: "linear-gradient(180deg, transparent 0, #000 9%, #000 91%, transparent 100%)",
275
+ WebkitMaskImage: "linear-gradient(180deg, transparent 0, #000 9%, #000 91%, transparent 100%)",
276
+ backdropFilter: "blur(2px)",
277
+ WebkitBackdropFilter: "blur(2px)",
278
+ pointerEvents: "none",
279
+ } }), _jsx("div", { "data-jg-game-menu-rail": "", "aria-hidden": "true", style: {
270
280
  position: "absolute",
271
281
  left: 0,
272
282
  right: 8,
@@ -14,15 +14,18 @@ export const SAVE_SLOT_CHARACTER_KEY = "character";
14
14
  const cardSurface = {
15
15
  position: "relative",
16
16
  width: "100%",
17
+ minWidth: 0,
18
+ height: "var(--jg-menu-card-height, clamp(244px, 27vh, 300px))",
17
19
  boxSizing: "border-box",
20
+ overflow: "hidden",
18
21
  display: "flex",
19
22
  flexDirection: "column",
20
23
  gap: 8,
21
- minWidth: 0,
22
24
  padding: 10,
23
25
  borderRadius: "var(--jg-menu-card-radius, 12px)",
24
- background: "linear-gradient(180deg, rgba(16,22,30,0.78), rgba(8,11,16,0.86))",
25
- boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.09), 0 14px 34px -18px rgba(0,0,0,0.9)",
26
+ background: "linear-gradient(180deg, rgba(16,22,30,0.9), rgba(8,11,16,0.94))",
27
+ border: "1px solid rgba(255,255,255,0.14)",
28
+ boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.05), 0 14px 34px -18px rgba(0,0,0,0.9)",
26
29
  backdropFilter: "blur(6px)",
27
30
  color: "var(--jg-text, #f2f6fb)",
28
31
  fontFamily: hudFont.body,
@@ -67,9 +70,9 @@ export function SaveSlotCard({ slot, now = Date.now, recent = false, onSelect, o
67
70
  cursor: onSelect === undefined ? "default" : "pointer",
68
71
  alignItems: "flex-start",
69
72
  justifyContent: "flex-end",
70
- border: "none",
73
+ border: "1px solid rgba(255,255,255,0.1)",
71
74
  boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.07), inset 0 0 40px -18px rgba(125,211,252,0.4), 0 10px 26px -20px rgba(0,0,0,0.9)",
72
- background: "linear-gradient(180deg, rgba(12,17,24,0.6), rgba(6,9,13,0.72))",
75
+ background: "linear-gradient(180deg, rgba(12,17,24,0.82), rgba(6,9,13,0.9))",
73
76
  ...style,
74
77
  }, children: [_jsx("div", { "data-save-slot-thumb": "", "data-empty": "", style: {
75
78
  width: "100%",
@@ -94,7 +97,8 @@ export function SaveSlotCard({ slot, now = Date.now, recent = false, onSelect, o
94
97
  }, children: [_jsxs("div", { "data-save-slot-thumb": "", style: {
95
98
  position: "relative",
96
99
  width: "100%",
97
- aspectRatio: "16 / 9",
100
+ flexShrink: 0,
101
+ height: "var(--jg-menu-card-thumb, clamp(88px, 14vh, 160px))",
98
102
  borderRadius: 8,
99
103
  overflow: "hidden",
100
104
  background: "radial-gradient(120% 120% at 30% 20%, rgba(125,211,252,0.22), rgba(8,12,18,0.9))",
@@ -124,6 +128,10 @@ export function SaveSlotCard({ slot, now = Date.now, recent = false, onSelect, o
124
128
  fontWeight: 800,
125
129
  letterSpacing: "0.01em",
126
130
  textShadow: "0 2px 12px rgba(0,0,0,0.8)",
131
+ maxWidth: "calc(100% - 16px)",
132
+ whiteSpace: "nowrap",
133
+ overflow: "hidden",
134
+ textOverflow: "ellipsis",
127
135
  }, children: slot.name ?? slot.id }), recent ? (_jsx("span", { "data-save-slot-recent": "", style: {
128
136
  position: "absolute",
129
137
  top: 6,
@@ -231,6 +239,10 @@ export function SaveSlotsPane({ model, onContinue, onLoad, onNew, onDelete, head
231
239
  }, children: ["Continue ", recent.name ?? recent.id] }))] }), _jsx("div", { ...nav.containerProps, "data-save-slots-grid": "", style: {
232
240
  display: "grid",
233
241
  gridTemplateColumns: columns === "auto" ? "repeat(auto-fill, minmax(230px, 1fr))" : `repeat(${columns}, minmax(0, 1fr))`,
242
+ // Every card gets the same box: equal tracks, equal rows, and content that cannot widen it.
243
+ gridAutoRows: "auto",
244
+ alignItems: "stretch",
245
+ flex: 1,
234
246
  gap: hudSpace("md"),
235
247
  minHeight: 0,
236
248
  overflowY: "auto",
@@ -240,25 +252,25 @@ export function SaveSlotsPane({ model, onContinue, onLoad, onNew, onDelete, head
240
252
  paddingBottom: 4,
241
253
  }, children: slots.map((slot) => {
242
254
  const active = nav.activeId === slot.id;
243
- return (_jsx("div", { style: { minWidth: 0, outline: active ? "none" : undefined }, children: _jsx(SaveSlotCard, { slot: slot, now: now, recent: recent !== null && recent.id === slot.id, ...(onLoad === undefined && onNew === undefined
244
- ? {}
245
- : {
246
- onSelect: (chosen) => {
247
- if (chosen.empty)
248
- onNew?.(chosen);
249
- else
250
- onLoad?.(chosen);
251
- },
252
- }), ...(onDelete === undefined ? {} : { onDelete }), style: {
253
- height: "100%",
254
- ...(active
255
- ? {
256
- boxShadow: "inset 0 0 0 1px var(--jg-accent, #7dd3fc), 0 0 0 1px rgba(125,211,252,0.5), 0 18px 40px -18px rgba(0,0,0,0.95)",
257
- transform: "translateY(-2px)",
258
- transition: `transform ${hudDuration("fast")} ${hudEase("spring")}`,
259
- }
260
- : {}),
261
- } }) }, slot.id));
255
+ return (_jsx(SaveSlotCard, { slot: slot, now: now, recent: recent !== null && recent.id === slot.id, ...(onLoad === undefined && onNew === undefined
256
+ ? {}
257
+ : {
258
+ onSelect: (chosen) => {
259
+ if (chosen.empty)
260
+ onNew?.(chosen);
261
+ else
262
+ onLoad?.(chosen);
263
+ },
264
+ }), ...(onDelete === undefined ? {} : { onDelete }), style: {
265
+ minWidth: 0,
266
+ ...(active
267
+ ? {
268
+ boxShadow: "inset 0 0 0 1px var(--jg-accent, #7dd3fc), 0 0 0 1px rgba(125,211,252,0.5), 0 18px 40px -18px rgba(0,0,0,0.95)",
269
+ transform: "translateY(-2px)",
270
+ transition: `transform ${hudDuration("fast")} ${hudEase("spring")}`,
271
+ }
272
+ : {}),
273
+ } }, slot.id));
262
274
  }) })] }));
263
275
  }
264
276
  /**
package/dist/hudTheme.js CHANGED
@@ -105,10 +105,14 @@ export function hudThemeVars(theme) {
105
105
  "--jg-frame-bg": frame.bg,
106
106
  "--jg-frame-border": frame.border,
107
107
  "--jg-frame-radius": frame.radius,
108
- // The game menu's two design tokens: a logo sized off the display step and card corners that
109
- // follow the frame radius, so a menu inherits the theme it is rendered under.
108
+ // The game menu's design tokens: a logo sized off the display step, card corners that follow the
109
+ // frame radius, and the card's fixed height and thumbnail band, so a slot card is the same box
110
+ // whatever it holds and a game can retune the menu from its theme.
110
111
  "--jg-menu-logo-size": "clamp(30px, 4.6vw, 66px)",
111
112
  "--jg-menu-card-radius": frame.radius,
113
+ "--jg-menu-card-height": "clamp(244px, 27vh, 300px)",
114
+ "--jg-menu-card-thumb": "clamp(88px, 14vh, 160px)",
115
+ "--jg-menu-surface-radius": "8px",
112
116
  "--jg-frame-glow": frame.glow,
113
117
  "--jg-slot-bg": slot.bg,
114
118
  "--jg-slot-border": slot.border,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/react",
3
- "version": "0.38.0",
3
+ "version": "0.38.1",
4
4
  "description": "React UI layer for JGengine: GameProvider, hooks, and headless primitives over @jgengine/core.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "styles": "bun scripts/styles.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@jgengine/core": "^0.38.0"
38
+ "@jgengine/core": "^0.38.1"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "^19.0.0",
@@ -6,7 +6,7 @@
6
6
 
7
7
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
8
8
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
9
- - `VERSION` (const): const VERSION: "0.38.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.38.1" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
@@ -285,7 +285,7 @@
285
285
 
286
286
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
287
287
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
288
- - `VERSION` (const): const VERSION: "0.38.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
288
+ - `VERSION` (const): const VERSION: "0.38.1" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
289
289
 
290
290
  ## @jgengine/core/runtime/adapter
291
291