jaml-ui 0.14.3 → 0.16.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 (116) hide show
  1. package/DESIGN.md +197 -0
  2. package/dist/assets.d.ts +1 -0
  3. package/dist/assets.js +2 -0
  4. package/dist/decode/motelyItemDecoder.js +27 -3
  5. package/dist/decode/motelySprite.d.ts +15 -0
  6. package/dist/decode/motelySprite.js +27 -0
  7. package/dist/motely.d.ts +1 -0
  8. package/dist/motely.js +1 -0
  9. package/dist/r3f/Card3D.js +2 -0
  10. package/dist/r3f/JimboBillboard.d.ts +10 -0
  11. package/dist/r3f/JimboBillboard.js +29 -0
  12. package/dist/r3f/JimboText3D.d.ts +9 -0
  13. package/dist/r3f/JimboText3D.js +7 -0
  14. package/dist/r3f.d.ts +2 -0
  15. package/dist/r3f.js +2 -0
  16. package/dist/sprites/spriteData.d.ts +1 -0
  17. package/dist/sprites/spriteData.js +1 -0
  18. package/dist/sprites/spriteMapper.d.ts +7 -1
  19. package/dist/sprites/spriteMapper.js +12 -0
  20. package/dist/ui/JimboBadge.d.ts +7 -0
  21. package/dist/ui/JimboBadge.js +24 -0
  22. package/dist/ui/JimboFloating.d.ts +8 -0
  23. package/dist/ui/JimboFloating.js +17 -0
  24. package/dist/ui/JimboToggleList.d.ts +11 -0
  25. package/dist/ui/JimboToggleList.js +10 -0
  26. package/dist/ui/footer.js +1 -1
  27. package/dist/ui/jimboFlankNav.js +5 -5
  28. package/dist/ui/panel.d.ts +2 -1
  29. package/dist/ui/panel.js +4 -4
  30. package/dist/ui/sprites.d.ts +14 -0
  31. package/dist/ui/sprites.js +52 -12
  32. package/dist/ui.d.ts +3 -0
  33. package/dist/ui.js +3 -0
  34. package/package.json +129 -122
  35. package/assets/Balatro Seed Curator (DesignsV2)/.design-canvas.state.json +0 -1
  36. package/assets/Balatro Seed Curator (DesignsV2)/Assets/BlindChips.png +0 -0
  37. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/Boosters.json +0 -303
  38. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/boosters.png +0 -0
  39. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters.png +0 -0
  40. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/BlindChips.png +0 -0
  41. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/blinds_metadata.json +0 -51
  42. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/8BitDeck.png +0 -0
  43. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/Enhancers.png +0 -0
  44. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/balatro-stake-chips.png +0 -0
  45. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/enhancers_metadata.json +0 -52
  46. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/playing_cards_metadata.json +0 -249
  47. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/stakes.json +0 -19
  48. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Editions.png +0 -0
  49. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Enhancers.png +0 -0
  50. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Editions.png +0 -0
  51. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Jokers.png +0 -0
  52. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/jokers.json +0 -1087
  53. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers.png +0 -0
  54. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers_metadata.json +0 -25
  55. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers.png +0 -0
  56. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.json +0 -191
  57. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.png +0 -0
  58. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/Tarots.png +0 -0
  59. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/planets.json +0 -15
  60. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/spectrals.json +0 -21
  61. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/tarots.json +0 -163
  62. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots.png +0 -0
  63. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/Vouchers.png +0 -0
  64. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/vouchers.json +0 -130
  65. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers.png +0 -0
  66. package/assets/Balatro Seed Curator (DesignsV2)/Assets/blinds.json +0 -51
  67. package/assets/Balatro Seed Curator (DesignsV2)/Assets/boosters.json +0 -303
  68. package/assets/Balatro Seed Curator (DesignsV2)/Assets/fonts/m6x11plusplus.otf +0 -0
  69. package/assets/Balatro Seed Curator (DesignsV2)/Assets/jokers.json +0 -1087
  70. package/assets/Balatro Seed Curator (DesignsV2)/Assets/planets.json +0 -15
  71. package/assets/Balatro Seed Curator (DesignsV2)/Assets/spectrals.json +0 -21
  72. package/assets/Balatro Seed Curator (DesignsV2)/Assets/stakes.png +0 -0
  73. package/assets/Balatro Seed Curator (DesignsV2)/Assets/stickers.png +0 -0
  74. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.json +0 -191
  75. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.png +0 -0
  76. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tarots.json +0 -163
  77. package/assets/Balatro Seed Curator (DesignsV2)/Assets/vouchers.json +0 -130
  78. package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail v2.html +0 -40
  79. package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail.html +0 -34
  80. package/assets/Balatro Seed Curator (DesignsV2)/public/fonts/m6x11plusplus.otf +0 -0
  81. package/assets/Balatro Seed Curator (DesignsV2)/src/AntePage.jsx +0 -228
  82. package/assets/Balatro Seed Curator (DesignsV2)/src/SeedDetail.jsx +0 -222
  83. package/assets/Balatro Seed Curator (DesignsV2)/src/app.jsx +0 -35
  84. package/assets/Balatro Seed Curator (DesignsV2)/src/mockData.js +0 -185
  85. package/assets/Balatro Seed Curator (DesignsV2)/src/sprites.jsx +0 -259
  86. package/assets/Balatro Seed Curator (DesignsV2)/src/tokens.js +0 -49
  87. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/AntePageV2.jsx +0 -290
  88. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/BalButton.jsx +0 -107
  89. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlBuilderV2.jsx +0 -594
  90. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlIde.jsx +0 -302
  91. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SearchResultsV2.jsx +0 -286
  92. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedDetailV2.jsx +0 -336
  93. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedOGCard.jsx +0 -251
  94. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/Showcase.jsx +0 -131
  95. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/app.jsx +0 -55
  96. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/data.js +0 -296
  97. package/assets/Balatro Seed Curator (DesignsV2)/starters/design-canvas.jsx +0 -622
  98. package/assets/Balatro Seed Curator (DesignsV2)/uploads/8BitDeck.png +0 -0
  99. package/assets/Balatro Seed Curator (DesignsV2)/uploads/BlindChips.png +0 -0
  100. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Boosters.png +0 -0
  101. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Editions.png +0 -0
  102. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Enhancers.png +0 -0
  103. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Jokers.png +0 -0
  104. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Tarots.png +0 -0
  105. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749540653-0.png +0 -0
  106. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749644934-0.png +0 -0
  107. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749661871-0.png +0 -0
  108. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749674748-0.png +0 -0
  109. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749703076-0.png +0 -0
  110. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749882759-0.png +0 -0
  111. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750354200-0.png +0 -0
  112. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750733265-0.png +0 -0
  113. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776751928925-0.png +0 -0
  114. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776800975060-0.png +0 -0
  115. package/assets/Balatro Seed Curator (DesignsV2)/uploads/stickers.png +0 -0
  116. package/assets/Balatro Seed Curator (DesignsV2)/uploads/tags.png +0 -0
