jaml-ui 0.14.4 → 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.
package/DESIGN.md CHANGED
@@ -124,7 +124,7 @@ components:
124
124
 
125
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
126
 
127
- The system targets iPhone SE portrait (375x667) as the minimum viewport. No fat padding, no bloated margins — every pixel earns its place.
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
128
 
129
129
  ## Colors
130
130
 
@@ -153,7 +153,7 @@ All text is uppercase with generous letter-spacing (0.04em-0.1em) for labels and
153
153
 
154
154
  ## Layout
155
155
 
156
- Target: iPhone SE portrait (375x667). Components must fit without horizontal scroll. Vertical snap-scroll for ante pages. Horizontal swipe for seed navigation.
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
157
 
158
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
159
 
@@ -187,7 +187,7 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
187
187
 
188
188
  - DO use m6x11plus for everything. No fallback display fonts.
189
189
  - DO eyedrop colors from the game. Never guess or approximate.
190
- - DO design for iPhone SE portrait first. Desktop is a stretched phone.
190
+ - DO design for 320px portrait first. Desktop is an expanded view of the mobile baseline.
191
191
  - DO use translateY + box-shadow for button depth. Not CSS 3D transforms.
192
192
  - DO dim non-matching items (opacity 0.4 + grayscale 0.6). They stay visible for context.
193
193
  - DON'T use font-weight bold. m6x11plus is single-weight. Bold = muddy.
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;
package/dist/ui/panel.js CHANGED
@@ -41,10 +41,10 @@ const JIMBO_TONE_PAIRS = {
41
41
  gold: [JimboColorOption.GOLD, '#8a6a1e'],
42
42
  grey: [JimboColorOption.DARK_GREY, JimboColorOption.DARKEST],
43
43
  };
44
- export function JimboButton({ tone = 'orange', size = 'md', fullWidth = false, disabled = false, onClick, style, children, }) {
44
+ export function JimboButton({ tone = 'orange', size = 'md', fullWidth = false, disabled = false, uppercase = false, onClick, style, children, }) {
45
45
  const [pressed, setPressed] = useState(false);
46
46
  const [fg, sh] = JIMBO_TONE_PAIRS[tone] ?? JIMBO_TONE_PAIRS.orange;
47
- const pad = size === 'xs' ? '2px 8px' : size === 'sm' ? '4px 10px' : size === 'lg' ? '14px 18px' : '9px 14px';
47
+ const pad = size === 'xs' ? '2px 8px' : size === 'sm' ? '4px 10px' : size === 'lg' ? '10px 18px' : '6px 14px';
48
48
  const textSize = size === 'xs' ? 'xs' : size === 'sm' ? 'sm' : size === 'lg' ? 'lg' : 'md';
49
49
  return (_jsxs("div", { onMouseDown: () => { if (!disabled)
50
50
  setPressed(true); }, onMouseUp: () => setPressed(false), onMouseLeave: () => setPressed(false), onTouchStart: () => { if (!disabled)
@@ -54,10 +54,10 @@ export function JimboButton({ tone = 'orange', size = 'md', fullWidth = false, d
54
54
  transform: pressed ? 'translate(1px, 3px)' : 'translate(0,0)',
55
55
  transition: 'transform 55ms linear',
56
56
  textAlign: 'center',
57
- }, children: _jsx(JimboText, { size: textSize, uppercase: true, children: children }) })] }));
57
+ }, children: _jsx(JimboText, { size: textSize, uppercase: uppercase, children: children }) })] }));
58
58
  }
59
59
  export function JimboBackButton({ onClick }) {
60
- return (_jsx("div", { style: { display: 'flex', justifyContent: 'center', width: '100%', padding: '8px 10px 10px' }, children: _jsx(JimboButton, { tone: "orange", size: "md", onClick: onClick, style: { width: '66.666%' }, children: "Back" }) }));
60
+ return (_jsx("div", { style: { display: 'flex', justifyContent: 'center', width: '100%', padding: '4px 0' }, children: _jsx(JimboButton, { tone: "orange", size: "md", fullWidth: true, onClick: onClick, children: "Back" }) }));
61
61
  }
