jaml-ui 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/DESIGN.md +9 -11
  2. package/dist/assets.d.ts +6 -0
  3. package/dist/assets.js +9 -0
  4. package/dist/components/AnalyzerExplorer.d.ts +4 -1
  5. package/dist/components/AnalyzerExplorer.js +14 -48
  6. package/dist/components/GameCard.js +8 -7
  7. package/dist/components/JamlAestheticSelector.d.ts +4 -0
  8. package/dist/components/JamlAestheticSelector.js +6 -19
  9. package/dist/components/JamlAnalyzerFullscreen.d.ts +7 -1
  10. package/dist/components/JamlAnalyzerFullscreen.js +18 -47
  11. package/dist/components/JamlIde.js +12 -24
  12. package/dist/components/JamlIdeVisual.js +3 -56
  13. package/dist/components/JamlMapPreview.d.ts +6 -1
  14. package/dist/components/JamlMapPreview.js +99 -21
  15. package/dist/components/JamlSeedInput.d.ts +5 -0
  16. package/dist/components/JamlSeedInput.js +11 -14
  17. package/dist/components/JamlSpeedometer.d.ts +8 -8
  18. package/dist/components/JamlSpeedometer.js +24 -46
  19. package/dist/components/MotelyVersionBadge.d.ts +1 -3
  20. package/dist/components/MotelyVersionBadge.js +4 -16
  21. package/dist/components/jamlMap/JamlMapEditorDemo.d.ts +8 -0
  22. package/dist/components/jamlMap/JamlMapEditorDemo.js +170 -0
  23. package/dist/components/jamlMap/JokerPicker.d.ts +7 -0
  24. package/dist/components/jamlMap/JokerPicker.js +258 -0
  25. package/dist/components/jamlMap/MysterySlot.d.ts +32 -0
  26. package/dist/components/jamlMap/MysterySlot.js +109 -0
  27. package/dist/components/jamlMap/index.d.ts +3 -0
  28. package/dist/components/jamlMap/index.js +3 -0
  29. package/dist/core.d.ts +0 -2
  30. package/dist/core.js +0 -2
  31. package/dist/decode/motelyItemDecoder.d.ts +10 -23
  32. package/dist/decode/motelyItemDecoder.js +103 -272
  33. package/dist/decode/motelySprite.d.ts +4 -0
  34. package/dist/decode/motelySprite.js +57 -0
  35. package/dist/hooks/analyzerStreamRegistry.js +30 -82
  36. package/dist/hooks/useAnalyzer.d.ts +10 -3
  37. package/dist/hooks/useAnalyzer.js +11 -6
  38. package/dist/hooks/useIntersectionObserver.d.ts +14 -0
  39. package/dist/hooks/useIntersectionObserver.js +50 -0
  40. package/dist/index.d.ts +5 -8
  41. package/dist/index.js +4 -7
  42. package/dist/motely.d.ts +2 -2
  43. package/dist/motely.js +2 -2
  44. package/dist/motelyDisplay.d.ts +4 -623
  45. package/dist/motelyDisplay.js +26 -165
  46. package/dist/r3f/Card3D.d.ts +2 -2
  47. package/dist/r3f/Card3D.js +13 -48
  48. package/dist/r3f/JimboText3D.js +3 -2
  49. package/dist/render/CanvasRenderer.js +7 -171
  50. package/dist/sprites/spriteMapper.d.ts +71 -0
  51. package/dist/sprites/spriteMapper.js +40 -0
  52. package/dist/ui/JimboBadge.d.ts +8 -2
  53. package/dist/ui/JimboBadge.js +6 -22
  54. package/dist/ui/JimboToggleList.js +2 -7
  55. package/dist/ui/codeBlock.js +2 -3
  56. package/dist/ui/footer.d.ts +4 -0
  57. package/dist/ui/footer.js +6 -4
  58. package/dist/ui/hooks.d.ts +89 -0
  59. package/dist/ui/hooks.js +551 -0
  60. package/dist/ui/jimboBackground.js +2 -131
  61. package/dist/ui/jimboCopyRow.d.ts +4 -0
  62. package/dist/ui/jimboCopyRow.js +5 -22
  63. package/dist/ui/jimboFilterBar.d.ts +1 -4
  64. package/dist/ui/jimboFilterBar.js +2 -61
  65. package/dist/ui/jimboFlankNav.d.ts +1 -2
  66. package/dist/ui/jimboFlankNav.js +5 -30
  67. package/dist/ui/jimboTabs.d.ts +1 -5
  68. package/dist/ui/jimboTabs.js +6 -41
  69. package/dist/ui/jimboText.d.ts +1 -1
  70. package/dist/ui/jimboText.js +15 -32
  71. package/dist/ui/jimboTooltip.d.ts +1 -12
  72. package/dist/ui/jimboTooltip.js +6 -82
  73. package/dist/ui/panel.d.ts +2 -1
  74. package/dist/ui/panel.js +11 -47
  75. package/dist/ui/showcase.d.ts +4 -0
  76. package/dist/ui/showcase.js +9 -36
  77. package/dist/ui/sprites.js +3 -2
  78. package/dist/ui.d.ts +1 -0
  79. package/dist/ui.js +2 -0
  80. package/package.json +7 -6
  81. package/dist/decode/packedBalatroItem.d.ts +0 -13
  82. package/dist/decode/packedBalatroItem.js +0 -26
  83. package/dist/hooks/loadMotelyWasm.d.ts +0 -7
  84. package/dist/hooks/loadMotelyWasm.js +0 -16
  85. package/dist/utils/itemUtils.d.ts +0 -11
  86. package/dist/utils/itemUtils.js +0 -71