package/DESIGN.md ADDED
@@ -0,0 +1,197 @@
1
+ ---
2
+ name: Jimbo
3
+ description: Balatro-inspired design system for JAML seed finder tools. Eyedropped from actual game shader output — not Lua hex values.
4
+ colors:
5
+ red: "#ff4c40"
6
+ blue: "#0093ff"
7
+ green: "#429f79"
8
+ orange: "#ff9800"
9
+ gold: "#e4b643"
10
+ purple: "#9e74ce"
11
+ dark-red: "#a02721"
12
+ dark-blue: "#0057a1"
13
+ dark-orange: "#a05b00"
14
+ dark-green: "#215f46"
15
+ dark-purple: "#5e437e"
16
+ dark-grey: "#3a5055"
17
+ darkest: "#1e2b2d"
18
+ grey: "#708386"
19
+ teal-grey: "#404c4e"
20
+ panel-edge: "#1e2e32"
21
+ inner-border: "#334461"
22
+ border-silver: "#b9c2d2"
23
+ border-south: "#777e89"
24
+ gold-text: "#e4b643"
25
+ green-text: "#35bd86"
26
+ orange-text: "#ff8f00"
27
+ white: "#ffffff"
28
+ black: "#000000"
29
+ tarot-button: "#9e74ce"
30
+ planet-button: "#00a7ca"
31
+ spectral-button: "#2e76fd"
32
+ typography:
33
+ display:
34
+ fontFamily: m6x11plus, monospace
35
+ fontSize: 26px
36
+ fontWeight: 400
37
+ lineHeight: 1
38
+ letterSpacing: 0.04em
39
+ heading:
40
+ fontFamily: m6x11plus, monospace
41
+ fontSize: 14px
42
+ fontWeight: 400
43
+ lineHeight: 1.2
44
+ letterSpacing: 0.08em
45
+ body:
46
+ fontFamily: m6x11plus, monospace
47
+ fontSize: 11px
48
+ fontWeight: 400
49
+ lineHeight: 1.3
50
+ letterSpacing: 0.05em
51
+ label:
52
+ fontFamily: m6x11plus, monospace
53
+ fontSize: 9px
54
+ fontWeight: 400
55
+ lineHeight: 1
56
+ letterSpacing: 0.1em
57
+ micro:
58
+ fontFamily: m6x11plus, monospace
59
+ fontSize: 8px
60
+ fontWeight: 400
61
+ lineHeight: 1
62
+ letterSpacing: 0.08em
63
+ rounded:
64
+ sm: 4px
65
+ md: 6px
66
+ lg: 8px
67
+ pill: 10px
68
+ spacing:
69
+ xs: 2px
70
+ sm: 4px
71
+ md: 8px
72
+ lg: 12px
73
+ xl: 16px
74
+ components:
75
+ panel:
76
+ backgroundColor: "{colors.dark-grey}"
77
+ rounded: "{rounded.md}"
78
+ panel-edge:
79
+ backgroundColor: "{colors.panel-edge}"
80
+ panel-darkest:
81
+ backgroundColor: "{colors.darkest}"
82
+ rounded: "{rounded.md}"
83
+ button-primary:
84
+ backgroundColor: "{colors.red}"
85
+ textColor: "{colors.white}"
86
+ rounded: "{rounded.md}"
87
+ padding: 8px
88
+ button-primary-hover:
89
+ backgroundColor: "{colors.dark-red}"
90
+ button-secondary:
91
+ backgroundColor: "{colors.blue}"
92
+ textColor: "{colors.white}"
93
+ rounded: "{rounded.md}"
94
+ padding: 8px
95
+ button-secondary-hover:
96
+ backgroundColor: "{colors.dark-blue}"
97
+ button-back:
98
+ backgroundColor: "{colors.orange}"
99
+ textColor: "{colors.white}"
100
+ rounded: "{rounded.md}"
101
+ padding: 8px
102
+ button-back-hover:
103
+ backgroundColor: "{colors.dark-orange}"
104
+ tab-active:
105
+ backgroundColor: "{colors.gold}"
106
+ textColor: "{colors.black}"
107
+ rounded: "{rounded.sm}"
108
+ tab-inactive:
109
+ backgroundColor: "transparent"
110
+ textColor: "{colors.grey}"
111
+ score-must:
112
+ backgroundColor: "{colors.blue}"
113
+ textColor: "{colors.white}"
114
+ score-should:
115
+ backgroundColor: "{colors.red}"
116
+ textColor: "{colors.white}"
117
+ glow-must:
118
+ backgroundColor: "{colors.gold}"
119
+ glow-should:
120
+ backgroundColor: "{colors.green-text}"
121
+ ---
122
+
123
+ ## Overview
124
+
125
+ Jimbo is the design system for Balatro seed finder tools (JAML-UI, WeeJoker, Seed Finder). It recreates the cozy, tactile, chunky feel of LocalThunk's Balatro — dark panels with silver borders, 3D-press buttons, pixel typography, juice animations. Everything feels like a physical object you can poke.
126
+
127
+ The system is built **Mobile First**. The absolute minimum viewport width is **320px**. All components must be accessible and usable at 320px without breaking layouts or horizontal scrolling. No fat padding, no bloated margins — every pixel earns its place.
128
+
129
+ ## Colors
130
+
131
+ All colors are eyedropped from Balatro's actual rendered shader output. Do NOT substitute with Lua source hex values — the game's shader pipeline transforms them.
132
+
133
+ - **Red (#ff4c40):** Primary action, mult scoring, should-clause hits. The "play" color.
134
+ - **Blue (#0093ff):** Secondary action, chips scoring, must-clause gates. The "requirement" color.
135
+ - **Green (#429f79):** Success, positive state, money.
136
+ - **Orange (#ff9800):** Back/return actions, warning.
137
+ - **Gold (#e4b643):** Seed text, premium highlights, active tab. The "treasure" color.
138
+ - **Purple (#9e74ce):** Joker rarity, tarot cards.
139
+ - **Dark Grey (#3a5055):** Panel backgrounds — the primary surface.
140
+ - **Darkest (#1e2b2d):** Deepest background, inset areas.
141
+ - **Grey (#708386):** Disabled text, labels, inactive elements.
142
+ - **Border Silver (#b9c2d2):** Panel top/side borders — the "silver frame."
143
+ - **Border South (#777e89):** Panel bottom border — creates the 3D depth illusion.
144
+ - **Panel Edge (#1e2e32):** Thin outer edge on panels.
145
+
146
+ Must-clause items glow blue. Should-clause items glow gold/green. Non-matching items render at 40-60% opacity with slight grayscale.
147
+
148
+ ## Typography
149
+
150
+ m6x11plus (m6x11plusplus.otf) is the ONLY font. It is a single-weight pixel font. NEVER apply font-weight bold, semibold, or any weight other than 400. Bold makes it look muddy. Use size and letter-spacing for hierarchy instead.
151
+
152
+ All text is uppercase with generous letter-spacing (0.04em-0.1em) for labels and micro text. Seed codes use the display size (26px) in gold (#e4b643) with 0.04em tracking.
153
+
154
+ ## Layout
155
+
156
+ Target: Minimum 320px portrait width. Components must scale gracefully using relative units and flexible layouts. Avoid fixed widths that break at 320px. Vertical snap-scroll for ante pages. Horizontal swipe for seed navigation.
157
+
158
+ Panels use 2px solid borders with border-silver on top/sides and border-south on bottom, creating a subtle 3D card effect. Inner shadow: `inset 0 0 0 1px rgba(255,255,255,0.04)`. Outer shadow: `0 2px 0 #000`.
159
+
160
+ ## Elevation & Depth
161
+
162
+ Buttons have a colored "underside" via box-shadow (not blur). On press, translateY increases by 2-3px and the shadow collapses — the button physically sinks. On hover, translateY decreases by 2px (lifts) with a tiny brightness bump.
163
+
164
+ Panels sit on a dark south-shadow (`0 3px 0 rgba(0,0,0,0.55)`). Translucent panels (for swirl-background contexts) use `rgba(15, 24, 26, 0.78)` with `backdrop-filter: blur(2px)`.
165
+
166
+ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]` with a 1.6s pulse animation. Must = blue glow, should = gold/green glow.
167
+
168
+ ## Components
169
+
170
+ **Button:** Chunky 3D press. Colored underside via box-shadow. Hover lifts -2px + brightness. Press sinks +2-3px + shadow collapse. Variants: primary (red), secondary (blue), back (orange), ghost (transparent). Sizes via padding, not font-size. Easing: `cubic-bezier(0.34, 1.56, 0.64, 1)`.
171
+
172
+ **Panel:** Dark grey (#3a5055) background, 2px solid border (silver top/sides, south bottom), border-radius 6px. Inner highlight: `inset 0 0 0 1px rgba(255,255,255,0.04)`. Drop: `0 2px 0 #000`.
173
+
174
+ **Tabs (JimboTabs):** Active tab = gold background + black text. Inactive = transparent + grey text. No bold. Small radius (4px). Tabs are the ONLY navigation — no hamburger menus, no sidebars on mobile.
175
+
176
+ **ScoreCol:** Must-clauses show as blue-framed boxes with checkmark/cross badge. Should-clauses show as bare sprites with a red corner count badge (x2, x3...). Unlit = 40% opacity + grayscale.
177
+
178
+ **ShopTape:** Horizontal grab-scrollable strip of item sprites. Edge fades (linear-gradient masks) on left/right. Hidden scrollbar. Cursor: grab/grabbing.
179
+
180
+ **PackCell:** Tap to fan — cards explode out with springy stagger (40ms delay, juice easing). Tap again to collapse. Closed state = single pack sprite with type label.
181
+
182
+ **GlowRing:** Pulsing outline around JAML-hit items. `0 0 0 2px [color], 0 0 10px [color]`. Animation: opacity 0.55 → 1.0 over 1.6s ease-in-out infinite.
183
+
184
+ **SeedPagerHeader:** Three columns: [left stride arrow] [identity panel with seed + copy + deck/stake] [right stride arrow]. Stride arrows are tall red bars with dark-red inset shadow. Identity panel is translucent with counter pip.
185
+
186
+ ## Do's and Don'ts
187
+
188
+ - DO use m6x11plus for everything. No fallback display fonts.
189
+ - DO eyedrop colors from the game. Never guess or approximate.
190
+ - DO design for 320px portrait first. Desktop is an expanded view of the mobile baseline.
191
+ - DO use translateY + box-shadow for button depth. Not CSS 3D transforms.
192
+ - DO dim non-matching items (opacity 0.4 + grayscale 0.6). They stay visible for context.
193
+ - DON'T use font-weight bold. m6x11plus is single-weight. Bold = muddy.
194
+ - DON'T use fat padding or margins. Balatro UI is dense and cozy.
195
+ - DON'T add horizontal scroll. Vertical snap-scroll + horizontal swipe only.
196
+ - DON'T use rounded corners larger than 10px. Balatro is chunky, not bubbly.
197
+ - DON'T use blur-based shadows for depth. Use solid colored box-shadows.
package/dist/assets.d.ts CHANGED
@@ -9,6 +9,7 @@ export declare const JAML_ASSET_FILES: {
9
9
  readonly vouchers: "Vouchers.png";
10
10
  readonly stickers: "stickers.png";
11
11
  readonly tags: "tags.png";
12
+ readonly stakes: "balatro-stake-chips.png";
12
13
  };
13
14
  export type JamlAssetKey = keyof typeof JAML_ASSET_FILES;
14
15
  export type JamlAssetFile = (typeof JAML_ASSET_FILES)[JamlAssetKey];
package/dist/assets.js CHANGED
@@ -9,6 +9,7 @@ export const JAML_ASSET_FILES = {
9
9
  vouchers: "Vouchers.png",
10
10
  stickers: "stickers.png",
11
11
  tags: "tags.png",
12
+ stakes: "balatro-stake-chips.png",
12
13
  };
13
14
  const assetKeyByFileName = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [fileName, key]));
14
15
  const defaultAssetUrls = {
@@ -22,6 +23,7 @@ const defaultAssetUrls = {
22
23
  vouchers: new URL(`../assets/${JAML_ASSET_FILES.vouchers}`, import.meta.url).href,
23
24
  stickers: new URL(`../assets/${JAML_ASSET_FILES.stickers}`, import.meta.url).href,
24
25
  tags: new URL(`../assets/${JAML_ASSET_FILES.tags}`, import.meta.url).href,
26
+ stakes: new URL(`../assets/${JAML_ASSET_FILES.stakes}`, import.meta.url).href,
25
27
  };
26
28
  let customAssetBaseUrl = null;
27
29
  function normalizeBaseUrl(baseUrl) {
@@ -23,15 +23,39 @@ const CATEGORY_TO_TYPE = {
23
23
  };
24
24
  // ─── Reverse lookup: MotelyItemType integer → string name ────────────────────
25
25
  const _itemTypeToName = new Map();
26
+ const MOTELY_ITEM_TYPES_STANDARD = [
27
+ "TwoOfClubs", "ThreeOfClubs", "FourOfClubs", "FiveOfClubs", "SixOfClubs", "SevenOfClubs", "EightOfClubs", "NineOfClubs", "TenOfClubs", "JackOfClubs", "QueenOfClubs", "KingOfClubs", "AceOfClubs",
28
+ "TwoOfDiamonds", "ThreeOfDiamonds", "FourOfDiamonds", "FiveOfDiamonds", "SixOfDiamonds", "SevenOfDiamonds", "EightOfDiamonds", "NineOfDiamonds", "TenOfDiamonds", "JackOfDiamonds", "QueenOfDiamonds", "KingOfDiamonds", "AceOfDiamonds",
29
+ "TwoOfHearts", "ThreeOfHearts", "FourOfHearts", "FiveOfHearts", "SixOfHearts", "SevenOfHearts", "EightOfHearts", "NineOfHearts", "TenOfHearts", "JackOfHearts", "QueenOfHearts", "KingOfHearts", "AceOfHearts",
30
+ "TwoOfSpades", "ThreeOfSpades", "FourOfSpades", "FiveOfSpades", "SixOfSpades", "SevenOfSpades", "EightOfSpades", "NineOfSpades", "TenOfSpades", "JackOfSpades", "QueenOfSpades", "KingOfSpades", "AceOfSpades"
31
+ ];
32
+ const MOTELY_ITEM_TYPES_SPECTRAL = ["Familiar", "Grim", "Incantation", "Talisman", "Aura", "Wraith", "Sigil", "Ouija", "Ectoplasm", "Immolate", "Ankh", "DejaVu", "Hex", "Trance", "Medium", "Cryptid", "TheSoul", "BlackHole"];
33
+ const MOTELY_ITEM_TYPES_TAROT = ["TheFool", "TheMagician", "TheHighPriestess", "TheEmpress", "TheEmperor", "TheHierophant", "TheLovers", "TheChariot", "Justice", "TheHermit", "TheWheelOfFortune", "Strength", "TheHangedMan", "Death", "Temperance", "TheDevil", "TheTower", "TheStar", "TheMoon", "TheSun", "Judgement", "TheWorld"];
34
+ const MOTELY_ITEM_TYPES_PLANET = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "PlanetX", "Ceres", "Eris"];
35
+ const MOTELY_ITEM_TYPES_JOKER = ["Joker", "GreedyJoker", "LustyJoker", "WrathfulJoker", "GluttonousJoker", "JollyJoker", "ZanyJoker", "MadJoker", "CrazyJoker", "DrollJoker", "SlyJoker", "WilyJoker", "CleverJoker", "DeviousJoker", "CraftyJoker", "HalfJoker", "CreditCard", "Banner", "MysticSummit", "EightBall", "Misprint", "RaisedFist", "ChaostheClown", "ScaryFace", "AbstractJoker", "DelayedGratification", "GrosMichel", "EvenSteven", "OddTodd", "Scholar", "BusinessCard", "Supernova", "RideTheBus", "Egg", "Runner", "IceCream", "Splash", "BlueJoker", "FacelessJoker", "GreenJoker", "Superposition", "ToDoList", "Cavendish", "RedCard", "SquareJoker", "RiffRaff", "Photograph", "ReservedParking", "MailInRebate", "Hallucination", "FortuneTeller", "Juggler", "Drunkard", "GoldenJoker", "Popcorn", "WalkieTalkie", "SmileyFace", "GoldenTicket", "Swashbuckler", "HangingChad", "ShootTheMoon", "JokerStencil", "FourFingers", "Mime", "CeremonialDagger", "MarbleJoker", "LoyaltyCard", "Dusk", "Fibonacci", "SteelJoker", "Hack", "Pareidolia", "SpaceJoker", "Burglar", "Blackboard", "SixthSense", "Constellation", "Hiker", "CardSharp", "Madness", "Seance", "Vampire", "Shortcut", "Hologram", "Cloud9", "Rocket", "MidasMask", "Luchador", "GiftCard", "TurtleBean", "Erosion", "ToTheMoon", "StoneJoker", "LuckyCat", "Bull", "DietCola", "TradingCard", "FlashCard", "SpareTrousers", "Ramen", "Seltzer", "Castle", "MrBones", "Acrobat", "SockAndBuskin", "Troubadour", "Certificate", "SmearedJoker", "Throwback", "RoughGem", "Bloodstone", "Arrowhead", "OnyxAgate", "GlassJoker", "Showman", "FlowerPot", "MerryAndy", "OopsAll6s", "TheIdol", "SeeingDouble", "Matador", "Satellite", "Cartomancer", "Astronomer", "Bootstraps", "DNA", "Vagabond", "Baron", "Obelisk", "BaseballCard", "AncientJoker", "Campfire", "Blueprint", "WeeJoker", "HitTheRoad", "TheDuo", "TheTrio", "TheFamily", "TheOrder", "TheTribe", "Stuntman", "InvisibleJoker", "Brainstorm", "DriversLicense", "BurntJoker", "Canio", "Triboulet", "Yorick", "Chicot", "Perkeo"];
36
+ const MOTELY_ITEM_TYPES_INVALID = ["Invalid", "NotImplemented", "JokerExcludedByStream", "PlanetExcludedByStream", "TarotExcludedByStream", "SpectralExcludedByStream"];
26
37
  function ensureItemTypeMap() {
27
38
  if (_itemTypeToName.size > 0)
28
39
  return;
40
+ // Fallback to runtime enum if present (motely-wasm < 14)
29
41
  const e = Motely.MotelyItemType;
30
- for (const [key, val] of Object.entries(e)) {
31
- if (typeof val === "number" && typeof key === "string" && !/^\d+$/.test(key)) {
32
- _itemTypeToName.set(val, key);
42
+ if (e && Object.keys(e).length > 0) {
43
+ for (const [key, val] of Object.entries(e)) {
44
+ if (typeof val === "number" && typeof key === "string" && !/^\d+$/.test(key)) {
45
+ _itemTypeToName.set(val, key);
46
+ }
33
47
  }
34
48
  }
49
+ else {
50
+ // Populate using hardcoded categories (motely-wasm 14+)
51
+ _itemTypeToName.set(0, "None"); // Handle 0
52
+ MOTELY_ITEM_TYPES_STANDARD.forEach((name, i) => _itemTypeToName.set(0x1000 + i, name));
53
+ MOTELY_ITEM_TYPES_SPECTRAL.forEach((name, i) => _itemTypeToName.set(0x2000 + i, name));
54
+ MOTELY_ITEM_TYPES_TAROT.forEach((name, i) => _itemTypeToName.set(0x3000 + i, name));
55
+ MOTELY_ITEM_TYPES_PLANET.forEach((name, i) => _itemTypeToName.set(0x4000 + i, name));
56
+ MOTELY_ITEM_TYPES_JOKER.forEach((name, i) => _itemTypeToName.set(0x5000 + i, name));
57
+ MOTELY_ITEM_TYPES_INVALID.forEach((name, i) => _itemTypeToName.set(0xf000 + i, name));
58
+ }
35
59
  }
36
60
  function asRuntimeItem(input) {
37
61
  return input !== null && typeof input === "object" ? input : null;
@@ -0,0 +1,15 @@
1
+ import { type MotelyRenderableCategory } from "./motelyItemDecoder.js";
2
+ export interface MotelySpriteData {
3
+ atlasPath: string;
4
+ gridCol: number;
5
+ gridRow: number;
6
+ gridCols: number;
7
+ gridRows: number;
8
+ displayName: string;
9
+ category: MotelyRenderableCategory;
10
+ }
11
+ /**
12
+ * Given a raw motely-wasm item value (which may be a bitpacked integer or raw MotelyItemType),
13
+ * resolves it to a sprite atlas path and grid coordinates for rendering.
14
+ */
15
+ export declare function motelyItemToSprite(rawValue: number): MotelySpriteData | null;
@@ -0,0 +1,27 @@
1
+ import { decodeMotelyItem } from "./motelyItemDecoder.js";
2
+ import { getSpriteData, SHEET_META } from "../sprites/spriteMapper.js";
3
+ import { resolveJamlAssetUrl } from "../assets.js";
4
+ /**
5
+ * Given a raw motely-wasm item value (which may be a bitpacked integer or raw MotelyItemType),
6
+ * resolves it to a sprite atlas path and grid coordinates for rendering.
7
+ */
8
+ export function motelyItemToSprite(rawValue) {
9
+ const decoded = decodeMotelyItem(rawValue);
10
+ if (!decoded)
11
+ return null;
12
+ const sprite = getSpriteData(decoded.displayName);
13
+ if (!sprite)
14
+ return null;
15
+ const meta = SHEET_META[sprite.type];
16
+ if (!meta)
17
+ return null;
18
+ return {
19
+ atlasPath: resolveJamlAssetUrl(meta.assetKey),
20
+ gridCol: sprite.pos.x,
21
+ gridRow: sprite.pos.y,
22
+ gridCols: meta.cols,
23
+ gridRows: meta.rows,
24
+ displayName: decoded.displayName,
25
+ category: decoded.category
26
+ };
27
+ }
package/dist/motely.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { decodeMotelyItem, decodeMotelyItemToJamlCard, motelyItemTypeName, motelyItemCategory, motelyItemDisplayName, motelyItemRenderCategory, motelyItemEditionName, motelyItemSealName, motelyItemEnhancementName, motelyStandardcardRankName, motelyStandardcardSuitName, decodeMotelyItemName, resolveMotelyItemType, warmMotelyItemCache, motelyItemCacheSize, type DecodedMotelyItem, type MotelyItemInput, type MotelyJamlCard, type MotelyRenderableCategory, type MotelyRuntimeItem, } from "./decode/motelyItemDecoder.js";
2
+ export { motelyItemToSprite, type MotelySpriteData, } from "./decode/motelySprite.js";
2
3
  export { MOTELY_DISPLAY_SCHEMA, motelyBossDisplayName, motelyBossDisplayNameFromKey, motelyBossKeyFromDisplayName, motelyBoosterPackDisplayName, motelyBoosterPackDisplayNameFromKey, motelyBoosterPackKeyFromDisplayName, motelyItemDisplayNameFromKey, motelyItemDisplayNameFromValue, motelyTagDisplayName, motelyTagDisplayNameFromKey, motelyTagKeyFromDisplayName, motelyVoucherDisplayName, motelyVoucherDisplayNameFromKey, motelyVoucherKeyFromDisplayName, type MotelyBoosterPackKey, type MotelyBossKey, type MotelyDisplaySchema, type MotelyTagKey, type MotelyVoucherKey, } from "./motelyDisplay.js";
package/dist/motely.js CHANGED
@@ -1,3 +1,4 @@
1
1
  "use client";
2
2
  export { decodeMotelyItem, decodeMotelyItemToJamlCard, motelyItemTypeName, motelyItemCategory, motelyItemDisplayName, motelyItemRenderCategory, motelyItemEditionName, motelyItemSealName, motelyItemEnhancementName, motelyStandardcardRankName, motelyStandardcardSuitName, decodeMotelyItemName, resolveMotelyItemType, warmMotelyItemCache, motelyItemCacheSize, } from "./decode/motelyItemDecoder.js";
3
+ export { motelyItemToSprite, } from "./decode/motelySprite.js";
3
4
  export { MOTELY_DISPLAY_SCHEMA, motelyBossDisplayName, motelyBossDisplayNameFromKey, motelyBossKeyFromDisplayName, motelyBoosterPackDisplayName, motelyBoosterPackDisplayNameFromKey, motelyBoosterPackKeyFromDisplayName, motelyItemDisplayNameFromKey, motelyItemDisplayNameFromValue, motelyTagDisplayName, motelyTagDisplayNameFromKey, motelyTagKeyFromDisplayName, motelyVoucherDisplayName, motelyVoucherDisplayNameFromKey, motelyVoucherKeyFromDisplayName, } from "./motelyDisplay.js";
@@ -23,6 +23,8 @@ const SHEET_KEY_MAP = {
23
23
  Editions: 'editions',
24
24
  BlindChips: 'blinds',
25
25
  tags: 'tags',
26
+ Stakes: 'stakes',
27
+ Decks: 'deck',
26
28
  };
27
29
  const _textureCache = new Map();
28
30
  function useSpriteTexture(sprite) {
@@ -0,0 +1,10 @@
1
+ import type { MotelySpriteData } from '../decode/motelySprite.js';
2
+ export interface JimboBillboardProps {
3
+ sprite: MotelySpriteData | null;
4
+ label?: string;
5
+ width?: number;
6
+ height?: number;
7
+ yLockOnly?: boolean;
8
+ position?: [number, number, number];
9
+ }
10
+ export declare function JimboBillboard({ sprite, label, width, height, yLockOnly, position }: JimboBillboardProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { Billboard } from '@react-three/drei';
4
+ import { useLoader } from '@react-three/fiber';
5
+ import * as THREE from 'three';
6
+ export function JimboBillboard({ sprite, label, width = 3.4, height = 4.5, yLockOnly = false, position = [0, 0, 0] }) {
7
+ if (!sprite)
8
+ return null;
9
+ // Memoize texture to avoid per-render allocation
10
+ const texture = useLoader(THREE.TextureLoader, sprite.atlasPath);
11
+ const clonedTexture = useMemo(() => {
12
+ const tex = texture.clone();
13
+ tex.magFilter = THREE.NearestFilter;
14
+ tex.minFilter = THREE.NearestFilter;
15
+ // Set up sprite cropping
16
+ tex.repeat.set(1 / sprite.gridCols, 1 / sprite.gridRows);
17
+ tex.offset.set(sprite.gridCol / sprite.gridCols, 1 - ((sprite.gridRow + 1) / sprite.gridRows));
18
+ tex.needsUpdate = true;
19
+ return tex;
20
+ }, [texture, sprite.gridCol, sprite.gridRow, sprite.gridCols, sprite.gridRows]);
21
+ const material = useMemo(() => {
22
+ return new THREE.MeshBasicMaterial({
23
+ map: clonedTexture,
24
+ transparent: true,
25
+ alphaTest: 0.5
26
+ });
27
+ }, [clonedTexture]);
28
+ return (_jsx(Billboard, { lockY: yLockOnly, lockX: false, lockZ: false, position: position, children: _jsx("mesh", { material: material, children: _jsx("planeGeometry", { args: [width, height] }) }) }));
29
+ }
@@ -0,0 +1,9 @@
1
+ export interface JimboText3DProps {
2
+ children: string;
3
+ color?: string;
4
+ outlineColor?: string;
5
+ outlineWidth?: number;
6
+ position?: [number, number, number];
7
+ fontSize?: number;
8
+ }
9
+ export declare function JimboText3D({ children, color, outlineColor, outlineWidth, position, fontSize }: JimboText3DProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from '@react-three/drei';
3
+ import { resolveJamlAssetUrl } from '../assets.js';
4
+ export function JimboText3D({ children, color = '#ffffff', outlineColor = '#000000', outlineWidth = 0.05, position = [0, 0, 0], fontSize = 1 }) {
5
+ // 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('fonts') /* TODO: ensure m6x11plus ttf is accessible */, anchorX: "center", anchorY: "middle", children: children }));
7
+ }
package/dist/r3f.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from './r3f/Card3D.js';
2
+ export * from './r3f/JimboBillboard.js';
3
+ export * from './r3f/JimboText3D.js';
package/dist/r3f.js CHANGED
@@ -1 +1,3 @@
1
1
  export * from './r3f/Card3D.js';
2
+ export * from './r3f/JimboBillboard.js';
3
+ export * from './r3f/JimboText3D.js';
@@ -32,6 +32,7 @@ export declare const SPRITE_SHEETS: {
32
32
  readonly vouchers: SpriteSheetInfo;
33
33
  readonly tags: SpriteSheetInfo;
34
34
  readonly boosters: SpriteSheetInfo;
35
+ readonly stakes: SpriteSheetInfo;
35
36
  };
36
37
  export declare const JOKERS: SpriteEntry[];
37
38
  export declare const JOKER_FACES: SpriteEntry[];
@@ -27,6 +27,7 @@ export const SPRITE_SHEETS = {
27
27
  vouchers: defineSpriteSheet("vouchers", 9, 4),
28
28
  tags: defineSpriteSheet("tags", 6, 5),
29
29
  boosters: defineSpriteSheet("boosters", 4, 9),
30
+ stakes: defineSpriteSheet("stakes", 5, 2),
30
31
  };
31
32
  export const JOKERS = [
32
33
  { name: "Joker", pos: { x: 0, y: 0 } }, { name: "Greedy Joker", pos: { x: 6, y: 1 } }, { name: "Lusty Joker", pos: { x: 7, y: 1 } }, { name: "Wrathful Joker", pos: { x: 8, y: 1 } }, { name: "Gluttonous Joker", pos: { x: 9, y: 1 } }, { name: "Jolly Joker", pos: { x: 2, y: 0 } }, { name: "Zany Joker", pos: { x: 3, y: 0 } }, { name: "Mad Joker", pos: { x: 4, y: 0 } }, { name: "Crazy Joker", pos: { x: 5, y: 0 } }, { name: "Droll Joker", pos: { x: 6, y: 0 } }, { name: "Sly Joker", pos: { x: 0, y: 14 } }, { name: "Wily Joker", pos: { x: 1, y: 14 } }, { name: "Clever Joker", pos: { x: 2, y: 14 } }, { name: "Devious Joker", pos: { x: 3, y: 14 } }, { name: "Crafty Joker", pos: { x: 4, y: 14 } }, { name: "Half Joker", pos: { x: 7, y: 0 } }, { name: "Joker Stencil", pos: { x: 2, y: 5 } }, { name: "Four Fingers", pos: { x: 6, y: 6 } }, { name: "Mime", pos: { x: 4, y: 1 } }, { name: "Credit Card", pos: { x: 5, y: 1 } }, { name: "Ceremonial Dagger", pos: { x: 5, y: 5 } }, { name: "Banner", pos: { x: 1, y: 2 } }, { name: "Mystic Summit", pos: { x: 2, y: 2 } }, { name: "Marble Joker", pos: { x: 3, y: 2 } }, { name: "Loyalty Card", pos: { x: 4, y: 2 } }, { name: "8 Ball", pos: { x: 0, y: 5 } }, { name: "Misprint", pos: { x: 6, y: 2 } }, { name: "Dusk", pos: { x: 4, y: 7 } }, { name: "Raised Fist", pos: { x: 8, y: 2 } }, { name: "Chaos the Clown", pos: { x: 1, y: 0 } }, { name: "Fibonacci", pos: { x: 1, y: 5 } }, { name: "Steel Joker", pos: { x: 7, y: 2 } }, { name: "Scary Face", pos: { x: 2, y: 3 } }, { name: "Abstract Joker", pos: { x: 3, y: 3 } }, { name: "Delayed Gratification", pos: { x: 4, y: 3 } }, { name: "Hack", pos: { x: 5, y: 2 } }, { name: "Pareidolia", pos: { x: 6, y: 3 } }, { name: "Gros Michel", pos: { x: 7, y: 6 } }, { name: "Even Steven", pos: { x: 8, y: 3 } }, { name: "Odd Todd", pos: { x: 9, y: 3 } }, { name: "Scholar", pos: { x: 3, y: 6 } }, { name: "Business Card", pos: { x: 1, y: 4 } }, { name: "Supernova", pos: { x: 2, y: 4 } }, { name: "Ride the Bus", pos: { x: 1, y: 6 } }, { name: "Space Joker", pos: { x: 3, y: 5 } }, { name: "Egg", pos: { x: 0, y: 10 } }, { name: "Burglar", pos: { x: 1, y: 10 } }, { name: "Blackboard", pos: { x: 2, y: 10 } }, { name: "Runner", pos: { x: 3, y: 10 } }, { name: "Ice Cream", pos: { x: 4, y: 10 } }, { name: "DNA", pos: { x: 5, y: 10 } }, { name: "Splash", pos: { x: 6, y: 10 } }, { name: "Blue Joker", pos: { x: 7, y: 10 } }, { name: "Sixth Sense", pos: { x: 8, y: 10 } }, { name: "Constellation", pos: { x: 9, y: 10 } }, { name: "Hiker", pos: { x: 0, y: 11 } }, { name: "Faceless Joker", pos: { x: 1, y: 11 } }, { name: "Green Joker", pos: { x: 2, y: 11 } }, { name: "Superposition", pos: { x: 3, y: 11 } }, { name: "To Do List", pos: { x: 4, y: 11 } }, { name: "Cavendish", pos: { x: 5, y: 11 } }, { name: "Card Sharp", pos: { x: 6, y: 11 } }, { name: "Red Card", pos: { x: 7, y: 11 } }, { name: "Madness", pos: { x: 8, y: 11 } }, { name: "Square Joker", pos: { x: 9, y: 11 } }, { name: "Seance", pos: { x: 0, y: 12 } }, { name: "Riff-raff", pos: { x: 1, y: 12 } }, { name: "Vampire", pos: { x: 2, y: 12 } }, { name: "Shortcut", pos: { x: 3, y: 12 } }, { name: "Hologram", pos: { x: 4, y: 12 } }, { name: "Vagabond", pos: { x: 5, y: 12 } }, { name: "Baron", pos: { x: 6, y: 12 } }, { name: "Cloud 9", pos: { x: 7, y: 12 } }, { name: "Rocket", pos: { x: 8, y: 12 } }, { name: "Obelisk", pos: { x: 9, y: 12 } }, { name: "Midas Mask", pos: { x: 0, y: 13 } }, { name: "Luchador", pos: { x: 1, y: 13 } }, { name: "Photograph", pos: { x: 2, y: 13 } }, { name: "Gift Card", pos: { x: 3, y: 13 } }, { name: "Turtle Bean", pos: { x: 4, y: 13 } }, { name: "Erosion", pos: { x: 5, y: 13 } }, { name: "Reserved Parking", pos: { x: 6, y: 13 } }, { name: "Mail In Rebate", pos: { x: 7, y: 13 } }, { name: "To the Moon", pos: { x: 8, y: 13 } }, { name: "Hallucination", pos: { x: 9, y: 13 } }, { name: "Fortune Teller", pos: { x: 7, y: 5 } }, { name: "Juggler", pos: { x: 0, y: 1 } }, { name: "Drunkard", pos: { x: 1, y: 1 } }, { name: "Stone Joker", pos: { x: 9, y: 0 } }, { name: "Golden Joker", pos: { x: 9, y: 2 } }, { name: "Lucky Cat", pos: { x: 5, y: 14 } }, { name: "Baseball Card", pos: { x: 6, y: 14 } }, { name: "Bull", pos: { x: 7, y: 14 } }, { name: "Diet Cola", pos: { x: 8, y: 14 } }, { name: "Trading Card", pos: { x: 9, y: 14 } }, { name: "Flash Card", pos: { x: 0, y: 15 } }, { name: "Popcorn", pos: { x: 1, y: 15 } }, { name: "Spare Trousers", pos: { x: 4, y: 15 } }, { name: "Ancient Joker", pos: { x: 7, y: 15 } }, { name: "Ramen", pos: { x: 2, y: 15 } }, { name: "Walkie Talkie", pos: { x: 8, y: 15 } }, { name: "Seltzer", pos: { x: 3, y: 15 } }, { name: "Castle", pos: { x: 9, y: 15 } }, { name: "Smiley Face", pos: { x: 6, y: 15 } }, { name: "Campfire", pos: { x: 5, y: 15 } }, { name: "Golden Ticket", pos: { x: 5, y: 3 } }, { name: "Mr. Bones", pos: { x: 3, y: 4 } }, { name: "Acrobat", pos: { x: 2, y: 1 } }, { name: "Sock and Buskin", pos: { x: 3, y: 1 } }, { name: "Swashbuckler", pos: { x: 9, y: 5 } }, { name: "Troubadour", pos: { x: 0, y: 2 } }, { name: "Certificate", pos: { x: 8, y: 8 } }, { name: "Smeared Joker", pos: { x: 4, y: 6 } }, { name: "Throwback", pos: { x: 5, y: 7 } }, { name: "Hanging Chad", pos: { x: 9, y: 6 } }, { name: "Rough Gem", pos: { x: 9, y: 7 } }, { name: "Bloodstone", pos: { x: 0, y: 8 } }, { name: "Arrowhead", pos: { x: 1, y: 8 } }, { name: "Onyx Agate", pos: { x: 2, y: 8 } }, { name: "Glass Joker", pos: { x: 1, y: 3 } }, { name: "Showman", pos: { x: 6, y: 5 } }, { name: "Flower Pot", pos: { x: 0, y: 6 } }, { name: "Blueprint", pos: { x: 0, y: 3 } }, { name: "Wee Joker", pos: { x: 0, y: 4 } }, { name: "Merry Andy", pos: { x: 8, y: 0 } }, { name: "Oops! All 6s", pos: { x: 5, y: 6 } }, { name: "The Idol", pos: { x: 6, y: 7 } }, { name: "Seeing Double", pos: { x: 4, y: 4 } }, { name: "Matador", pos: { x: 4, y: 5 } }, { name: "Hit the Road", pos: { x: 8, y: 5 } }, { name: "The Duo", pos: { x: 5, y: 4 } }, { name: "The Trio", pos: { x: 6, y: 4 } }, { name: "The Family", pos: { x: 7, y: 4 } }, { name: "The Order", pos: { x: 8, y: 4 } }, { name: "The Tribe", pos: { x: 9, y: 4 } }, { name: "Stuntman", pos: { x: 8, y: 6 } }, { name: "Invisible Joker", pos: { x: 1, y: 7 } }, { name: "Brainstorm", pos: { x: 7, y: 7 } }, { name: "Satellite", pos: { x: 8, y: 7 } }, { name: "Shoot the Moon", pos: { x: 2, y: 6 } }, { name: "Drivers License", pos: { x: 0, y: 7 } }, { name: "Cartomancer", pos: { x: 7, y: 3 } }, { name: "Astronomer", pos: { x: 2, y: 7 } }, { name: "Burnt Joker", pos: { x: 3, y: 7 } }, { name: "Bootstraps", pos: { x: 9, y: 8 } }, { name: "Canio", pos: { x: 3, y: 8 } }, { name: "Triboulet", pos: { x: 4, y: 8 } }, { name: "Yorick", pos: { x: 5, y: 8 } }, { name: "Chicot", pos: { x: 6, y: 8 } }, { name: "Perkeo", pos: { x: 7, y: 8 } },
@@ -2,7 +2,13 @@ export interface SpritePos {
2
2
  x: number;
3
3
  y: number;
4
4
  }
5
- export type SpriteSheetType = "Jokers" | "Tarots" | "Vouchers" | "Boosters" | "Enhancers" | "Editions" | "BlindChips" | "tags";
5
+ export type SpriteSheetType = "Jokers" | "Tarots" | "Vouchers" | "Boosters" | "Enhancers" | "Editions" | "BlindChips" | "tags" | "Stakes" | "Decks";
6
+ import type { JamlAssetKey } from '../assets.js';
7
+ export declare const SHEET_META: Record<SpriteSheetType, {
8
+ cols: number;
9
+ rows: number;
10
+ assetKey: JamlAssetKey;
11
+ }>;
6
12
  export interface SpriteData {
7
13
  pos: SpritePos;
8
14
  type: SpriteSheetType;
@@ -1,4 +1,16 @@
1
1
  import { JOKERS, JOKER_FACES, TAROTS_AND_PLANETS, CONSUMABLE_FACES, VOUCHERS, BOSSES, TAGS, BOOSTER_PACKS, } from "./spriteData.js";
2
+ export const SHEET_META = {
3
+ Jokers: { cols: 10, rows: 16, assetKey: 'jokers' },
4
+ Tarots: { cols: 10, rows: 6, assetKey: 'tarots' },
5
+ Vouchers: { cols: 9, rows: 4, assetKey: 'vouchers' },
6
+ Boosters: { cols: 4, rows: 9, assetKey: 'boosters' },
7
+ BlindChips: { cols: 21, rows: 31, assetKey: 'blinds' },
8
+ tags: { cols: 6, rows: 5, assetKey: 'tags' },
9
+ Enhancers: { cols: 7, rows: 5, assetKey: 'enhancers' },
10
+ Editions: { cols: 5, rows: 1, assetKey: 'editions' },
11
+ Stakes: { cols: 5, rows: 2, assetKey: 'stakes' },
12
+ Decks: { cols: 13, rows: 4, assetKey: 'deck' },
13
+ };
2
14
  const normalize = (name) => name.toLowerCase().replace(/\s+/g, "");
3
15
  const stripPrefix = (name) => name.replace(/^(Joker|Tarot|Planet|Voucher|Pack|Edition|Tag) [|:] /i, "").trim();
4
16
  const ITEM_MAP = new Map();
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export interface JimboBadgeProps {
3
+ size?: 'sm' | 'md';
4
+ tone?: 'dark' | 'blue' | 'red' | 'green' | 'gold' | 'grey';
5
+ children: React.ReactNode;
6
+ }
7
+ export declare function JimboBadge({ size, tone, children }: JimboBadgeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { JimboColorOption as C } from './tokens.js';
3
+ export function JimboBadge({ size = 'sm', tone = 'dark', children }) {
4
+ const bgColors = {
5
+ dark: C.DARKEST,
6
+ blue: C.BLUE,
7
+ red: C.RED,
8
+ green: C.GREEN,
9
+ gold: C.GOLD,
10
+ grey: C.GREY
11
+ };
12
+ return (_jsx("span", { style: {
13
+ display: 'inline-flex',
14
+ alignItems: 'center',
15
+ padding: size === 'sm' ? '2px 6px' : '4px 8px',
16
+ fontSize: size === 'sm' ? 10 : 12,
17
+ background: bgColors[tone],
18
+ color: tone === 'dark' || tone === 'grey' ? C.WHITE : C.DARKEST,
19
+ border: `1px solid ${tone === 'dark' ? C.PANEL_EDGE : C.BLACK}`,
20
+ borderRadius: 4,
21
+ fontFamily: 'var(--font-sans, m6x11plus), monospace',
22
+ whiteSpace: 'nowrap'
23
+ }, children: children }));
24
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface JimboFloatingProps {
3
+ anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';
4
+ offset?: number;
5
+ zIndex?: number;
6
+ children: React.ReactNode;
7
+ }
8
+ export declare function JimboFloating({ anchor, offset, zIndex, children }: JimboFloatingProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function JimboFloating({ anchor = 'top-right', offset = 12, zIndex = 20, children }) {
3
+ const pos = { position: 'absolute', zIndex };
4
+ if (anchor.includes('top'))
5
+ pos.top = offset;
6
+ if (anchor.includes('bottom'))
7
+ pos.bottom = offset;
8
+ if (anchor.includes('left'))
9
+ pos.left = offset;
10
+ if (anchor.includes('right'))
11
+ pos.right = offset;
12
+ if (anchor.includes('center')) {
13
+ pos.left = '50%';
14
+ pos.transform = 'translateX(-50%)';
15
+ }
16
+ return (_jsx("div", { style: pos, children: children }));
17
+ }
@@ -0,0 +1,11 @@
1
+ export interface ToggleItem {
2
+ id: string;
3
+ label: string;
4
+ on: boolean;
5
+ }
6
+ export interface JimboToggleListProps {
7
+ items: ToggleItem[];
8
+ onToggle: (id: string) => void;
9
+ title?: string;
10
+ }
11
+ export declare function JimboToggleList({ items, onToggle, title }: JimboToggleListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { JimboPanel, JimboButton } from './panel.js';
3
+ export function JimboToggleList({ items, onToggle, title }) {
4
+ return (_jsx(JimboPanel, { children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [title && _jsx("div", { style: { fontSize: 12, color: 'var(--c-grey)', marginBottom: 4 }, children: title }), items.map(item => (_jsxs(JimboButton, { tone: "grey", size: "sm", onClick: () => onToggle(item.id), style: { justifyContent: 'flex-start', textAlign: 'left', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("div", { style: {
5
+ width: 10, height: 10, flexShrink: 0,
6
+ background: item.on ? 'var(--c-orange)' : 'var(--c-darkest)',
7
+ border: '1px solid var(--c-dark-grey)',
8
+ boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.5)'
9
+ } }), item.label] }, item.id)))] }) }));
10
+ }
package/dist/ui/footer.js CHANGED
@@ -9,7 +9,7 @@ const SUITS = [
9
9
  ];
10
10
  const CYCLE = '5s';
11
11
  export function JimboBalatroFooter({ hidden = false, className = '' }) {
12
- return (_jsxs("div", { className: ['fixed right-0 bottom-0 left-0 w-screen min-w-full transition-opacity duration-200', hidden ? 'pointer-events-none opacity-0' : 'opacity-100', className].filter(Boolean).join(' '), children: [_jsx("div", { style: { width: '100%', borderTop: '1px solid rgba(255,255,255,0.1)', background: 'rgba(0,0,0,0.9)', padding: '0 1rem 3px', textAlign: 'center' }, children: _jsxs("p", { style: { fontFamily: 'm6x11plus, monospace', fontSize: 'clamp(11px, 0.8vw + 8px, 14px)', display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'center', gap: '0 0.5rem', color: 'white', margin: 0 }, children: [_jsx("span", { children: "Not affiliated with LocalThunk or PlayStack" }), _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }, children: ["Made with", ' ', _jsx("span", { style: { position: 'relative', display: 'inline-block', width: '1.5em', height: '1em', verticalAlign: 'middle' }, children: SUITS.map(({ char, kf }) => (_jsx("span", { style: { position: 'absolute', inset: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', opacity: 0, animationName: kf, animationDuration: CYCLE, animationDelay: '0s', animationIterationCount: 'infinite', animationTimingFunction: 'ease-out' }, children: char }, char))) }), ' ', "for the", ' ', _jsx("a", { href: "https://playbalatro.com", target: "_blank", rel: "noopener noreferrer", style: { color: JimboColorOption.GOLD, textDecoration: 'none' }, children: "Balatro" }), ' ', "community"] })] }) }), _jsx("style", { children: `
12
+ return (_jsxs("div", { className: ['w-full transition-opacity duration-200', hidden ? 'pointer-events-none opacity-0' : 'opacity-100', className].filter(Boolean).join(' '), children: [_jsx("div", { style: { width: '100%', borderTop: '1px solid rgba(255,255,255,0.1)', background: 'rgba(0,0,0,0.9)', padding: '0 1rem 3px', textAlign: 'center' }, children: _jsxs("p", { style: { fontFamily: 'm6x11plus, monospace', fontSize: 'clamp(11px, 0.8vw + 8px, 14px)', display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'center', gap: '0 0.5rem', color: 'white', margin: 0 }, children: [_jsx("span", { children: "Not affiliated with LocalThunk or PlayStack" }), _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }, children: ["Made with", ' ', _jsx("span", { style: { position: 'relative', display: 'inline-block', width: '1.5em', height: '1em', verticalAlign: 'middle' }, children: SUITS.map(({ char, kf }) => (_jsx("span", { style: { position: 'absolute', inset: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', opacity: 0, animationName: kf, animationDuration: CYCLE, animationDelay: '0s', animationIterationCount: 'infinite', animationTimingFunction: 'ease-out' }, children: char }, char))) }), ' ', "for the", ' ', _jsx("a", { href: "https://playbalatro.com", target: "_blank", rel: "noopener noreferrer", style: { color: JimboColorOption.GOLD, textDecoration: 'none' }, children: "Balatro" }), ' ', "community"] })] }) }), _jsx("style", { children: `
13
13
  @keyframes jaml-heart { 0%{opacity:0;transform:scale(1)} 1%{opacity:1;transform:scale(1.45)} 3.5%{opacity:1;transform:scale(1)} 61.5%{opacity:1;transform:scale(1)} 62%{opacity:0} 100%{opacity:0} }
14
14
  @keyframes jaml-spade { 0%,61.5%{opacity:0} 62%{opacity:1;transform:scale(1.45)} 64.5%{opacity:1;transform:scale(1)} 71.5%{opacity:1} 72%{opacity:0} 100%{opacity:0} }
15
15
  @keyframes jaml-diamond { 0%,71.5%{opacity:0} 72%{opacity:1;transform:scale(1.45)} 74.5%{opacity:1;transform:scale(1)} 81.5%{opacity:1} 82%{opacity:0} 100%{opacity:0} }
@@ -25,16 +25,16 @@ function NavButton({ direction, onClick, disabled, 'aria-label': ariaLabel, }) {
25
25
  width: 48,
26
26
  border: 'none',
27
27
  borderRadius: 8,
28
- cursor: disabled ? 'not-allowed' : 'pointer',
29
- opacity: disabled ? 0.35 : 1,
30
- backgroundColor: JimboColorOption.RED,
28
+ cursor: disabled ? 'default' : 'pointer',
29
+ opacity: 1,
30
+ backgroundColor: disabled ? JimboColorOption.DARK_RED : JimboColorOption.RED,
31
31
  color: JimboColorOption.WHITE,
32
32
  display: 'flex',
33
33
  alignItems: 'center',
34
34
  justifyContent: 'center',
35
35
  transform: pressed ? `translateY(${JIMBO_ANIMATIONS.PRESS_TRANSLATE_Y}px)` : 'translateY(0)',
36
- boxShadow: pressed ? 'none' : `0 ${JIMBO_ANIMATIONS.PRESS_TRANSLATE_Y}px 0 0 ${JimboColorOption.DARK_RED}`,
37
- transition: `transform ${JIMBO_ANIMATIONS.PRESS_DURATION}ms ease, box-shadow ${JIMBO_ANIMATIONS.PRESS_DURATION}ms ease`,
36
+ boxShadow: pressed || disabled ? 'none' : `0 ${JIMBO_ANIMATIONS.PRESS_TRANSLATE_Y}px 0 0 ${JimboColorOption.DARK_RED}`,
37
+ transition: `transform ${JIMBO_ANIMATIONS.PRESS_DURATION}ms ease, box-shadow ${JIMBO_ANIMATIONS.PRESS_DURATION}ms ease, background-color ${JIMBO_ANIMATIONS.PRESS_DURATION}ms ease`,
38
38
  }, children: _jsx(ChevronSvg, { direction: direction }) }));
39
39
  }
40
40
  function ChevronSvg({ direction }) {
@@ -14,11 +14,12 @@ export interface JimboButtonProps {
14
14
  size?: 'xs' | 'sm' | 'md' | 'lg';
15
15
  fullWidth?: boolean;
16
16
  disabled?: boolean;
17
+ uppercase?: boolean;
17
18
  onClick?: () => void;
18
19
  style?: React.CSSProperties;
19
20
  children?: React.ReactNode;
20
21
  }
21
- export declare function JimboButton({ tone, size, fullWidth, disabled, onClick, style, children, }: JimboButtonProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function JimboButton({ tone, size, fullWidth, disabled, uppercase, onClick, style, children, }: JimboButtonProps): import("react/jsx-runtime").JSX.Element;
22
23
  export declare function JimboBackButton({ onClick }: {
23
24
  onClick?: () => void;
24
25
  }): import("react/jsx-runtime").JSX.Element;