62
62
  export function JimboModal({ children, open, onClose, title, className }) {
63
63
  const [visible, setVisible] = useState(open);
@@ -8,3 +8,17 @@ export interface JimboSpriteProps {
8
8
  style?: React.CSSProperties;
9
9
  }
10
10
  export declare function JimboSprite({ name, sheet, width, height, style }: JimboSpriteProps): import("react/jsx-runtime").JSX.Element | null;
11
+ export interface StakeSpriteProps {
12
+ stake: string;
13
+ width?: number;
14
+ height?: number;
15
+ style?: React.CSSProperties;
16
+ }
17
+ export declare function StakeSprite({ stake, width, height, style }: StakeSpriteProps): import("react/jsx-runtime").JSX.Element;
18
+ export interface DeckSpriteProps {
19
+ deck: string;
20
+ width?: number;
21
+ height?: number;
22
+ style?: React.CSSProperties;
23
+ }
24
+ export declare function DeckSprite({ deck, width, height, style }: DeckSpriteProps): import("react/jsx-runtime").JSX.Element;
@@ -1,23 +1,17 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { resolveJamlAssetUrl } from '../assets.js';
4
- import { getSpriteData } from '../sprites/spriteMapper.js';
5
- const SHEET_META = {
6
- Jokers: { cols: 10, rows: 16, assetKey: 'jokers' },
7
- Tarots: { cols: 10, rows: 6, assetKey: 'tarots' },
8
- Vouchers: { cols: 9, rows: 4, assetKey: 'vouchers' },
9
- Boosters: { cols: 4, rows: 9, assetKey: 'boosters' },
10
- BlindChips: { cols: 21, rows: 31, assetKey: 'blinds' },
11
- tags: { cols: 6, rows: 5, assetKey: 'tags' },
12
- Enhancers: { cols: 7, rows: 5, assetKey: 'enhancers' },
13
- Editions: { cols: 5, rows: 1, assetKey: 'editions' },
14
- };
4
+ import { getSpriteData, SHEET_META } from '../sprites/spriteMapper.js';
15
5
  export function JimboSprite({ name, sheet, width = 40, height, style }) {
16
6
  const sprite = getSpriteData(name);
17
7
  const resolvedSheet = sheet ?? sprite?.type ?? 'Jokers';
18
8
  const meta = SHEET_META[resolvedSheet];
19
9
  const pos = sprite?.pos ?? { x: 0, y: 0 };
20
- const h = height ?? width;
10
+ let defaultH = width;
11
+ if (["Jokers", "Tarots", "Vouchers", "Boosters", "Decks", "Enhancers", "Editions"].includes(resolvedSheet)) {
12
+ defaultH = Math.round((width * 95) / 71);
13
+ }
14
+ const h = height ?? defaultH;
21
15
  if (!meta)
22
16
  return null;
23
17
  const bgW = width * meta.cols;
@@ -34,3 +28,49 @@ export function JimboSprite({ name, sheet, width = 40, height, style }) {
34
28
  ...style,
35
29
  } }));
36
30
  }
31
+ const STAKE_MAP = ["White", "Red", "Green", "Black", "Blue", "Purple", "Orange", "Gold"];
32
+ export function StakeSprite({ stake, width = 29, height, style }) {
33
+ const index = STAKE_MAP.indexOf(stake.replace(" Stake", ""));
34
+ const idx = index >= 0 ? index : 0;
35
+ const x = idx % 5;
36
+ const y = Math.floor(idx / 5);
37
+ const h = height ?? width;
38
+ const bgW = width * 5;
39
+ const bgH = h * 2;
40
+ return (_jsx("div", { style: {
41
+ width, height: h, flexShrink: 0,
42
+ backgroundImage: `url(${resolveJamlAssetUrl('stakes')})`,
43
+ backgroundSize: `${bgW}px ${bgH}px`,
44
+ backgroundPosition: `-${x * width}px -${y * h}px`,
45
+ backgroundRepeat: 'no-repeat',
46
+ imageRendering: 'pixelated',
47
+ ...style,
48
+ } }));
49
+ }
50
+ const DECK_ROWS = {
51
+ Red: 0,
52
+ Blue: 1,
53
+ Yellow: 2,
54
+ Green: 3,
55
+ Black: 0,
56
+ Magic: 1,
57
+ Nebula: 2,
58
+ Ghost: 3,
59
+ };
60
+ export function DeckSprite({ deck, width = 71, height, style }) {
61
+ const baseDeck = deck.replace(" Deck", "");
62
+ const y = DECK_ROWS[baseDeck] ?? 0;
63
+ const x = 12;
64
+ const h = height ?? (width * 95 / 71);
65
+ const bgW = width * 13;
66
+ const bgH = h * 4;
67
+ return (_jsx("div", { style: {
68
+ width, height: h, flexShrink: 0,
69
+ backgroundImage: `url(${resolveJamlAssetUrl('deck')})`,
70
+ backgroundSize: `${bgW}px ${bgH}px`,
71
+ backgroundPosition: `-${x * width}px -${y * h}px`,
72
+ backgroundRepeat: 'no-repeat',
73
+ imageRendering: 'pixelated',
74
+ ...style,
75
+ } }));
76
+ }
package/dist/ui.d.ts CHANGED
@@ -2,6 +2,9 @@ export * from './ui/tokens.js';
2
2
  export * from './ui/jimboText.js';
