jaml-ui 0.16.0 → 0.17.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/DESIGN.md +9 -11
- package/dist/assets.d.ts +6 -0
- package/dist/assets.js +9 -0
- package/dist/components/AnalyzerExplorer.d.ts +4 -1
- package/dist/components/AnalyzerExplorer.js +14 -48
- package/dist/components/GameCard.js +8 -7
- package/dist/components/JamlAestheticSelector.d.ts +4 -0
- package/dist/components/JamlAestheticSelector.js +6 -19
- package/dist/components/JamlAnalyzerFullscreen.d.ts +7 -1
- package/dist/components/JamlAnalyzerFullscreen.js +18 -47
- package/dist/components/JamlIde.js +12 -24
- package/dist/components/JamlIdeVisual.js +3 -56
- package/dist/components/JamlMapPreview.d.ts +6 -1
- package/dist/components/JamlMapPreview.js +99 -21
- package/dist/components/JamlSeedInput.d.ts +5 -0
- package/dist/components/JamlSeedInput.js +11 -14
- package/dist/components/JamlSpeedometer.d.ts +8 -8
- package/dist/components/JamlSpeedometer.js +24 -46
- package/dist/components/MotelyVersionBadge.d.ts +1 -3
- package/dist/components/MotelyVersionBadge.js +4 -16
- package/dist/components/jamlMap/JamlMapEditorDemo.d.ts +8 -0
- package/dist/components/jamlMap/JamlMapEditorDemo.js +170 -0
- package/dist/components/jamlMap/JokerPicker.d.ts +7 -0
- package/dist/components/jamlMap/JokerPicker.js +258 -0
- package/dist/components/jamlMap/MysterySlot.d.ts +32 -0
- package/dist/components/jamlMap/MysterySlot.js +109 -0
- package/dist/components/jamlMap/index.d.ts +3 -0
- package/dist/components/jamlMap/index.js +3 -0
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -2
- package/dist/decode/motelyItemDecoder.d.ts +10 -23
- package/dist/decode/motelyItemDecoder.js +103 -272
- package/dist/decode/motelySprite.d.ts +4 -0
- package/dist/decode/motelySprite.js +57 -0
- package/dist/hooks/analyzerStreamRegistry.js +30 -82
- package/dist/hooks/useAnalyzer.d.ts +10 -3
- package/dist/hooks/useAnalyzer.js +11 -6
- package/dist/hooks/useIntersectionObserver.d.ts +14 -0
- package/dist/hooks/useIntersectionObserver.js +50 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +4 -7
- package/dist/motely.d.ts +2 -2
- package/dist/motely.js +2 -2
- package/dist/motelyDisplay.d.ts +4 -623
- package/dist/motelyDisplay.js +26 -165
- package/dist/r3f/Card3D.d.ts +2 -2
- package/dist/r3f/Card3D.js +13 -48
- package/dist/r3f/JimboText3D.js +3 -2
- package/dist/render/CanvasRenderer.js +7 -171
- package/dist/sprites/spriteMapper.d.ts +71 -0
- package/dist/sprites/spriteMapper.js +40 -0
- package/dist/ui/JimboBadge.d.ts +8 -2
- package/dist/ui/JimboBadge.js +6 -22
- package/dist/ui/JimboToggleList.js +2 -7
- package/dist/ui/codeBlock.js +2 -3
- package/dist/ui/footer.d.ts +4 -0
- package/dist/ui/footer.js +6 -4
- package/dist/ui/hooks.d.ts +89 -0
- package/dist/ui/hooks.js +551 -0
- package/dist/ui/jimboBackground.js +2 -131
- package/dist/ui/jimboCopyRow.d.ts +4 -0
- package/dist/ui/jimboCopyRow.js +5 -22
- package/dist/ui/jimboFilterBar.d.ts +1 -4
- package/dist/ui/jimboFilterBar.js +2 -61
- package/dist/ui/jimboFlankNav.d.ts +1 -2
- package/dist/ui/jimboFlankNav.js +5 -30
- package/dist/ui/jimboTabs.d.ts +1 -5
- package/dist/ui/jimboTabs.js +6 -41
- package/dist/ui/jimboText.d.ts +1 -1
- package/dist/ui/jimboText.js +15 -32
- package/dist/ui/jimboTooltip.d.ts +1 -12
- package/dist/ui/jimboTooltip.js +6 -82
- package/dist/ui/panel.d.ts +2 -1
- package/dist/ui/panel.js +11 -47
- package/dist/ui/showcase.d.ts +4 -0
- package/dist/ui/showcase.js +9 -36
- package/dist/ui/sprites.js +3 -2
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +2 -0
- package/package.json +7 -6
- package/dist/decode/packedBalatroItem.d.ts +0 -13
- package/dist/decode/packedBalatroItem.js +0 -26
- package/dist/hooks/loadMotelyWasm.d.ts +0 -7
- package/dist/hooks/loadMotelyWasm.js +0 -16
- package/dist/utils/itemUtils.d.ts +0 -11
- package/dist/utils/itemUtils.js +0 -71
package/dist/motelyDisplay.js
CHANGED
|
@@ -1,189 +1,50 @@
|
|
|
1
1
|
import { Motely } from "motely-wasm";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
{ key: "CrimsonHeart", label: "Crimson Heart" },
|
|
7
|
-
{ key: "VerdantLeaf", label: "Verdant Leaf" },
|
|
8
|
-
{ key: "VioletVessel", label: "Violet Vessel" },
|
|
9
|
-
{ key: "TheArm", label: "The Arm" },
|
|
10
|
-
{ key: "TheClub", label: "The Club" },
|
|
11
|
-
{ key: "TheEye", label: "The Eye" },
|
|
12
|
-
{ key: "TheFish", label: "The Fish" },
|
|
13
|
-
{ key: "TheFlint", label: "The Flint" },
|
|
14
|
-
{ key: "TheGoad", label: "The Goad" },
|
|
15
|
-
{ key: "TheHead", label: "The Head" },
|
|
16
|
-
{ key: "TheHook", label: "The Hook" },
|
|
17
|
-
{ key: "TheHouse", label: "The House" },
|
|
18
|
-
{ key: "TheManacle", label: "The Manacle" },
|
|
19
|
-
{ key: "TheMark", label: "The Mark" },
|
|
20
|
-
{ key: "TheMouth", label: "The Mouth" },
|
|
21
|
-
{ key: "TheNeedle", label: "The Needle" },
|
|
22
|
-
{ key: "TheOx", label: "The Ox" },
|
|
23
|
-
{ key: "ThePillar", label: "The Pillar" },
|
|
24
|
-
{ key: "ThePlant", label: "The Plant" },
|
|
25
|
-
{ key: "ThePsychic", label: "The Psychic" },
|
|
26
|
-
{ key: "TheSerpent", label: "The Serpent" },
|
|
27
|
-
{ key: "TheTooth", label: "The Tooth" },
|
|
28
|
-
{ key: "TheWall", label: "The Wall" },
|
|
29
|
-
{ key: "TheWater", label: "The Water" },
|
|
30
|
-
{ key: "TheWheel", label: "The Wheel" },
|
|
31
|
-
{ key: "TheWindow", label: "The Window" },
|
|
32
|
-
];
|
|
33
|
-
const VOUCHER_ENTRIES = [
|
|
34
|
-
{ key: "Overstock", label: "Overstock" },
|
|
35
|
-
{ key: "OverstockPlus", label: "Overstock Plus" },
|
|
36
|
-
{ key: "ClearanceSale", label: "Clearance Sale" },
|
|
37
|
-
{ key: "Liquidation", label: "Liquidation" },
|
|
38
|
-
{ key: "Hone", label: "Hone" },
|
|
39
|
-
{ key: "GlowUp", label: "Glow Up" },
|
|
40
|
-
{ key: "RerollSurplus", label: "Reroll Surplus" },
|
|
41
|
-
{ key: "RerollGlut", label: "Reroll Glut" },
|
|
42
|
-
{ key: "CrystalBall", label: "Crystal Ball" },
|
|
43
|
-
{ key: "OmenGlobe", label: "Omen Globe" },
|
|
44
|
-
{ key: "Telescope", label: "Telescope" },
|
|
45
|
-
{ key: "Observatory", label: "Observatory" },
|
|
46
|
-
{ key: "Grabber", label: "Grabber" },
|
|
47
|
-
{ key: "NachoTong", label: "Nacho Tong" },
|
|
48
|
-
{ key: "Wasteful", label: "Wasteful" },
|
|
49
|
-
{ key: "Recyclomancy", label: "Recyclomancy" },
|
|
50
|
-
{ key: "TarotMerchant", label: "Tarot Merchant" },
|
|
51
|
-
{ key: "TarotTycoon", label: "Tarot Tycoon" },
|
|
52
|
-
{ key: "PlanetMerchant", label: "Planet Merchant" },
|
|
53
|
-
{ key: "PlanetTycoon", label: "Planet Tycoon" },
|
|
54
|
-
{ key: "SeedMoney", label: "Seed Money" },
|
|
55
|
-
{ key: "MoneyTree", label: "Money Tree" },
|
|
56
|
-
{ key: "Blank", label: "Blank" },
|
|
57
|
-
{ key: "Antimatter", label: "Antimatter" },
|
|
58
|
-
{ key: "MagicTrick", label: "Magic Trick" },
|
|
59
|
-
{ key: "Illusion", label: "Illusion" },
|
|
60
|
-
{ key: "Hieroglyph", label: "Hieroglyph" },
|
|
61
|
-
{ key: "Petroglyph", label: "Petroglyph" },
|
|
62
|
-
{ key: "DirectorsCut", label: "Director's Cut" },
|
|
63
|
-
{ key: "Retcon", label: "Retcon" },
|
|
64
|
-
{ key: "PaintBrush", label: "Paint Brush" },
|
|
65
|
-
{ key: "Palette", label: "Palette" },
|
|
66
|
-
];
|
|
67
|
-
const TAG_ENTRIES = [
|
|
68
|
-
{ key: "UncommonTag", label: "Uncommon Tag" },
|
|
69
|
-
{ key: "RareTag", label: "Rare Tag" },
|
|
70
|
-
{ key: "NegativeTag", label: "Negative Tag" },
|
|
71
|
-
{ key: "FoilTag", label: "Foil Tag" },
|
|
72
|
-
{ key: "HolographicTag", label: "Holographic Tag" },
|
|
73
|
-
{ key: "PolychromeTag", label: "Polychrome Tag" },
|
|
74
|
-
{ key: "InvestmentTag", label: "Investment Tag" },
|
|
75
|
-
{ key: "VoucherTag", label: "Voucher Tag" },
|
|
76
|
-
{ key: "BossTag", label: "Boss Tag" },
|
|
77
|
-
{ key: "StandardTag", label: "Standard Tag" },
|
|
78
|
-
{ key: "CharmTag", label: "Charm Tag" },
|
|
79
|
-
{ key: "MeteorTag", label: "Meteor Tag" },
|
|
80
|
-
{ key: "BuffoonTag", label: "Buffoon Tag" },
|
|
81
|
-
{ key: "HandyTag", label: "Handy Tag" },
|
|
82
|
-
{ key: "GarbageTag", label: "Garbage Tag" },
|
|
83
|
-
{ key: "EtherealTag", label: "Ethereal Tag" },
|
|
84
|
-
{ key: "CouponTag", label: "Coupon Tag" },
|
|
85
|
-
{ key: "DoubleTag", label: "Double Tag" },
|
|
86
|
-
{ key: "JuggleTag", label: "Juggle Tag" },
|
|
87
|
-
{ key: "D6Tag", label: "D6 Tag" },
|
|
88
|
-
{ key: "TopupTag", label: "Top-up Tag" },
|
|
89
|
-
{ key: "SpeedTag", label: "Speed Tag" },
|
|
90
|
-
{ key: "OrbitalTag", label: "Orbital Tag" },
|
|
91
|
-
{ key: "EconomyTag", label: "Economy Tag" },
|
|
92
|
-
];
|
|
93
|
-
const BOOSTER_PACK_ENTRIES = [
|
|
94
|
-
{ key: "Arcana", label: "Arcana Pack" },
|
|
95
|
-
{ key: "JumboArcana", label: "Jumbo Arcana Pack" },
|
|
96
|
-
{ key: "MegaArcana", label: "Mega Arcana Pack" },
|
|
97
|
-
{ key: "Celestial", label: "Celestial Pack" },
|
|
98
|
-
{ key: "JumboCelestial", label: "Jumbo Celestial Pack" },
|
|
99
|
-
{ key: "MegaCelestial", label: "Mega Celestial Pack" },
|
|
100
|
-
{ key: "Standard", label: "Standard Pack" },
|
|
101
|
-
{ key: "JumboStandard", label: "Jumbo Standard Pack" },
|
|
102
|
-
{ key: "MegaStandard", label: "Mega Standard Pack" },
|
|
103
|
-
{ key: "Buffoon", label: "Buffoon Pack" },
|
|
104
|
-
{ key: "JumboBuffoon", label: "Jumbo Buffoon Pack" },
|
|
105
|
-
{ key: "MegaBuffoon", label: "Mega Buffoon Pack" },
|
|
106
|
-
{ key: "Spectral", label: "Spectral Pack" },
|
|
107
|
-
{ key: "JumboSpectral", label: "Jumbo Spectral Pack" },
|
|
108
|
-
{ key: "MegaSpectral", label: "Mega Spectral Pack" },
|
|
109
|
-
];
|
|
110
|
-
const BOSS_VALUE_MASK = 0xff;
|
|
111
|
-
const ITEM_VALUE_MASK = 0xffff;
|
|
112
|
-
export const MOTELY_DISPLAY_SCHEMA = {
|
|
113
|
-
bosses: BOSS_ENTRIES,
|
|
114
|
-
vouchers: VOUCHER_ENTRIES,
|
|
115
|
-
tags: TAG_ENTRIES,
|
|
116
|
-
boosterPacks: BOOSTER_PACK_ENTRIES,
|
|
117
|
-
};
|
|
118
|
-
function createLabelLookup(entries) {
|
|
119
|
-
return {
|
|
120
|
-
keyToLabel: new Map(entries.map((entry) => [entry.key, entry.label])),
|
|
121
|
-
labelToKey: new Map(entries.map((entry) => [entry.label, entry.key])),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const bossLookup = createLabelLookup(BOSS_ENTRIES);
|
|
125
|
-
const voucherLookup = createLabelLookup(VOUCHER_ENTRIES);
|
|
126
|
-
const tagLookup = createLabelLookup(TAG_ENTRIES);
|
|
127
|
-
const boosterPackLookup = createLabelLookup(BOOSTER_PACK_ENTRIES);
|
|
2
|
+
/**
|
|
3
|
+
* Display-name utilities — thin wrappers over motely-wasm runtime enums.
|
|
4
|
+
* No hand-maintained lookup tables. The enum IS the source of truth.
|
|
5
|
+
*/
|
|
128
6
|
function spaceSplit(value) {
|
|
129
7
|
return value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
|
|
130
8
|
}
|
|
131
|
-
function displayNameFromKey(lookup, key, fallback) {
|
|
132
|
-
return lookup.keyToLabel.get(key) ?? fallback;
|
|
133
|
-
}
|
|
134
|
-
function keyFromDisplayName(lookup, label) {
|
|
135
|
-
return lookup.labelToKey.get(label) ?? null;
|
|
136
|
-
}
|
|
137
9
|
function runtimeEnumKey(enumObject, value) {
|
|
138
10
|
if (!enumObject || typeof enumObject !== "object")
|
|
139
11
|
return null;
|
|
140
12
|
const key = enumObject[String(value)];
|
|
141
13
|
return typeof key === "string" && key.length > 0 ? key : null;
|
|
142
14
|
}
|
|
143
|
-
|
|
144
|
-
return displayNameFromKey(bossLookup, key, spaceSplit(key));
|
|
145
|
-
}
|
|
146
|
-
export function motelyVoucherDisplayNameFromKey(key) {
|
|
147
|
-
return displayNameFromKey(voucherLookup, key, spaceSplit(key));
|
|
148
|
-
}
|
|
149
|
-
export function motelyTagDisplayNameFromKey(key) {
|
|
150
|
-
return displayNameFromKey(tagLookup, key, spaceSplit(key));
|
|
151
|
-
}
|
|
152
|
-
export function motelyBoosterPackDisplayNameFromKey(key) {
|
|
153
|
-
return displayNameFromKey(boosterPackLookup, key, `${spaceSplit(key)} Pack`);
|
|
154
|
-
}
|
|
155
|
-
export function motelyItemDisplayNameFromKey(key) {
|
|
156
|
-
return getItemDisplayName(key);
|
|
157
|
-
}
|
|
15
|
+
// ─── Public API (same signatures as before, zero hand-rolled tables) ────────
|
|
158
16
|
export function motelyBossDisplayName(value) {
|
|
159
|
-
const key = runtimeEnumKey(Motely.MotelyBossBlind, value &
|
|
160
|
-
return key === null ? `boss#${value}` :
|
|
17
|
+
const key = runtimeEnumKey(Motely.MotelyBossBlind, value & 0xff);
|
|
18
|
+
return key === null ? `boss#${value}` : spaceSplit(key);
|
|
19
|
+
}
|
|
20
|
+
export function motelyBossDisplayNameFromKey(key) {
|
|
21
|
+
return spaceSplit(key);
|
|
161
22
|
}
|
|
162
23
|
export function motelyVoucherDisplayName(value) {
|
|
163
24
|
const key = runtimeEnumKey(Motely.MotelyVoucher, value);
|
|
164
|
-
return key === null ? `voucher#${value}` :
|
|
25
|
+
return key === null ? `voucher#${value}` : spaceSplit(key);
|
|
26
|
+
}
|
|
27
|
+
export function motelyVoucherDisplayNameFromKey(key) {
|
|
28
|
+
return spaceSplit(key);
|
|
165
29
|
}
|
|
166
30
|
export function motelyTagDisplayName(value) {
|
|
167
31
|
const key = runtimeEnumKey(Motely.MotelyTag, value);
|
|
168
|
-
return key === null ? `tag#${value}` :
|
|
32
|
+
return key === null ? `tag#${value}` : spaceSplit(key);
|
|
33
|
+
}
|
|
34
|
+
export function motelyTagDisplayNameFromKey(key) {
|
|
35
|
+
return spaceSplit(key);
|
|
169
36
|
}
|
|
170
37
|
export function motelyBoosterPackDisplayName(value) {
|
|
171
38
|
const key = runtimeEnumKey(Motely.MotelyBoosterPack, value);
|
|
172
|
-
return key === null ? `pack#${value}` :
|
|
39
|
+
return key === null ? `pack#${value}` : spaceSplit(key);
|
|
173
40
|
}
|
|
174
|
-
export function
|
|
175
|
-
|
|
176
|
-
return key === null ? `item#${value}` : motelyItemDisplayNameFromKey(key);
|
|
177
|
-
}
|
|
178
|
-
export function motelyBossKeyFromDisplayName(label) {
|
|
179
|
-
return keyFromDisplayName(bossLookup, label);
|
|
180
|
-
}
|
|
181
|
-
export function motelyVoucherKeyFromDisplayName(label) {
|
|
182
|
-
return keyFromDisplayName(voucherLookup, label);
|
|
41
|
+
export function motelyBoosterPackDisplayNameFromKey(key) {
|
|
42
|
+
return `${spaceSplit(key)} Pack`;
|
|
183
43
|
}
|
|
184
|
-
export function
|
|
185
|
-
return
|
|
44
|
+
export function motelyItemDisplayNameFromKey(key) {
|
|
45
|
+
return spaceSplit(key);
|
|
186
46
|
}
|
|
187
|
-
export function
|
|
188
|
-
|
|
47
|
+
export function motelyItemDisplayNameFromValue(value) {
|
|
48
|
+
const key = runtimeEnumKey(Motely.MotelyItemType, value & 0xffff);
|
|
49
|
+
return key === null ? `item#${value}` : spaceSplit(key);
|
|
189
50
|
}
|
package/dist/r3f/Card3D.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MotelySpriteData } from '../decode/motelySprite.js';
|
|
2
2
|
export declare const CARD_DIMENSIONS: {
|
|
3
3
|
readonly WIDTH: 0.7;
|
|
4
4
|
readonly HEIGHT: 0.95;
|
|
@@ -13,7 +13,7 @@ export declare const CARD_MAGNET: {
|
|
|
13
13
|
readonly LERP_OUT: 10;
|
|
14
14
|
};
|
|
15
15
|
export interface Card3DProps {
|
|
16
|
-
sprite:
|
|
16
|
+
sprite: MotelySpriteData;
|
|
17
17
|
position?: [number, number, number];
|
|
18
18
|
rotation?: [number, number, number];
|
|
19
19
|
selected?: boolean;
|
package/dist/r3f/Card3D.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useMemo, useState,
|
|
4
|
-
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
import { useRef, useMemo, useState, memo } from 'react';
|
|
4
|
+
import { useFrame, useLoader } from '@react-three/fiber';
|
|
5
5
|
import { useSpring, animated } from '@react-spring/three';
|
|
6
6
|
import * as THREE from 'three';
|
|
7
|
-
import { SPRITE_SHEETS } from '../sprites/spriteData.js';
|
|
8
7
|
export const CARD_DIMENSIONS = { WIDTH: 0.7, HEIGHT: 0.95, DEPTH: 0.02 };
|
|
9
8
|
export const CARD_MAGNET = {
|
|
10
9
|
MAX_TILT_X: 0.36,
|
|
@@ -14,52 +13,18 @@ export const CARD_MAGNET = {
|
|
|
14
13
|
LERP_IN: 18,
|
|
15
14
|
LERP_OUT: 10,
|
|
16
15
|
};
|
|
17
|
-
const SHEET_KEY_MAP = {
|
|
18
|
-
Jokers: 'jokers',
|
|
19
|
-
Tarots: 'tarots',
|
|
20
|
-
Vouchers: 'vouchers',
|
|
21
|
-
Boosters: 'boosters',
|
|
22
|
-
Enhancers: 'enhancers',
|
|
23
|
-
Editions: 'editions',
|
|
24
|
-
BlindChips: 'blinds',
|
|
25
|
-
tags: 'tags',
|
|
26
|
-
Stakes: 'stakes',
|
|
27
|
-
Decks: 'deck',
|
|
28
|
-
};
|
|
29
|
-
const _textureCache = new Map();
|
|
30
16
|
function useSpriteTexture(sprite) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
t.repeat.set(1 / cols, 1 / rows);
|
|
43
|
-
t.offset.set(x / cols, (rows - y - 1) / rows);
|
|
44
|
-
t.needsUpdate = true;
|
|
45
|
-
return t;
|
|
46
|
-
};
|
|
47
|
-
if (_textureCache.has(url)) {
|
|
48
|
-
setTexture(applySlice(_textureCache.get(url)));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const loader = new THREE.TextureLoader();
|
|
52
|
-
loader.load(url, (loaded) => {
|
|
53
|
-
if (id !== serial.current)
|
|
54
|
-
return;
|
|
55
|
-
loaded.colorSpace = THREE.SRGBColorSpace;
|
|
56
|
-
loaded.magFilter = THREE.NearestFilter;
|
|
57
|
-
loaded.minFilter = THREE.NearestFilter;
|
|
58
|
-
_textureCache.set(url, loaded);
|
|
59
|
-
setTexture(applySlice(loaded));
|
|
60
|
-
}, undefined, (err) => console.error('[Card3D] texture load failed:', url, err));
|
|
61
|
-
}, [sprite.type, sprite.pos.x, sprite.pos.y]);
|
|
62
|
-
return texture;
|
|
17
|
+
const texture = useLoader(THREE.TextureLoader, sprite.atlasPath);
|
|
18
|
+
return useMemo(() => {
|
|
19
|
+
const t = texture.clone();
|
|
20
|
+
t.colorSpace = THREE.SRGBColorSpace;
|
|
21
|
+
t.magFilter = THREE.NearestFilter;
|
|
22
|
+
t.minFilter = THREE.NearestFilter;
|
|
23
|
+
t.repeat.set(1 / sprite.gridCols, 1 / sprite.gridRows);
|
|
24
|
+
t.offset.set(sprite.gridCol / sprite.gridCols, 1 - ((sprite.gridRow + 1) / sprite.gridRows));
|
|
25
|
+
t.needsUpdate = true;
|
|
26
|
+
return t;
|
|
27
|
+
}, [texture, sprite.gridCol, sprite.gridRow, sprite.gridCols, sprite.gridRows]);
|
|
63
28
|
}
|
|
64
29
|
export const Card3D = memo(function Card3D({ sprite, position = [0, 0, 0], rotation = [0, 0, 0], selected = false, highlighted = false, onClick, onPointerEnter, onPointerLeave, }) {
|
|
65
30
|
const tiltRef = useRef(null);
|
package/dist/r3f/JimboText3D.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Text } from '@react-three/drei';
|
|
3
3
|
import { resolveJamlAssetUrl } from '../assets.js';
|
|
4
|
-
|
|
4
|
+
import { JimboColorOption } from '../ui/tokens.js';
|
|
5
|
+
export function JimboText3D({ children, color = JimboColorOption.WHITE, outlineColor = JimboColorOption.BLACK, outlineWidth = 0.05, position = [0, 0, 0], fontSize = 1 }) {
|
|
5
6
|
// We use the m6x11plus font from assets if possible, or fallback
|
|
6
|
-
return (_jsx(Text, { position: position, fontSize: fontSize, color: color, outlineColor: outlineColor, outlineWidth: outlineWidth, font: resolveJamlAssetUrl('
|
|
7
|
+
return (_jsx(Text, { position: position, fontSize: fontSize, color: color, outlineColor: outlineColor, outlineWidth: outlineWidth, font: resolveJamlAssetUrl('font'), anchorX: "center", anchorY: "middle", children: children }));
|
|
7
8
|
}
|
|
@@ -1,175 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import { SPRITE_SHEETS } from "../sprites/spriteData.js";
|
|
5
|
-
function loadImage(url) {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
const image = new window.Image();
|
|
8
|
-
image.addEventListener("load", () => {
|
|
9
|
-
resolve(image);
|
|
10
|
-
});
|
|
11
|
-
image.addEventListener("error", () => {
|
|
12
|
-
resolve(null);
|
|
13
|
-
});
|
|
14
|
-
image.src = url;
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
function renderImage(canvas, context, image, layer, timestamp) {
|
|
18
|
-
if (!image || !layer || !layer?.pos)
|
|
19
|
-
return 0;
|
|
20
|
-
const cardWidth = image.width / layer.columns;
|
|
21
|
-
const cardHeight = image.height / layer.rows;
|
|
22
|
-
const canvasStyle = canvas.style;
|
|
23
|
-
if (layer.order === 0) {
|
|
24
|
-
canvas.width = cardWidth;
|
|
25
|
-
canvas.height = cardHeight;
|
|
26
|
-
canvasStyle.width = `${cardWidth}px`;
|
|
27
|
-
canvasStyle.height = `${cardHeight}px`;
|
|
28
|
-
}
|
|
29
|
-
canvasStyle.imageRendering = "pixelated";
|
|
30
|
-
context.imageSmoothingEnabled = true;
|
|
31
|
-
context.save();
|
|
32
|
-
if (layer.animated && timestamp) {
|
|
33
|
-
const elapsed = timestamp;
|
|
34
|
-
const yOffset = Math.sin(elapsed / 1000) * 3;
|
|
35
|
-
const xOffset = Math.sin(elapsed / 1500) * 1.5;
|
|
36
|
-
context.globalAlpha = 0.65 + (Math.sin(elapsed / 2000) + 1) * 0.075;
|
|
37
|
-
context.translate(xOffset, yOffset);
|
|
38
|
-
}
|
|
39
|
-
context.drawImage(image, layer.pos.x * cardWidth, layer.pos.y * cardHeight, cardWidth, cardHeight, 0, 0, canvas.width, canvas.height);
|
|
40
|
-
context.restore();
|
|
41
|
-
return cardWidth / cardHeight;
|
|
42
|
-
}
|
|
3
|
+
import { useJamlCardRenderer } from "../ui/hooks.js";
|
|
43
4
|
export function JamlCardRenderer({ layers, invert = false, className = "", hoverTilt = false }) {
|
|
44
|
-
const canvasRef =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
51
|
-
const [transform, setTransform] = useState("none");
|
|
52
|
-
const hasAnimatedLayer = layers?.some((layer) => layer.animated);
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
let cancelled = false;
|
|
55
|
-
const imageCache = imageCacheRef.current;
|
|
56
|
-
const preload = async () => {
|
|
57
|
-
const urls = Array.from(new Set(Object.values(SPRITE_SHEETS).map((sheet) => sheet.src)));
|
|
58
|
-
const images = await Promise.all(urls.map((url) => loadImage(url)));
|
|
59
|
-
if (cancelled)
|
|
60
|
-
return;
|
|
61
|
-
images.forEach((image, index) => {
|
|
62
|
-
if (image) {
|
|
63
|
-
imageCache.set(urls[index], image);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
forceUpdate((prev) => prev + 1);
|
|
67
|
-
};
|
|
68
|
-
preload().catch((err) => {
|
|
69
|
-
console.error("[JamlCardRenderer]", err);
|
|
70
|
-
});
|
|
71
|
-
return () => {
|
|
72
|
-
cancelled = true;
|
|
73
|
-
imageCache.clear();
|
|
74
|
-
};
|
|
75
|
-
}, []);
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (!hasAnimatedLayer)
|
|
78
|
-
return;
|
|
79
|
-
let startTime;
|
|
80
|
-
const animate = (timestamp) => {
|
|
81
|
-
if (!startTime)
|
|
82
|
-
startTime = timestamp;
|
|
83
|
-
const now = timestamp - startTime;
|
|
84
|
-
if (!animationFrameRef.current || timestamp - 100 > animationFrameRef.current) {
|
|
85
|
-
animationFrameRef.current = timestamp;
|
|
86
|
-
setElapsed(now);
|
|
87
|
-
}
|
|
88
|
-
animationFrameRef.current = requestAnimationFrame(animate);
|
|
89
|
-
};
|
|
90
|
-
animationFrameRef.current = requestAnimationFrame(animate);
|
|
91
|
-
return () => {
|
|
92
|
-
if (animationFrameRef.current) {
|
|
93
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}, [hasAnimatedLayer]);
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (!canvasRef.current || !layers || layers.length === 0)
|
|
99
|
-
return;
|
|
100
|
-
const canvas = canvasRef.current;
|
|
101
|
-
const context = canvas.getContext("2d");
|
|
102
|
-
if (!context)
|
|
103
|
-
return;
|
|
104
|
-
let cancelled = false;
|
|
105
|
-
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
106
|
-
[...layers]
|
|
107
|
-
.sort((a, b) => a.order - b.order)
|
|
108
|
-
.forEach((layer) => {
|
|
109
|
-
if (imageCacheRef.current.has(layer.source)) {
|
|
110
|
-
const image = imageCacheRef.current.get(layer.source);
|
|
111
|
-
if (!image)
|
|
112
|
-
return;
|
|
113
|
-
const imageRatio = renderImage(canvas, context, image, layer, hasAnimatedLayer ? elapsed : undefined);
|
|
114
|
-
if (layer.order === 0) {
|
|
115
|
-
setRatio(imageRatio);
|
|
116
|
-
}
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
loadImage(layer.source).then((img) => {
|
|
120
|
-
if (cancelled || !img)
|
|
121
|
-
return;
|
|
122
|
-
const imageRatio = renderImage(canvas, context, img, layer, hasAnimatedLayer ? elapsed : undefined);
|
|
123
|
-
imageCacheRef.current.set(layer.source, img);
|
|
124
|
-
if (layer.order === 0) {
|
|
125
|
-
setRatio(imageRatio);
|
|
126
|
-
}
|
|
127
|
-
forceUpdate((prev) => prev + 1);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
if (invert) {
|
|
131
|
-
canvas.style.filter = "invert(0.94)";
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
canvas.style.filter = "none";
|
|
135
|
-
}
|
|
136
|
-
return () => { cancelled = true; };
|
|
137
|
-
}, [layers, elapsed, invert, hasAnimatedLayer]);
|
|
138
|
-
const handlePointerEnter = (event) => {
|
|
139
|
-
if (!hoverTilt || event.pointerType === "touch")
|
|
140
|
-
return;
|
|
141
|
-
setIsHovered(true);
|
|
142
|
-
};
|
|
143
|
-
const handlePointerLeave = () => {
|
|
144
|
-
if (!hoverTilt)
|
|
145
|
-
return;
|
|
146
|
-
setIsHovered(false);
|
|
147
|
-
setTransform("none");
|
|
148
|
-
};
|
|
149
|
-
const handlePointerMove = (event) => {
|
|
150
|
-
if (!hoverTilt || event.pointerType === "touch")
|
|
151
|
-
return;
|
|
152
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
153
|
-
const x = event.clientX - rect.left;
|
|
154
|
-
const y = event.clientY - rect.top;
|
|
155
|
-
const rotateY = (x / rect.width) * 12 - 6;
|
|
156
|
-
const rotateX = (y / rect.height) * -16 + 8;
|
|
157
|
-
setTransform(`perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(10px)`);
|
|
158
|
-
};
|
|
159
|
-
const containerStyle = {
|
|
160
|
-
aspectRatio: String(ratio),
|
|
161
|
-
width: "100%",
|
|
162
|
-
display: "flex",
|
|
163
|
-
transition: hoverTilt && !isHovered ? "transform 0.4s ease" : undefined,
|
|
164
|
-
transform: hoverTilt ? (isHovered ? transform : "none") : undefined,
|
|
165
|
-
transformStyle: hoverTilt ? "preserve-3d" : undefined,
|
|
166
|
-
transformOrigin: hoverTilt ? "center center" : undefined,
|
|
167
|
-
};
|
|
168
|
-
const canvasStyle = {
|
|
169
|
-
borderRadius: "6px",
|
|
170
|
-
boxShadow: hoverTilt && isHovered ? "0 2px 12px rgba(0,0,0,0.3)" : "0 2px 8px rgba(0,0,0,0.2)",
|
|
171
|
-
imageRendering: "pixelated",
|
|
172
|
-
transition: hoverTilt && !isHovered ? "box-shadow 0.4s ease-out" : undefined,
|
|
173
|
-
};
|
|
174
|
-
return (_jsx("div", { className: className, style: containerStyle, onPointerEnter: hoverTilt ? handlePointerEnter : undefined, onPointerLeave: hoverTilt ? handlePointerLeave : undefined, onPointerMove: hoverTilt ? handlePointerMove : undefined, children: _jsx("canvas", { ref: canvasRef, style: canvasStyle }) }));
|
|
5
|
+
const { canvasRef, containerStyle, canvasStyle, handlers } = useJamlCardRenderer({
|
|
6
|
+
layers,
|
|
7
|
+
invert,
|
|
8
|
+
hoverTilt
|
|
9
|
+
});
|
|
10
|
+
return (_jsx("div", { className: className, style: containerStyle, ...handlers, children: _jsx("canvas", { ref: canvasRef, style: canvasStyle }) }));
|
|
175
11
|
}
|
|
@@ -13,5 +13,76 @@ export interface SpriteData {
|
|
|
13
13
|
pos: SpritePos;
|
|
14
14
|
type: SpriteSheetType;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Per-sheet mystery/back card positions for unknown items.
|
|
18
|
+
* Used as fallback when an item name can't be resolved.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MYSTERY_SPRITES: Partial<Record<SpriteSheetType, SpritePos>>;
|
|
21
|
+
/** Get the mystery/fallback sprite for a given sheet type. */
|
|
22
|
+
export declare function getMysterySprite(sheet: SpriteSheetType): SpriteData;
|
|
16
23
|
/** Look up sprite data by name. Accepts display names ("Icy Joker"), enum keys ("IcyJoker"), and "Joker | Name" prefixed forms. */
|
|
17
24
|
export declare function getSpriteData(name: string): SpriteData | null;
|
|
25
|
+
/**
|
|
26
|
+
* Wildcard "Any" sprites from the Enhancers sheet.
|
|
27
|
+
* Used in the JAML visual editor when a clause value is "Any" (e.g. `legendaryJoker: Any`).
|
|
28
|
+
*/
|
|
29
|
+
export declare const WILDCARD_SPRITES: {
|
|
30
|
+
/** Grey silhouette — Enhancers row 3 col 5. For generic "Any" items. */
|
|
31
|
+
readonly anySilhouette: {
|
|
32
|
+
readonly pos: {
|
|
33
|
+
readonly x: 5;
|
|
34
|
+
readonly y: 3;
|
|
35
|
+
};
|
|
36
|
+
readonly type: SpriteSheetType;
|
|
37
|
+
};
|
|
38
|
+
/** Grey ? circle — Enhancers row 3 col 6. For "boss: Any" etc. */
|
|
39
|
+
readonly anyMystery: {
|
|
40
|
+
readonly pos: {
|
|
41
|
+
readonly x: 6;
|
|
42
|
+
readonly y: 3;
|
|
43
|
+
};
|
|
44
|
+
readonly type: SpriteSheetType;
|
|
45
|
+
};
|
|
46
|
+
/** Red X debuff — Editions row 0 col 4. For mustNot zone indicator. */
|
|
47
|
+
readonly mustNotDebuff: {
|
|
48
|
+
readonly pos: {
|
|
49
|
+
readonly x: 4;
|
|
50
|
+
readonly y: 0;
|
|
51
|
+
};
|
|
52
|
+
readonly type: SpriteSheetType;
|
|
53
|
+
};
|
|
54
|
+
/** The Soul card — Tarots row 2 col 2. Base for "legendaryJoker: Any" (they spawn from Soul). */
|
|
55
|
+
readonly anyLegendary: {
|
|
56
|
+
readonly pos: {
|
|
57
|
+
readonly x: 2;
|
|
58
|
+
readonly y: 2;
|
|
59
|
+
};
|
|
60
|
+
readonly type: SpriteSheetType;
|
|
61
|
+
};
|
|
62
|
+
/** Blank spectral back — Tarots row 2 col 5. For "spectralCard: Any". */
|
|
63
|
+
readonly anySpectral: {
|
|
64
|
+
readonly pos: {
|
|
65
|
+
readonly x: 5;
|
|
66
|
+
readonly y: 2;
|
|
67
|
+
};
|
|
68
|
+
readonly type: SpriteSheetType;
|
|
69
|
+
};
|
|
70
|
+
/** Blank tarot back — Tarots row 2 col 6. For "tarotCard: Any". */
|
|
71
|
+
readonly anyTarot: {
|
|
72
|
+
readonly pos: {
|
|
73
|
+
readonly x: 6;
|
|
74
|
+
readonly y: 2;
|
|
75
|
+
};
|
|
76
|
+
readonly type: SpriteSheetType;
|
|
77
|
+
};
|
|
78
|
+
/** Blank planet back — Tarots row 2 col 7. For "planetCard: Any". */
|
|
79
|
+
readonly anyPlanet: {
|
|
80
|
+
readonly pos: {
|
|
81
|
+
readonly x: 7;
|
|
82
|
+
readonly y: 2;
|
|
83
|
+
};
|
|
84
|
+
readonly type: SpriteSheetType;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
/** Look up sprite data by name, falling back to the mystery card for the given sheet if not found. */
|
|
88
|
+
export declare function getSpriteDataOrMystery(name: string, fallbackSheet?: SpriteSheetType): SpriteData;
|
|
@@ -29,7 +29,47 @@ registerAll(VOUCHERS, "Vouchers");
|
|
|
29
29
|
registerAll(BOSSES, "BlindChips");
|
|
30
30
|
registerAll(TAGS, "tags");
|
|
31
31
|
registerAll(BOOSTER_PACKS ?? [], "Boosters");
|
|
32
|
+
/**
|
|
33
|
+
* Per-sheet mystery/back card positions for unknown items.
|
|
34
|
+
* Used as fallback when an item name can't be resolved.
|
|
35
|
+
*/
|
|
36
|
+
export const MYSTERY_SPRITES = {
|
|
37
|
+
Jokers: { x: 9, y: 9 }, // grey card back (legendary face row)
|
|
38
|
+
Tarots: { x: 4, y: 2 }, // blank consumable
|
|
39
|
+
Vouchers: { x: 8, y: 2 }, // mystery voucher
|
|
40
|
+
Boosters: { x: 0, y: 5 }, // grey empty pack
|
|
41
|
+
tags: { x: 3, y: 4 }, // grey ? tag
|
|
42
|
+
BlindChips: { x: 0, y: 30 }, // grey ? blind
|
|
43
|
+
};
|
|
44
|
+
/** Get the mystery/fallback sprite for a given sheet type. */
|
|
45
|
+
export function getMysterySprite(sheet) {
|
|
46
|
+
return { pos: MYSTERY_SPRITES[sheet] ?? { x: 0, y: 0 }, type: sheet };
|
|
47
|
+
}
|
|
32
48
|
/** Look up sprite data by name. Accepts display names ("Icy Joker"), enum keys ("IcyJoker"), and "Joker | Name" prefixed forms. */
|
|
33
49
|
export function getSpriteData(name) {
|
|
34
50
|
return ITEM_MAP.get(normalize(stripPrefix(name))) ?? ITEM_MAP.get(normalize(name)) ?? null;
|
|
35
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Wildcard "Any" sprites from the Enhancers sheet.
|
|
54
|
+
* Used in the JAML visual editor when a clause value is "Any" (e.g. `legendaryJoker: Any`).
|
|
55
|
+
*/
|
|
56
|
+
export const WILDCARD_SPRITES = {
|
|
57
|
+
/** Grey silhouette — Enhancers row 3 col 5. For generic "Any" items. */
|
|
58
|
+
anySilhouette: { pos: { x: 5, y: 3 }, type: "Enhancers" },
|
|
59
|
+
/** Grey ? circle — Enhancers row 3 col 6. For "boss: Any" etc. */
|
|
60
|
+
anyMystery: { pos: { x: 6, y: 3 }, type: "Enhancers" },
|
|
61
|
+
/** Red X debuff — Editions row 0 col 4. For mustNot zone indicator. */
|
|
62
|
+
mustNotDebuff: { pos: { x: 4, y: 0 }, type: "Editions" },
|
|
63
|
+
/** The Soul card — Tarots row 2 col 2. Base for "legendaryJoker: Any" (they spawn from Soul). */
|
|
64
|
+
anyLegendary: { pos: { x: 2, y: 2 }, type: "Tarots" },
|
|
65
|
+
/** Blank spectral back — Tarots row 2 col 5. For "spectralCard: Any". */
|
|
66
|
+
anySpectral: { pos: { x: 5, y: 2 }, type: "Tarots" },
|
|
67
|
+
/** Blank tarot back — Tarots row 2 col 6. For "tarotCard: Any". */
|
|
68
|
+
anyTarot: { pos: { x: 6, y: 2 }, type: "Tarots" },
|
|
69
|
+
/** Blank planet back — Tarots row 2 col 7. For "planetCard: Any". */
|
|
70
|
+
anyPlanet: { pos: { x: 7, y: 2 }, type: "Tarots" },
|
|
71
|
+
};
|
|
72
|
+
/** Look up sprite data by name, falling back to the mystery card for the given sheet if not found. */
|
|
73
|
+
export function getSpriteDataOrMystery(name, fallbackSheet = "Jokers") {
|
|
74
|
+
return getSpriteData(name) ?? getMysterySprite(fallbackSheet);
|
|
75
|
+
}
|
package/dist/ui/JimboBadge.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
export type JimboBadgeTone = 'dark' | 'blue' | 'red' | 'green' | 'gold' | 'grey' | 'orange' | 'purple';
|
|
2
3
|
export interface JimboBadgeProps {
|
|
3
4
|
size?: 'sm' | 'md';
|
|
4
|
-
tone?:
|
|
5
|
+
tone?: JimboBadgeTone;
|
|
5
6
|
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
6
8
|
}
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Small colored label pill. Matches Balatro's in-game tag/rarity badges.
|
|
11
|
+
* All styling via jimbo.css `.j-badge` classes.
|
|
12
|
+
*/
|
|
13
|
+
export declare function JimboBadge({ size, tone, className, children }: JimboBadgeProps): import("react/jsx-runtime").JSX.Element;
|