@@ -9,46 +9,19 @@ const TONE_COLOR = {
9
9
  gold: JimboColorOption.GOLD,
10
10
  green: JimboColorOption.GREEN,
11
11
  };
12
- const DEFAULT_STATS = { searched: '15.6B', matches: '2,847', speed: '5.4M/s' };
12
+ const DEFAULT_STATS = { searched: '0', matches: '0', speed: '0' };
13
+ /**
14
+ * Landing/showcase screen for the seed curator.
15
+ * All styling via jimbo.css `.j-showcase` classes — zero inline styles.
16
+ */
13
17
  export function Showcase({ hotFilters = [], recentFinds = [], stats = DEFAULT_STATS, onNewSearch, onBrowseFilters, onBack, }) {
14
18
  const C = JimboColorOption;
15
- return (_jsxs("div", { style: {
16
- width: '100%', height: '100%', background: C.DARKEST,
17
- display: 'flex', flexDirection: 'column',
18
- fontFamily: 'm6x11plus, monospace', color: C.WHITE, overflow: 'hidden',
19
- }, children: [_jsxs("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto', padding: '18px 14px 10px' }, children: [_jsxs("div", { style: { textAlign: 'center', marginBottom: 18 }, children: [_jsx("div", { style: { fontSize: 32, letterSpacing: 3, lineHeight: 1, color: C.GOLD, textShadow: '2px 2px 0 rgba(0,0,0,.8)' }, children: "Balatro" }), _jsx("div", { style: { fontSize: 14, letterSpacing: 4, color: C.GREY, marginTop: 4, textShadow: '1px 1px 0 rgba(0,0,0,.8)' }, children: "Seed \u00B7 Curator" })] }), _jsx("div", { style: {
20
- background: C.DARK_GREY, borderRadius: 6, padding: 10,
21
- border: `2px solid ${C.PANEL_EDGE}`, boxShadow: `0 2px 0 ${C.BLACK}`,
22
- display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8, textAlign: 'center', marginBottom: 16,
23
- }, children: [
19
+ return (_jsxs("div", { className: "j-showcase", children: [_jsxs("div", { className: "j-showcase__scroll", children: [_jsxs("div", { className: "j-showcase__wordmark", children: [_jsx("div", { className: "j-showcase__wordmark-title", children: "Balatro" }), _jsx("div", { className: "j-showcase__wordmark-sub", children: "Seed \u00B7 Curator" })] }), _jsx("div", { className: "j-showcase__stats", children: [
24
20
  [stats.searched, 'searched'],
25
21
  [stats.matches, 'matches'],
26
22
  [stats.speed, 'speed'],
27
- ].map(([n, l]) => (_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, color: C.GOLD, textShadow: '1px 1px 0 rgba(0,0,0,.8)' }, children: n }), _jsx("div", { style: { fontSize: 9, color: C.GREY, letterSpacing: 2, marginTop: 2 }, children: l })] }, l))) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }, children: [_jsx("div", { style: {
28
- fontSize: 11, letterSpacing: 2, padding: '2px 8px',
29
- background: C.BLUE, color: C.WHITE, borderRadius: 3,
30
- textShadow: '1px 1px 0 rgba(0,0,0,.8)',
31
- }, children: "Hot Filters" }), _jsx("div", { style: { flex: 1, height: 2, background: `${C.BLUE}55`, borderRadius: 1 } })] }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }, children: hotFilters.map((f, i) => {
23
+ ].map(([n, l]) => (_jsxs("div", { children: [_jsx("div", { className: "j-showcase__stat-value", children: n }), _jsx("div", { className: "j-showcase__stat-label", children: l })] }, l))) }), _jsxs("div", { className: "j-showcase__section-header", children: [_jsx("div", { className: "j-showcase__section-tag", style: { background: C.BLUE }, children: "Hot Filters" }), _jsx("div", { className: "j-showcase__section-rule", style: { background: `${C.BLUE}55` } })] }), _jsx("div", { className: "j-showcase__filter-list", children: hotFilters.map((f, i) => {
32
24
  const tColor = TONE_COLOR[f.tone];
33
- return (_jsxs("div", { style: {
34
- background: C.DARK_GREY, borderRadius: 6, padding: 10,
35
- border: `2px solid ${tColor}`, boxShadow: `0 2px 0 ${C.BLACK}`,
36
- display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
37
- }, children: [_jsx("div", { style: { display: 'flex', gap: 2 }, children: f.sample.map((name, j) => (_jsx("div", { style: { width: 30, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx(JimboSprite, { name: name, width: 28 }) }, j))) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: {
38
- fontSize: 13, color: C.WHITE, letterSpacing: 1,
39
- textShadow: '1px 1px 0 rgba(0,0,0,.8)',
40
- overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
41
- }, children: f.name }), _jsxs("div", { style: { fontSize: 9, color: C.GOLD_TEXT, letterSpacing: 1, marginTop: 2 }, children: ["by ", f.author] })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: 14, color: tColor, textShadow: '1px 1px 0 rgba(0,0,0,.8)' }, children: f.hits }), _jsx("div", { style: { fontSize: 8, color: C.GREY, letterSpacing: 1 }, children: "seeds" })] })] }, i));
42
- }) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }, children: [_jsx("div", { style: {
43
- fontSize: 11, letterSpacing: 2, padding: '2px 8px',
44
- background: C.GREEN, color: C.WHITE, borderRadius: 3,
45
- textShadow: '1px 1px 0 rgba(0,0,0,.8)',
46
- }, children: "Recent Finds" }), _jsx("div", { style: { flex: 1, height: 2, background: `${C.GREEN}55`, borderRadius: 1 } })] }), _jsx("div", { style: {
47
- background: C.DARK_GREY, borderRadius: 6, padding: '8px 10px',
48
- border: `2px solid ${C.PANEL_EDGE}`, boxShadow: `0 2px 0 ${C.BLACK}`,
49
- fontSize: 11, color: C.GREY, letterSpacing: 1, lineHeight: 1.7,
50
- }, children: recentFinds.length === 0 ? (_jsx("div", { style: { color: C.GREY }, children: "No recent finds yet." })) : recentFinds.map((r, i) => (_jsxs("div", { children: [_jsx("span", { style: { color: C.GOLD_TEXT }, children: r.seed }), ' · ', r.filterName, r.score > 0 && _jsxs("span", { style: { color: C.GREEN_TEXT }, children: [" +", r.score] })] }, i))) }), _jsx("div", { style: { height: 16 } })] }), _jsxs("div", { style: {
51
- padding: '8px 10px 10px', borderTop: `2px solid ${C.BLACK}`, background: C.DARK_GREY,
52
- display: 'flex', flexDirection: 'column', gap: 6,
53
- }, children: [_jsx(JimboButton, { tone: "green", fullWidth: true, size: "md", onClick: onNewSearch, children: "New Search" }), _jsx(JimboButton, { tone: "blue", fullWidth: true, size: "md", onClick: onBrowseFilters, children: "Browse Filters" }), _jsx(JimboButton, { tone: "orange", fullWidth: true, size: "md", onClick: onBack, children: "Back" })] })] }));
25
+ return (_jsxs("div", { className: "j-showcase__filter-card", style: { border: `2px solid ${tColor}` }, children: [_jsx("div", { className: "j-showcase__filter-sprites", children: f.sample.map((name, j) => (_jsx("div", { className: "j-showcase__filter-sprite", children: _jsx(JimboSprite, { name: name, width: 28 }) }, j))) }), _jsxs("div", { className: "j-showcase__filter-info", children: [_jsx("div", { className: "j-showcase__filter-name", children: f.name }), _jsxs("div", { className: "j-showcase__filter-author", children: ["by ", f.author] })] }), _jsxs("div", { className: "j-showcase__filter-hits", children: [_jsx("div", { className: "j-showcase__filter-hits-value", style: { color: tColor }, children: f.hits }), _jsx("div", { className: "j-showcase__filter-hits-label", children: "seeds" })] })] }, i));
26
+ }) }), _jsxs("div", { className: "j-showcase__section-header", children: [_jsx("div", { className: "j-showcase__section-tag", style: { background: C.GREEN }, children: "Recent Finds" }), _jsx("div", { className: "j-showcase__section-rule", style: { background: `${C.GREEN}55` } })] }), _jsx("div", { className: "j-showcase__recent", children: recentFinds.length === 0 ? (_jsx("div", { children: "No recent finds yet." })) : recentFinds.map((r, i) => (_jsxs("div", { children: [_jsx("span", { style: { color: C.GOLD_TEXT }, children: r.seed }), ' · ', r.filterName, r.score > 0 && _jsxs("span", { style: { color: C.GREEN_TEXT }, children: [" +", r.score] })] }, i))) }), _jsx("div", { style: { height: 16 } })] }), _jsxs("div", { className: "j-showcase__actions", children: [_jsx(JimboButton, { tone: "green", fullWidth: true, size: "md", onClick: onNewSearch, children: "New Search" }), _jsx(JimboButton, { tone: "blue", fullWidth: true, size: "md", onClick: onBrowseFilters, children: "Browse Filters" }), _jsx(JimboButton, { tone: "orange", fullWidth: true, size: "md", onClick: onBack, children: "Back" })] })] }));
54
27
  }
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { resolveJamlAssetUrl } from '../assets.js';
4
- import { getSpriteData, SHEET_META } from '../sprites/spriteMapper.js';
4
+ import { getSpriteData, getMysterySprite, SHEET_META } from '../sprites/spriteMapper.js';
5
5
  export function JimboSprite({ name, sheet, width = 40, height, style }) {
6
6
  const sprite = getSpriteData(name);
7
7
  const resolvedSheet = sheet ?? sprite?.type ?? 'Jokers';
8
8
  const meta = SHEET_META[resolvedSheet];
9
- const pos = sprite?.pos ?? { x: 0, y: 0 };
9
+ const mystery = getMysterySprite(resolvedSheet);
10
+ const pos = sprite?.pos ?? mystery.pos;
10
11
  let defaultH = width;
11
12
  if (["Jokers", "Tarots", "Vouchers", "Boosters", "Decks", "Enhancers", "Editions"].includes(resolvedSheet)) {
12
13
  defaultH = Math.round((width * 95) / 71);
package/dist/ui.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './ui/tokens.js';
2
+ import './ui/jimbo.css';
2
3
  export * from './ui/jimboText.js';
3
4
  export * from './ui/panel.js';
4
5
  export * from './ui/jimboTabs.js';
package/dist/ui.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './ui/tokens.js';
2
+ // Side-effect: design system CSS custom properties + component classes
3
+ import './ui/jimbo.css';
2
4
  export * from './ui/jimboText.js';
3
5
  export * from './ui/panel.js';
4
6
  export * from './ui/jimboTabs.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaml-ui",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -76,14 +76,14 @@
76
76
  "peerDependencies": {
77
77
  "@monaco-editor/react": ">=4.0.0",
78
78
  "@react-spring/three": ">=9.0.0",
79
+ "@react-three/drei": ">=9.0.0",
79
80
  "@react-three/fiber": ">=8.0.0",
80
81
  "monaco-editor": ">=0.50.0",
81
- "motely-wasm": "^10.2.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
82
+ "motely-wasm": "^14.3.1",
82
83
  "react": "^18.2.0 || ^19.0.0",
83
84
  "react-dom": "^18.2.0 || ^19.0.0",
84
85
  "react-icons": ">=5.0.0",
85
- "three": ">=0.150.0",
86
- "@react-three/drei": ">=9.0.0"
86
+ "three": ">=0.150.0"
87
87
  },
88
88
  "peerDependenciesMeta": {
89
89
  "@react-spring/three": {
@@ -110,12 +110,13 @@
110
110
  "@monaco-editor/react": "^4.7.0",
111
111
  "@react-spring/three": "^10.0.3",
112
112
  "@react-three/fiber": "^9.6.0",
113
+ "@types/node": "^25.6.0",
113
114
  "@types/react": "^19.2.14",
114
115
  "@types/react-dom": "^19.2.3",
115
116
  "@types/three": "^0.184.0",
116
117
  "@vitejs/plugin-react": "^5.0.4",
117
118
  "monaco-editor": "^0.55.1",
118
- "motely-wasm": "^14.0.2",
119
+ "motely-wasm": "^14.2.41",
119
120
  "react": "^19.2.4",
120
121
  "react-dom": "^19.2.4",
121
122
  "react-icons": "^5.6.0",
@@ -124,6 +125,6 @@
124
125
  "vite": "^8.0.9"
125
126
  },
126
127
  "dependencies": {
127
- "@react-three/drei": "^10.7.7"
128
+ "@react-three/drei": ">=9.0.0"
128
129
  }
129
130
  }
@@ -1,13 +0,0 @@
1
- /** Bit-packed shop/card ids (Balatro item encoding). */
2
- export declare const BalatroItemCategory: {
3
- readonly Standardcard: 1;
4
- readonly Spectral: 2;
5
- readonly Tarot: 3;
6
- readonly Planet: 4;
7
- readonly Joker: 5;
8
- readonly Invalid: 15;
9
- };
10
- export declare function packedItemCategory(packed: number): number;
11
- export declare function packedJokerRarity(packed: number): number;
12
- export declare function packedItemIndex(packed: number): number;
13
- export declare function isPackedItemValid(packed: number): boolean;
@@ -1,26 +0,0 @@
1
- /** Bit-packed shop/card ids (Balatro item encoding). */
2
- export const BalatroItemCategory = {
3
- Standardcard: 1,
4
- Spectral: 2,
5
- Tarot: 3,
6
- Planet: 4,
7
- Joker: 5,
8
- Invalid: 0xf,
9
- };
10
- const CATEGORY_OFFSET = 12;
11
- const CATEGORY_MASK = 0xf000;
12
- const RARITY_OFFSET = 10;
13
- const RARITY_MASK = 0x0c00;
14
- export function packedItemCategory(packed) {
15
- return (packed & CATEGORY_MASK) >> CATEGORY_OFFSET;
16
- }
17
- export function packedJokerRarity(packed) {
18
- return (packed & RARITY_MASK) >> RARITY_OFFSET;
19
- }
20
- export function packedItemIndex(packed) {
21
- return packed & ~(CATEGORY_MASK | RARITY_MASK);
22
- }
23
- export function isPackedItemValid(packed) {
24
- const category = packedItemCategory(packed);
25
- return category >= BalatroItemCategory.Standardcard && category <= BalatroItemCategory.Joker;
26
- }
@@ -1,7 +0,0 @@
1
- interface MotelyModules {
2
- MotelyWasm: any;
3
- MotelyWasmEvents: any;
4
- Motely: any;
5
- }
6
- export declare function loadMotelyWasm(url: string): Promise<MotelyModules>;
7
- export {};
@@ -1,16 +0,0 @@
1
- // Module-level cache so multiple hooks share a single boot per URL.
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- const cache = new Map();
4
- export function loadMotelyWasm(url) {
5
- if (!cache.has(url)) {
6
- cache.set(url, (async () => {
7
- const mod = await import(/* @vite-ignore */ url);
8
- await mod.default.boot();
9
- return { MotelyWasm: mod.MotelyWasm, MotelyWasmEvents: mod.MotelyWasmEvents, Motely: mod.Motely };
10
- })().catch((err) => {
11
- cache.delete(url);
12
- throw err;
13
- }));
14
- }
15
- return cache.get(url);
16
- }
@@ -1,11 +0,0 @@
1
- /** Map MotelyItemType enum names to display-friendly strings */
2
- export declare function getItemDisplayName(enumKey: string): string;
3
- export type CardCategory = "joker" | "tarot" | "planet" | "spectral" | "playing" | "unknown";
4
- export declare function getItemCategory(enumKey: string): CardCategory;
5
- export declare const CATEGORY_COLORS: Record<CardCategory, {
6
- bg: string;
7
- border: string;
8
- text: string;
9
- }>;
10
- /** Suit color for standard cards */
11
- export declare function getSuitColor(enumKey: string): string;
@@ -1,71 +0,0 @@
1
- /** Map MotelyItemType enum names to display-friendly strings */
2
- export function getItemDisplayName(enumKey) {
3
- // Convert PascalCase to spaced: "GreedyJoker" -> "Greedy Joker"
4
- // Handle special cases first
5
- const specials = {
6
- ChaostheClown: "Chaos the Clown",
7
- OopsAll6s: "Oops! All 6s",
8
- EightBall: "8 Ball",
9
- DNA: "DNA",
10
- MrBones: "Mr. Bones",
11
- ToDoList: "To Do List",
12
- Cloud9: "Cloud 9",
13
- SockAndBuskin: "Sock and Buskin",
14
- TheSoul: "The Soul",
15
- BlackHole: "Black Hole",
16
- PlanetX: "Planet X",
17
- };
18
- if (specials[enumKey])
19
- return specials[enumKey];
20
- // Standard cards: C2 = 2 of Clubs, D10 = 10 of Diamonds, etc.
21
- const suitMap = { C: "♣", D: "♦", H: "♥", S: "♠" };
22
- const cardMatch = enumKey.match(/^([CDHS])([2-9JQKA]|10)$/);
23
- if (cardMatch) {
24
- const [, suit, rank] = cardMatch;
25
- const rankName = { J: "Jack", Q: "Queen", K: "King", A: "Ace" }[rank] ?? rank;
26
- return `${rankName} ${suitMap[suit]}`;
27
- }
28
- // General PascalCase split
29
- return enumKey.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
30
- }
31
- const TAROT_NAMES = new Set([
32
- "TheFool", "TheMagician", "TheHighPriestess", "TheEmpress", "TheEmperor",
33
- "TheHierophant", "TheLovers", "TheChariot", "Justice", "TheHermit",
34
- "TheWheelOfFortune", "Strength", "TheHangedMan", "Death", "Temperance",
35
- "TheDevil", "TheTower", "TheStar", "TheMoon", "TheSun", "Judgement", "TheWorld",
36
- ]);
37
- const PLANET_NAMES = new Set([
38
- "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn",
39
- "Uranus", "Neptune", "Pluto", "PlanetX", "Ceres", "Eris",
40
- ]);
41
- const SPECTRAL_NAMES = new Set([
42
- "Familiar", "Grim", "Incantation", "Talisman", "Aura", "Wraith",
43
- "Sigil", "Ouija", "Ectoplasm", "Immolate", "Ankh", "DejaVu",
44
- "Hex", "Trance", "Medium", "Cryptid", "TheSoul", "BlackHole",
45
- ]);
46
- export function getItemCategory(enumKey) {
47
- if (/^[CDHS]([2-9JQKA]|10)$/.test(enumKey))
48
- return "playing";
49
- if (TAROT_NAMES.has(enumKey))
50
- return "tarot";
51
- if (PLANET_NAMES.has(enumKey))
52
- return "planet";
53
- if (SPECTRAL_NAMES.has(enumKey))
54
- return "spectral";
55
- // Everything else between the standard cards and Invalid is a joker
56
- return "joker";
57
- }
58
- export const CATEGORY_COLORS = {
59
- joker: { bg: "bg-purple-900/30", border: "border-purple-500/50", text: "text-purple-200" },
60
- tarot: { bg: "bg-blue-900/30", border: "border-blue-500/50", text: "text-blue-200" },
61
- planet: { bg: "bg-amber-900/30", border: "border-amber-500/50", text: "text-amber-200" },
62
- spectral: { bg: "bg-cyan-900/30", border: "border-cyan-500/50", text: "text-cyan-200" },
63
- playing: { bg: "bg-neutral-100 dark:bg-neutral-800", border: "border-neutral-300 dark:border-neutral-600", text: "text-neutral-900 dark:text-neutral-100" },
64
- unknown: { bg: "bg-neutral-900/30", border: "border-neutral-500/50", text: "text-neutral-300" },
65
- };
66
- /** Suit color for standard cards */
67
- export function getSuitColor(enumKey) {
68
- if (enumKey.startsWith("H") || enumKey.startsWith("D"))
69
- return "text-red-500";
70
- return "text-neutral-900 dark:text-neutral-100";
71
- }