3
3
  export * from './ui/panel.js';
4
4
  export * from './ui/jimboTabs.js';
5
+ export * from './ui/JimboFloating.js';
6
+ export * from './ui/JimboToggleList.js';
7
+ export * from './ui/JimboBadge.js';
5
8
  export * from './ui/jimboFlankNav.js';
6
9
  export * from './ui/jimboFilterBar.js';
7
10
  export * from './ui/jimboBackground.js';
package/dist/ui.js CHANGED
@@ -2,6 +2,9 @@ export * from './ui/tokens.js';
2
2
  export * from './ui/jimboText.js';
3
3
  export * from './ui/panel.js';
4
4
  export * from './ui/jimboTabs.js';
5
+ export * from './ui/JimboFloating.js';
6
+ export * from './ui/JimboToggleList.js';
7
+ export * from './ui/JimboBadge.js';
5
8
  export * from './ui/jimboFlankNav.js';
6
9
  export * from './ui/jimboFilterBar.js';
7
10
  export * from './ui/jimboBackground.js';
package/package.json CHANGED
@@ -1,122 +1,129 @@
1
- {
2
- "name": "jaml-ui",
3
- "version": "0.14.4",
4
- "description": "Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- },
13
- "./core": {
14
- "types": "./dist/core.d.ts",
15
- "import": "./dist/core.js"
16
- },
17
- "./motely": {
18
- "types": "./dist/motely.d.ts",
19
- "import": "./dist/motely.js"
20
- },
21
- "./ui": {
22
- "types": "./dist/ui.d.ts",
23
- "import": "./dist/ui.js"
24
- },
25
- "./r3f": {
26
- "types": "./dist/r3f.d.ts",
27
- "import": "./dist/r3f.js"
28
- },
29
- "./fonts.css": "./fonts.css",
30
- "./package.json": "./package.json"
31
- },
32
- "sideEffects": [
33
- "./fonts.css"
34
- ],
35
- "files": [
36
- "dist",
37
- "assets/*.png",
38
- "assets/fonts",
39
- "fonts.css",
40
- "README.md",
41
- "DESIGN.md",
42
- "LICENSE"
43
- ],
44
- "scripts": {
45
- "build": "tsc --pretty false",
46
- "dev": "tsc --watch",
47
- "demo": "vite --config demo/vite.config.ts",
48
- "typecheck": "tsc --noEmit --pretty false",
49
- "prepack": "npm run build"
50
- },
51
- "engines": {
52
- "node": ">=18"
53
- },
54
- "publishConfig": {
55
- "access": "public"
56
- },
57
- "repository": {
58
- "type": "git",
59
- "url": "https://github.com/OptimusPi/jaml-ui"
60
- },
61
- "homepage": "https://github.com/OptimusPi/jaml-ui#readme",
62
- "bugs": {
63
- "url": "https://github.com/OptimusPi/jaml-ui/issues"
64
- },
65
- "keywords": [
66
- "balatro",
67
- "jaml",
68
- "motely",
69
- "seed",
70
- "card",
71
- "sprite",
72
- "ui"
73
- ],
74
- "author": "pifreak",
75
- "license": "MIT",
76
- "peerDependencies": {
77
- "@monaco-editor/react": ">=4.0.0",
78
- "@react-spring/three": ">=9.0.0",
79
- "@react-three/fiber": ">=8.0.0",
80
- "monaco-editor": ">=0.50.0",
81
- "motely-wasm": "^10.2.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
82
- "react": "^18.2.0 || ^19.0.0",
83
- "react-dom": "^18.2.0 || ^19.0.0",
84
- "react-icons": ">=5.0.0",
85
- "three": ">=0.150.0"
86
- },
87
- "peerDependenciesMeta": {
88
- "@react-spring/three": {
89
- "optional": true
90
- },
91
- "@react-three/fiber": {
92
- "optional": true
93
- },
94
- "motely-wasm": {
95
- "optional": true
96
- },
97
- "react-icons": {
98
- "optional": true
99
- },
100
- "three": {
101
- "optional": true
102
- }
103
- },
104
- "devDependencies": {
105
- "@monaco-editor/react": "^4.7.0",
106
- "@react-spring/three": "^10.0.3",
107
- "@react-three/fiber": "^9.6.0",
108
- "@types/react": "^19.2.14",
109
- "@types/react-dom": "^19.2.3",
110
- "@types/three": "^0.184.0",
111
- "@vitejs/plugin-react": "^5.0.4",
112
- "monaco-editor": "^0.55.1",
113
- "motely-wasm": "^14.0.2",
114
- "react": "^19.2.4",
115
- "react-dom": "^19.2.4",
116
- "react-icons": "^5.6.0",
117
- "three": "^0.184.0",
118
- "typescript": "^5.9.3",
119
- "vite": "^8.0.9",
120
- "@google/design.md": "^0.1.1"
121
- }
122
- }
1
+ {
2
+ "name": "jaml-ui",
3
+ "version": "0.16.0",
4
+ "description": "Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./core": {
14
+ "types": "./dist/core.d.ts",
15
+ "import": "./dist/core.js"
16
+ },
17
+ "./motely": {
18
+ "types": "./dist/motely.d.ts",
19
+ "import": "./dist/motely.js"
20
+ },
21
+ "./ui": {
22
+ "types": "./dist/ui.d.ts",
23
+ "import": "./dist/ui.js"
24
+ },
25
+ "./r3f": {
26
+ "types": "./dist/r3f.d.ts",
27
+ "import": "./dist/r3f.js"
28
+ },
29
+ "./fonts.css": "./fonts.css",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "sideEffects": [
33
+ "./fonts.css"
34
+ ],
35
+ "files": [
36
+ "dist",
37
+ "assets/*.png",
38
+ "assets/fonts",
39
+ "fonts.css",
40
+ "README.md",
41
+ "DESIGN.md",
42
+ "LICENSE"
43
+ ],
44
+ "scripts": {
45
+ "build": "tsc --pretty false",
46
+ "dev": "tsc --watch",
47
+ "demo": "vite --config demo/vite.config.ts",
48
+ "typecheck": "tsc --noEmit --pretty false",
49
+ "prepack": "npm run build"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "https://github.com/OptimusPi/jaml-ui"
60
+ },
61
+ "homepage": "https://github.com/OptimusPi/jaml-ui#readme",
62
+ "bugs": {
63
+ "url": "https://github.com/OptimusPi/jaml-ui/issues"
64
+ },
65
+ "keywords": [
66
+ "balatro",
67
+ "jaml",
68
+ "motely",
69
+ "seed",
70
+ "card",
71
+ "sprite",
72
+ "ui"
73
+ ],
74
+ "author": "pifreak",
75
+ "license": "MIT",
76
+ "peerDependencies": {
77
+ "@monaco-editor/react": ">=4.0.0",
78
+ "@react-spring/three": ">=9.0.0",
79
+ "@react-three/fiber": ">=8.0.0",
80
+ "monaco-editor": ">=0.50.0",
81
+ "motely-wasm": "^10.2.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
82
+ "react": "^18.2.0 || ^19.0.0",
83
+ "react-dom": "^18.2.0 || ^19.0.0",
84
+ "react-icons": ">=5.0.0",
85
+ "three": ">=0.150.0",
86
+ "@react-three/drei": ">=9.0.0"
87
+ },
88
+ "peerDependenciesMeta": {
89
+ "@react-spring/three": {
90
+ "optional": true
91
+ },
92
+ "@react-three/fiber": {
93
+ "optional": true
94
+ },
95
+ "motely-wasm": {
96
+ "optional": true
97
+ },
98
+ "react-icons": {
99
+ "optional": true
100
+ },
101
+ "three": {
102
+ "optional": true
103
+ },
104
+ "@react-three/drei": {
105
+ "optional": true
106
+ }
107
+ },
108
+ "devDependencies": {
109
+ "@google/design.md": "^0.1.1",
110
+ "@monaco-editor/react": "^4.7.0",
111
+ "@react-spring/three": "^10.0.3",
112
+ "@react-three/fiber": "^9.6.0",
113
+ "@types/react": "^19.2.14",
114
+ "@types/react-dom": "^19.2.3",
115
+ "@types/three": "^0.184.0",
116
+ "@vitejs/plugin-react": "^5.0.4",
117
+ "monaco-editor": "^0.55.1",
118
+ "motely-wasm": "^14.0.2",
119
+ "react": "^19.2.4",
120
+ "react-dom": "^19.2.4",
121
+ "react-icons": "^5.6.0",
122
+ "three": "^0.184.0",
123
+ "typescript": "^5.9.3",
124
+ "vite": "^8.0.9"
125
+ },
126
+ "dependencies": {
127
+ "@react-three/drei": "^10.7.7"
128
+ }
129
+ }