jaml-ui 0.6.0 → 0.7.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/README.md +74 -84
- package/dist/assets.js +13 -10
- package/dist/components/JamlIdeToolbar.js +2 -36
- package/dist/components/JamlIdeVisual.js +2 -2
- package/dist/hooks/loadMotelyWasm.d.ts +7 -0
- package/dist/hooks/loadMotelyWasm.js +16 -0
- package/dist/hooks/searchWorkerCode.d.ts +1 -0
- package/dist/hooks/searchWorkerCode.js +62 -0
- package/dist/hooks/useAnalyzer.d.ts +8 -0
- package/dist/hooks/useAnalyzer.js +72 -0
- package/dist/hooks/useSearch.d.ts +21 -0
- package/dist/hooks/useSearch.js +76 -0
- package/dist/ui/codeBlock.js +3 -2
- package/dist/ui/panel.d.ts +6 -17
- package/dist/ui/panel.js +12 -47
- package/dist/ui/showcase.js +4 -4
- package/dist/ui/sprites.d.ts +2 -2
- package/dist/ui/sprites.js +1 -1
- package/package.json +1 -3
- package/assets/8BitDeck.png +0 -0
- package/assets/BlindChips.png +0 -0
- package/assets/Boosters.png +0 -0
- package/assets/Editions.png +0 -0
- package/assets/Enhancers.png +0 -0
- package/assets/Jokers.png +0 -0
- package/assets/Tarots.png +0 -0
- package/assets/Vouchers.png +0 -0
- package/assets/fonts/m6x11plusplus.otf +0 -0
- package/assets/stickers.png +0 -0
- package/assets/tags.png +0 -0
package/README.md
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
# jaml-ui
|
|
2
2
|
|
|
3
|
-
React components and utilities for Balatro/JAML
|
|
4
|
-
|
|
5
|
-
## Package shape
|
|
6
|
-
|
|
7
|
-
- `jaml-ui`
|
|
8
|
-
- React/client entry for rendered components and hooks
|
|
9
|
-
- `jaml-ui/core`
|
|
10
|
-
- Pure asset helpers, sprite metadata, and decode utilities that do not depend on `motely-wasm`
|
|
11
|
-
- `jaml-ui/motely`
|
|
12
|
-
- Optional plain `motely-wasm` helpers for decoding Motely item enums
|
|
3
|
+
React components, UI tokens, sprites, and utilities for Balatro/JAML apps.
|
|
13
4
|
|
|
14
5
|
## Install
|
|
15
6
|
|
|
@@ -17,129 +8,128 @@ React components and utilities for Balatro/JAML. Pair with **`motely-wasm`** whe
|
|
|
17
8
|
npm install jaml-ui react react-dom
|
|
18
9
|
```
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
## Package exports
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
| Entry | Contents |
|
|
14
|
+
|-------|----------|
|
|
15
|
+
| `jaml-ui` | Game card components, JAML IDE, Analyzer Explorer, hooks |
|
|
16
|
+
| `jaml-ui/ui` | Jimbo design system — JimboPanel, JimboButton, JimboModal, tokens |
|
|
17
|
+
| `jaml-ui/core` | Pure asset helpers, sprite metadata, decode utilities (no React) |
|
|
18
|
+
| `jaml-ui/motely` | motely-wasm decode helpers (requires `motely-wasm` peer) |
|
|
19
|
+
| `jaml-ui/r3f` | 3D card component via React Three Fiber (requires r3f peers) |
|
|
25
20
|
|
|
26
21
|
## Quick start
|
|
27
22
|
|
|
28
23
|
```tsx
|
|
29
|
-
|
|
24
|
+
import { JamlGameCard, AnalyzerExplorer, JamlIde } from "jaml-ui";
|
|
25
|
+
import { JimboPanel, JimboButton } from "jaml-ui/ui";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Game card
|
|
30
29
|
|
|
30
|
+
```tsx
|
|
31
31
|
import { JamlGameCard } from "jaml-ui";
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
card={{
|
|
38
|
-
name: "Blueprint",
|
|
39
|
-
edition: "Foil",
|
|
40
|
-
isEternal: true,
|
|
41
|
-
scale: 1.5,
|
|
42
|
-
}}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
33
|
+
<JamlGameCard
|
|
34
|
+
type="joker"
|
|
35
|
+
card={{ name: "Blueprint", edition: "Foil", isEternal: true, scale: 1.5 }}
|
|
36
|
+
/>
|
|
46
37
|
```
|
|
47
38
|
|
|
48
|
-
|
|
39
|
+
### Jimbo UI (Balatro design system)
|
|
49
40
|
|
|
50
41
|
```tsx
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
import { JamlMapPreview } from "jaml-ui";
|
|
42
|
+
import { JimboPanel, JimboButton, JimboModal } from "jaml-ui/ui";
|
|
43
|
+
import { JimboColorOption } from "jaml-ui/ui";
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
<JimboPanel sway onBack={() => setOpen(false)}>
|
|
46
|
+
<JimboButton variant="primary" onClick={handleSearch}>Search</JimboButton>
|
|
47
|
+
</JimboPanel>
|
|
58
48
|
```
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
Available variants: `primary`, `secondary`, `danger`, `back`, `ghost`
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
"use client";
|
|
52
|
+
### JAML IDE
|
|
64
53
|
|
|
65
|
-
|
|
54
|
+
```tsx
|
|
66
55
|
import { JamlIde } from "jaml-ui";
|
|
67
56
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
results={[]}
|
|
76
|
-
/>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
57
|
+
<JamlIde
|
|
58
|
+
jaml={jaml}
|
|
59
|
+
onChange={setJaml}
|
|
60
|
+
searchResults={results}
|
|
61
|
+
onSearch={handleSearch}
|
|
62
|
+
isSearching={isSearching}
|
|
63
|
+
/>
|
|
79
64
|
```
|
|
80
65
|
|
|
81
|
-
|
|
66
|
+
### Analyzer Explorer
|
|
82
67
|
|
|
83
|
-
|
|
68
|
+
```tsx
|
|
69
|
+
import { AnalyzerExplorer } from "jaml-ui";
|
|
70
|
+
|
|
71
|
+
// antes: AnalyzerAnteView[] — stream from motely-wasm createSearchContext
|
|
72
|
+
<AnalyzerExplorer antes={antes} totalAntes={8} highlights={highlights} />
|
|
73
|
+
```
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
### JAML Map Preview
|
|
86
76
|
|
|
87
77
|
```tsx
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
import { setJamlAssetBaseUrl } from "jaml-ui";
|
|
78
|
+
import { JamlMapPreview } from "jaml-ui";
|
|
91
79
|
|
|
92
|
-
|
|
80
|
+
<JamlMapPreview jaml={jaml} />
|
|
93
81
|
```
|
|
94
82
|
|
|
95
|
-
|
|
83
|
+
## Asset handling
|
|
84
|
+
|
|
85
|
+
By default sprites resolve from the package `assets/` directory via `import.meta.url`.
|
|
86
|
+
|
|
87
|
+
Override at app startup:
|
|
96
88
|
|
|
97
89
|
```ts
|
|
98
|
-
import { clearJamlAssetBaseUrl } from "jaml-ui";
|
|
90
|
+
import { setJamlAssetBaseUrl, clearJamlAssetBaseUrl } from "jaml-ui";
|
|
99
91
|
|
|
100
|
-
|
|
92
|
+
setJamlAssetBaseUrl("/vendor/jaml-ui/"); // custom CDN
|
|
93
|
+
clearJamlAssetBaseUrl(); // back to default
|
|
101
94
|
```
|
|
102
95
|
|
|
103
96
|
## Core utilities
|
|
104
97
|
|
|
105
98
|
```ts
|
|
106
99
|
import { SPRITE_SHEETS, getSpriteData, resolveJamlAssetUrl } from "jaml-ui/core";
|
|
107
|
-
|
|
108
|
-
const jokerSheetUrl = SPRITE_SHEETS.jokers.src;
|
|
109
|
-
const blueprintSprite = getSpriteData("Blueprint");
|
|
110
|
-
const vouchersUrl = resolveJamlAssetUrl("vouchers");
|
|
111
100
|
```
|
|
112
101
|
|
|
113
|
-
## Motely helpers
|
|
102
|
+
## Motely decode helpers
|
|
114
103
|
|
|
115
104
|
```ts
|
|
116
|
-
"use client";
|
|
117
|
-
|
|
118
105
|
import { decodeMotelyItemName, motelyItemTypeName } from "jaml-ui/motely";
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 3D card (optional)
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
```bash
|
|
111
|
+
npm install three @react-three/fiber @react-three/drei @react-spring/three
|
|
122
112
|
```
|
|
123
113
|
|
|
124
|
-
|
|
114
|
+
```tsx
|
|
115
|
+
import { Card3D } from "jaml-ui/r3f";
|
|
125
116
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- If you are consuming `jaml-ui` from a local workspace package in a Next.js app, you may need:
|
|
117
|
+
<Card3D itemName="Blueprint" />
|
|
118
|
+
```
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
// next.config.ts
|
|
132
|
-
import type { NextConfig } from "next";
|
|
120
|
+
## Next.js
|
|
133
121
|
|
|
134
|
-
|
|
135
|
-
transpilePackages: ["jaml-ui"],
|
|
136
|
-
};
|
|
122
|
+
Import pure helpers from `jaml-ui/core` for server components. For local workspace installs add:
|
|
137
123
|
|
|
138
|
-
|
|
124
|
+
```ts
|
|
125
|
+
// next.config.ts
|
|
126
|
+
const nextConfig = { transpilePackages: ["jaml-ui"] };
|
|
139
127
|
```
|
|
140
128
|
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
`jaml-ui` is designed for browser/React consumers. The optional `jaml-ui/motely` entry targets plain `motely-wasm` and does not assume threaded WASM, SAB, or COEP setup.
|
|
129
|
+
## Peer dependencies
|
|
144
130
|
|
|
145
|
-
|
|
131
|
+
| Peer | Required for |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| `react`, `react-dom` | All components |
|
|
134
|
+
| `motely-wasm ^10 \|\| ^11 \|\| ^12` | `jaml-ui/motely`, `AnalyzerExplorer` data |
|
|
135
|
+
| `three`, `@react-three/fiber`, `@react-three/drei`, `@react-spring/three` | `jaml-ui/r3f` only |
|
package/dist/assets.js
CHANGED
|
@@ -11,17 +11,20 @@ export const JAML_ASSET_FILES = {
|
|
|
11
11
|
tags: "tags.png",
|
|
12
12
|
};
|
|
13
13
|
const assetKeyByFileName = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [fileName, key]));
|
|
14
|
+
// Keep in lockstep with package.json version. Upload assets to this path when publishing.
|
|
15
|
+
const JAML_UI_VERSION = "0.7.0";
|
|
16
|
+
const CDN_BASE = `https://cdn.seedfinder.app/jaml-ui/${JAML_UI_VERSION}/assets/`;
|
|
14
17
|
const defaultAssetUrls = {
|
|
15
|
-
deck:
|
|
16
|
-
blinds:
|
|
17
|
-
boosters:
|
|
18
|
-
editions:
|
|
19
|
-
enhancers:
|
|
20
|
-
jokers:
|
|
21
|
-
tarots:
|
|
22
|
-
vouchers:
|
|
23
|
-
stickers:
|
|
24
|
-
tags:
|
|
18
|
+
deck: `${CDN_BASE}${JAML_ASSET_FILES.deck}`,
|
|
19
|
+
blinds: `${CDN_BASE}${JAML_ASSET_FILES.blinds}`,
|
|
20
|
+
boosters: `${CDN_BASE}${JAML_ASSET_FILES.boosters}`,
|
|
21
|
+
editions: `${CDN_BASE}${JAML_ASSET_FILES.editions}`,
|
|
22
|
+
enhancers: `${CDN_BASE}${JAML_ASSET_FILES.enhancers}`,
|
|
23
|
+
jokers: `${CDN_BASE}${JAML_ASSET_FILES.jokers}`,
|
|
24
|
+
tarots: `${CDN_BASE}${JAML_ASSET_FILES.tarots}`,
|
|
25
|
+
vouchers: `${CDN_BASE}${JAML_ASSET_FILES.vouchers}`,
|
|
26
|
+
stickers: `${CDN_BASE}${JAML_ASSET_FILES.stickers}`,
|
|
27
|
+
tags: `${CDN_BASE}${JAML_ASSET_FILES.tags}`,
|
|
25
28
|
};
|
|
26
29
|
let customAssetBaseUrl = null;
|
|
27
30
|
function normalizeBaseUrl(baseUrl) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { JimboButton } from "../ui/panel.js";
|
|
3
4
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
4
5
|
const TABS = [
|
|
5
6
|
{ id: "visual", label: "Visual" },
|
|
@@ -16,40 +17,5 @@ export function JamlIdeToolbar({ mode, onModeChange, resultCount = 0, className
|
|
|
16
17
|
padding: "6px 10px",
|
|
17
18
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
18
19
|
background: JimboColorOption.DARKEST,
|
|
19
|
-
}, children: [_jsx("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: TABS.map((tab) => {
|
|
20
|
-
const selected = mode === tab.id;
|
|
21
|
-
return (_jsxs("button", { type: "button", onClick: () => onModeChange(tab.id), style: {
|
|
22
|
-
cursor: "pointer",
|
|
23
|
-
borderRadius: 6,
|
|
24
|
-
border: selected ? `1px solid ${JimboColorOption.GOLD}` : "1px solid transparent",
|
|
25
|
-
background: selected ? `${JimboColorOption.GOLD}22` : "transparent",
|
|
26
|
-
color: selected ? JimboColorOption.GOLD_TEXT : JimboColorOption.GREY,
|
|
27
|
-
padding: "5px 10px",
|
|
28
|
-
fontSize: 11,
|
|
29
|
-
fontWeight: 700,
|
|
30
|
-
fontFamily: "m6x11plus, monospace",
|
|
31
|
-
transition: "background 120ms, color 120ms",
|
|
32
|
-
}, children: [tab.label, tab.id === "results" && resultCount > 0 ? (_jsx("span", { style: {
|
|
33
|
-
marginLeft: 6,
|
|
34
|
-
borderRadius: 999,
|
|
35
|
-
background: `${JimboColorOption.GOLD}33`,
|
|
36
|
-
color: JimboColorOption.GOLD_TEXT,
|
|
37
|
-
padding: "1px 6px",
|
|
38
|
-
fontSize: 10,
|
|
39
|
-
}, children: resultCount })) : null] }, tab.id));
|
|
40
|
-
}) }), onSearch ? (_jsx("button", { type: "button", onClick: onSearch, style: {
|
|
41
|
-
cursor: "pointer",
|
|
42
|
-
borderRadius: 6,
|
|
43
|
-
border: isSearching
|
|
44
|
-
? `1px solid ${JimboColorOption.DARK_RED}`
|
|
45
|
-
: `1px solid ${JimboColorOption.GREEN}`,
|
|
46
|
-
background: isSearching
|
|
47
|
-
? `${JimboColorOption.RED}22`
|
|
48
|
-
: `${JimboColorOption.GREEN}22`,
|
|
49
|
-
color: isSearching ? JimboColorOption.RED : JimboColorOption.GREEN_TEXT,
|
|
50
|
-
padding: "5px 14px",
|
|
51
|
-
fontSize: 11,
|
|
52
|
-
fontWeight: 700,
|
|
53
|
-
fontFamily: "m6x11plus, monospace",
|
|
54
|
-
}, children: isSearching ? "Stop" : "Search" })) : null] }));
|
|
20
|
+
}, children: [_jsx("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: TABS.map((tab) => (_jsxs(JimboButton, { tone: mode === tab.id ? "gold" : "grey", size: "xs", onClick: () => onModeChange(tab.id), children: [tab.label, tab.id === "results" && resultCount > 0 ? (_jsx("span", { style: { marginLeft: 6, borderRadius: 999, background: "rgba(228,182,67,0.2)", color: JimboColorOption.GOLD_TEXT, padding: "1px 6px", fontSize: 10 }, children: resultCount })) : null] }, tab.id))) }), onSearch ? (_jsx(JimboButton, { tone: isSearching ? "red" : "blue", size: "xs", onClick: onSearch, children: isSearching ? "Stop" : "Search" })) : null] }));
|
|
55
21
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
|
-
import {
|
|
5
|
+
import { JimboSprite } from "../ui/sprites.js";
|
|
6
6
|
const ZONE_META = {
|
|
7
7
|
must: { label: "MUST", color: JimboColorOption.BLUE },
|
|
8
8
|
should: { label: "SHOULD", color: JimboColorOption.RED },
|
|
@@ -25,7 +25,7 @@ function ClauseSprite({ clause, size = 26 }) {
|
|
|
25
25
|
const sheet = clauseSpriteSheet(clause.type);
|
|
26
26
|
if (!sheet)
|
|
27
27
|
return null;
|
|
28
|
-
return _jsx(
|
|
28
|
+
return _jsx(JimboSprite, { name: clause.value, sheet: sheet, width: size });
|
|
29
29
|
}
|
|
30
30
|
function DragClausePill({ clause, zone, onDragStart, }) {
|
|
31
31
|
const z = ZONE_META[zone];
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SEARCH_WORKER_CODE = "\nlet MotelyWasm = null;\nlet MotelyWasmEvents = null;\nlet activeSearch = null;\n\nself.addEventListener('message', async function(e) {\n const msg = e.data;\n\n if (msg.type === 'init') {\n try {\n const mod = await import(msg.url);\n await mod.default.boot();\n MotelyWasm = mod.MotelyWasm;\n MotelyWasmEvents = mod.MotelyWasmEvents;\n self.postMessage({ type: 'ready' });\n } catch (err) {\n self.postMessage({ type: 'error', message: String(err) });\n }\n return;\n }\n\n if (msg.type === 'start') {\n if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }\n const validation = MotelyWasm.validateJaml(msg.jaml);\n if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }\n\n let rId, pId, cId;\n function cleanup() {\n MotelyWasmEvents.onResult.unsubscribeById(rId);\n MotelyWasmEvents.onProgress.unsubscribeById(pId);\n MotelyWasmEvents.onComplete.unsubscribeById(cId);\n activeSearch = null;\n }\n\n rId = MotelyWasmEvents.onResult.subscribe(function(seed, score) {\n self.postMessage({ type: 'result', seed, score });\n });\n pId = MotelyWasmEvents.onProgress.subscribe(function(searched, matching) {\n self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });\n });\n cId = MotelyWasmEvents.onComplete.subscribe(function(status, searched, matched) {\n cleanup();\n self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });\n });\n\n try {\n activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);\n } catch (err) {\n cleanup();\n self.postMessage({ type: 'error', message: String(err) });\n }\n return;\n }\n\n if (msg.type === 'stop') {\n if (activeSearch) { activeSearch.cancel(); activeSearch = null; }\n self.postMessage({ type: 'cancelled' });\n }\n});\n";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Worker code as an inline string — created as a Blob URL at runtime.
|
|
2
|
+
// This avoids bundler/import.meta.url issues when shipped as an npm package.
|
|
3
|
+
export const SEARCH_WORKER_CODE = `
|
|
4
|
+
let MotelyWasm = null;
|
|
5
|
+
let MotelyWasmEvents = null;
|
|
6
|
+
let activeSearch = null;
|
|
7
|
+
|
|
8
|
+
self.addEventListener('message', async function(e) {
|
|
9
|
+
const msg = e.data;
|
|
10
|
+
|
|
11
|
+
if (msg.type === 'init') {
|
|
12
|
+
try {
|
|
13
|
+
const mod = await import(msg.url);
|
|
14
|
+
await mod.default.boot();
|
|
15
|
+
MotelyWasm = mod.MotelyWasm;
|
|
16
|
+
MotelyWasmEvents = mod.MotelyWasmEvents;
|
|
17
|
+
self.postMessage({ type: 'ready' });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
self.postMessage({ type: 'error', message: String(err) });
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (msg.type === 'start') {
|
|
25
|
+
if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
|
|
26
|
+
const validation = MotelyWasm.validateJaml(msg.jaml);
|
|
27
|
+
if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }
|
|
28
|
+
|
|
29
|
+
let rId, pId, cId;
|
|
30
|
+
function cleanup() {
|
|
31
|
+
MotelyWasmEvents.onResult.unsubscribeById(rId);
|
|
32
|
+
MotelyWasmEvents.onProgress.unsubscribeById(pId);
|
|
33
|
+
MotelyWasmEvents.onComplete.unsubscribeById(cId);
|
|
34
|
+
activeSearch = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rId = MotelyWasmEvents.onResult.subscribe(function(seed, score) {
|
|
38
|
+
self.postMessage({ type: 'result', seed, score });
|
|
39
|
+
});
|
|
40
|
+
pId = MotelyWasmEvents.onProgress.subscribe(function(searched, matching) {
|
|
41
|
+
self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });
|
|
42
|
+
});
|
|
43
|
+
cId = MotelyWasmEvents.onComplete.subscribe(function(status, searched, matched) {
|
|
44
|
+
cleanup();
|
|
45
|
+
self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
cleanup();
|
|
52
|
+
self.postMessage({ type: 'error', message: String(err) });
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (msg.type === 'stop') {
|
|
58
|
+
if (activeSearch) { activeSearch.cancel(); activeSearch = null; }
|
|
59
|
+
self.postMessage({ type: 'cancelled' });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnalyzerAnteView } from "../components/AnalyzerExplorer.js";
|
|
2
|
+
export type AnalyzerStatus = "idle" | "running" | "done" | "error";
|
|
3
|
+
export declare function useAnalyzer(motelyWasmUrl: string): {
|
|
4
|
+
antes: AnalyzerAnteView[];
|
|
5
|
+
status: AnalyzerStatus;
|
|
6
|
+
error: string | null;
|
|
7
|
+
analyze: (seed: string, deck: string, stake: string, jaml?: string) => Promise<void>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useCallback } from "react";
|
|
3
|
+
import { loadMotelyWasm } from "./loadMotelyWasm.js";
|
|
4
|
+
import { extractVisualJamlItems } from "../utils/jamlMapPreview.js";
|
|
5
|
+
import { motelyItemDisplayNameFromValue } from "../motelyDisplay.js";
|
|
6
|
+
export function useAnalyzer(motelyWasmUrl) {
|
|
7
|
+
const [antes, setAntes] = useState([]);
|
|
8
|
+
const [status, setStatus] = useState("idle");
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const analyze = useCallback(async (seed, deck, stake, jaml) => {
|
|
11
|
+
setAntes([]);
|
|
12
|
+
setStatus("running");
|
|
13
|
+
setError(null);
|
|
14
|
+
try {
|
|
15
|
+
const { MotelyWasm, Motely } = await loadMotelyWasm(motelyWasmUrl);
|
|
16
|
+
const deckEnum = Motely.MotelyDeck[deck] ?? Motely.MotelyDeck.Red;
|
|
17
|
+
const stakeEnum = Motely.MotelyStake[stake] ?? Motely.MotelyStake.White;
|
|
18
|
+
const desiredNames = new Set();
|
|
19
|
+
if (jaml) {
|
|
20
|
+
const groups = extractVisualJamlItems(jaml);
|
|
21
|
+
for (const item of [...groups.must, ...groups.should]) {
|
|
22
|
+
desiredNames.add(item.value.toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const ctx = MotelyWasm.createSearchContext(seed, deckEnum, stakeEnum);
|
|
26
|
+
const bossStream = ctx.createBossStream();
|
|
27
|
+
let runState = { voucherBitfield: 0, bossBitfield: 0 };
|
|
28
|
+
const results = [];
|
|
29
|
+
for (let ante = 1; ante <= 8; ante++) {
|
|
30
|
+
const bossResult = ctx.getNextBossForAnte(bossStream, ante, runState);
|
|
31
|
+
const bossName = Motely.MotelyBossBlind[bossResult.boss] ?? `Unknown(${bossResult.boss})`;
|
|
32
|
+
runState = bossResult.runState;
|
|
33
|
+
const voucherResult = ctx.getAnteFirstVoucher(ante, runState);
|
|
34
|
+
const voucherName = Motely.MotelyVoucher[voucherResult.voucher] ?? `Unknown(${voucherResult.voucher})`;
|
|
35
|
+
runState = voucherResult.runState;
|
|
36
|
+
const tagStream = ctx.createTagStream(ante);
|
|
37
|
+
const tag1 = ctx.getNextTag(tagStream);
|
|
38
|
+
const tag2 = ctx.getNextTag(tagStream);
|
|
39
|
+
const packStream = ctx.createBoosterPackStream(ante);
|
|
40
|
+
const packs = [];
|
|
41
|
+
for (let p = 0; p < 2; p++) {
|
|
42
|
+
const packResult = ctx.getNextBoosterPack(packStream);
|
|
43
|
+
packs.push(Motely.MotelyBoosterPack[packResult.pack] ?? `Unknown(${packResult.pack})`);
|
|
44
|
+
}
|
|
45
|
+
const shopStream = ctx.createShopItemStream(ante, runState, Motely.MotelyShopStreamFlags.Default, Motely.MotelyJokerStreamFlags.Default);
|
|
46
|
+
const shop = [];
|
|
47
|
+
for (let i = 0; i < 4; i++) {
|
|
48
|
+
const itemResult = ctx.getNextShopItem(shopStream);
|
|
49
|
+
const name = motelyItemDisplayNameFromValue(itemResult.item.value);
|
|
50
|
+
const desired = desiredNames.size > 0 && desiredNames.has(name.toLowerCase());
|
|
51
|
+
shop.push({ id: `${ante}-shop-${i}`, name, value: itemResult.item.value, desired });
|
|
52
|
+
}
|
|
53
|
+
results.push({
|
|
54
|
+
ante,
|
|
55
|
+
boss: bossName,
|
|
56
|
+
voucher: voucherName,
|
|
57
|
+
smallBlindTag: Motely.MotelyTag[tag1.tag] ?? `Unknown(${tag1.tag})`,
|
|
58
|
+
bigBlindTag: Motely.MotelyTag[tag2.tag] ?? `Unknown(${tag2.tag})`,
|
|
59
|
+
packs,
|
|
60
|
+
shop,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
setAntes(results);
|
|
64
|
+
setStatus("done");
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
68
|
+
setStatus("error");
|
|
69
|
+
}
|
|
70
|
+
}, [motelyWasmUrl]);
|
|
71
|
+
return { antes, status, error, analyze };
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface SearchResult {
|
|
2
|
+
seed: string;
|
|
3
|
+
score: number;
|
|
4
|
+
}
|
|
5
|
+
export type SearchStatus = "idle" | "booting" | "running" | "completed" | "cancelled" | "error";
|
|
6
|
+
export interface UseSearchState {
|
|
7
|
+
results: SearchResult[];
|
|
8
|
+
totalSearched: bigint;
|
|
9
|
+
matchingSeeds: bigint;
|
|
10
|
+
status: SearchStatus;
|
|
11
|
+
error: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function useSearch(motelyWasmUrl: string): {
|
|
14
|
+
start: (jaml: string, count: number) => void;
|
|
15
|
+
cancel: () => void;
|
|
16
|
+
results: SearchResult[];
|
|
17
|
+
totalSearched: bigint;
|
|
18
|
+
matchingSeeds: bigint;
|
|
19
|
+
status: SearchStatus;
|
|
20
|
+
error: string | null;
|
|
21
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useCallback, useRef, useEffect } from "react";
|
|
3
|
+
import { SEARCH_WORKER_CODE } from "./searchWorkerCode.js";
|
|
4
|
+
function createWorker(motelyWasmUrl) {
|
|
5
|
+
const blob = new Blob([SEARCH_WORKER_CODE], { type: "text/javascript" });
|
|
6
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
7
|
+
const worker = new Worker(blobUrl);
|
|
8
|
+
worker.postMessage({ type: "init", url: motelyWasmUrl });
|
|
9
|
+
return worker;
|
|
10
|
+
}
|
|
11
|
+
export function useSearch(motelyWasmUrl) {
|
|
12
|
+
const [state, setState] = useState({
|
|
13
|
+
results: [],
|
|
14
|
+
totalSearched: 0n,
|
|
15
|
+
matchingSeeds: 0n,
|
|
16
|
+
status: "idle",
|
|
17
|
+
error: null,
|
|
18
|
+
});
|
|
19
|
+
const workerRef = useRef(null);
|
|
20
|
+
const readyRef = useRef(false);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setState((s) => ({ ...s, status: "booting" }));
|
|
23
|
+
const worker = createWorker(motelyWasmUrl);
|
|
24
|
+
workerRef.current = worker;
|
|
25
|
+
readyRef.current = false;
|
|
26
|
+
worker.onmessage = (e) => {
|
|
27
|
+
const msg = e.data;
|
|
28
|
+
if (msg.type === "ready") {
|
|
29
|
+
readyRef.current = true;
|
|
30
|
+
setState((s) => s.status === "booting" ? { ...s, status: "idle" } : s);
|
|
31
|
+
}
|
|
32
|
+
else if (msg.type === "result") {
|
|
33
|
+
setState((s) => ({ ...s, results: [...s.results, { seed: msg.seed, score: msg.score }] }));
|
|
34
|
+
}
|
|
35
|
+
else if (msg.type === "progress") {
|
|
36
|
+
setState((s) => ({ ...s, totalSearched: BigInt(msg.searched), matchingSeeds: BigInt(msg.matching) }));
|
|
37
|
+
}
|
|
38
|
+
else if (msg.type === "complete") {
|
|
39
|
+
setState((s) => ({ ...s, status: msg.status === "Completed" ? "completed" : "error", error: msg.status !== "Completed" ? msg.status : null, totalSearched: BigInt(msg.searched), matchingSeeds: BigInt(msg.matched) }));
|
|
40
|
+
}
|
|
41
|
+
else if (msg.type === "cancelled") {
|
|
42
|
+
setState((s) => ({ ...s, status: "cancelled" }));
|
|
43
|
+
}
|
|
44
|
+
else if (msg.type === "error") {
|
|
45
|
+
setState((s) => ({ ...s, status: "error", error: msg.message }));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return () => {
|
|
49
|
+
worker.terminate();
|
|
50
|
+
workerRef.current = null;
|
|
51
|
+
};
|
|
52
|
+
}, [motelyWasmUrl]);
|
|
53
|
+
const start = useCallback((jaml, count) => {
|
|
54
|
+
const worker = workerRef.current;
|
|
55
|
+
if (!worker)
|
|
56
|
+
return;
|
|
57
|
+
setState({ results: [], totalSearched: 0n, matchingSeeds: 0n, status: "running", error: null });
|
|
58
|
+
if (readyRef.current) {
|
|
59
|
+
worker.postMessage({ type: "start", jaml, count });
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const orig = worker.onmessage;
|
|
63
|
+
worker.onmessage = (e) => {
|
|
64
|
+
orig?.call(worker, e);
|
|
65
|
+
if (e.data.type === "ready") {
|
|
66
|
+
worker.onmessage = orig;
|
|
67
|
+
worker.postMessage({ type: "start", jaml, count });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}, []);
|
|
72
|
+
const cancel = useCallback(() => {
|
|
73
|
+
workerRef.current?.postMessage({ type: "stop" });
|
|
74
|
+
}, []);
|
|
75
|
+
return { ...state, start, cancel };
|
|
76
|
+
}
|
package/dist/ui/codeBlock.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { FiCopy, FiCheck } from 'react-icons/fi';
|
|
5
4
|
import { JimboColorOption } from './tokens.js';
|
|
6
5
|
export function JimboCodeBlock({ code, language, filename, className = '' }) {
|
|
7
6
|
const [copied, setCopied] = useState(false);
|
|
@@ -10,5 +9,7 @@ export function JimboCodeBlock({ code, language, filename, className = '' }) {
|
|
|
10
9
|
setCopied(true);
|
|
11
10
|
setTimeout(() => setCopied(false), 2000);
|
|
12
11
|
};
|
|
13
|
-
return (_jsxs("div", { className: 'rounded-xl overflow-hidden flex flex-col border-2 ' + className, style: { backgroundColor: JimboColorOption.DARKEST, borderColor: JimboColorOption.PANEL_EDGE, boxShadow: '0 3px 0 0 rgba(0,0,0,0.5)' }, children: [_jsxs("div", { style: { padding: '0.5rem 1rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: `1px solid ${JimboColorOption.INNER_BORDER}` }, children: [_jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [filename && _jsx("span", { style: { fontSize: 10, textTransform: 'uppercase', opacity: 0.6 }, children: filename }), language && _jsx("span", { style: { fontSize: 9, padding: '1px 6px', borderRadius: 3, background: 'rgba(0,0,0,0.4)', color: '#60a5fa', textTransform: 'uppercase' }, children: language })] }), _jsx("button", { onClick: copy, title: "Copy", style: { padding: 4, background: 'none', border: 'none', cursor: 'pointer', color: copied ? '#4ade80' : 'rgba(255,255,255,0.5)', display: 'flex' }, children: copied
|
|
12
|
+
return (_jsxs("div", { className: 'rounded-xl overflow-hidden flex flex-col border-2 ' + className, style: { backgroundColor: JimboColorOption.DARKEST, borderColor: JimboColorOption.PANEL_EDGE, boxShadow: '0 3px 0 0 rgba(0,0,0,0.5)' }, children: [_jsxs("div", { style: { padding: '0.5rem 1rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: `1px solid ${JimboColorOption.INNER_BORDER}` }, children: [_jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center' }, children: [filename && _jsx("span", { style: { fontSize: 10, textTransform: 'uppercase', opacity: 0.6 }, children: filename }), language && _jsx("span", { style: { fontSize: 9, padding: '1px 6px', borderRadius: 3, background: 'rgba(0,0,0,0.4)', color: '#60a5fa', textTransform: 'uppercase' }, children: language })] }), _jsx("button", { onClick: copy, title: "Copy", style: { padding: 4, background: 'none', border: 'none', cursor: 'pointer', color: copied ? '#4ade80' : 'rgba(255,255,255,0.5)', display: 'flex' }, children: copied
|
|
13
|
+
? _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })
|
|
14
|
+
: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }) })] }), _jsx("pre", { style: { padding: '1rem', overflowX: 'auto', fontFamily: 'monospace', fontSize: '0.875rem', lineHeight: 1.6, color: '#f6f0d5', margin: 0 }, children: _jsx("code", { children: code }) })] }));
|
|
14
15
|
}
|
package/dist/ui/panel.d.ts
CHANGED
|
@@ -1,36 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type ButtonVariant } from './tokens.js';
|
|
3
2
|
export interface JimboPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
3
|
sway?: boolean;
|
|
5
4
|
onBack?: () => void;
|
|
6
|
-
backLabel?: string;
|
|
7
5
|
hideBack?: boolean;
|
|
8
6
|
}
|
|
9
|
-
export declare const JimboPanel: React.MemoExoticComponent<({ children, className, sway, onBack,
|
|
7
|
+
export declare const JimboPanel: React.MemoExoticComponent<({ children, className, sway, onBack, hideBack, style, ...props }: JimboPanelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
10
8
|
export interface JimboInnerPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
11
9
|
}
|
|
12
10
|
export declare const JimboInnerPanel: React.MemoExoticComponent<({ children, className, style, ...props }: JimboInnerPanelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
13
|
-
export
|
|
14
|
-
|
|
11
|
+
export type JimboTone = 'orange' | 'red' | 'blue' | 'green' | 'gold' | 'grey';
|
|
12
|
+
export interface JimboButtonProps {
|
|
13
|
+
tone?: JimboTone;
|
|
15
14
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
16
15
|
fullWidth?: boolean;
|
|
17
|
-
}
|
|
18
|
-
export declare function JimboButton({ children, variant, size, fullWidth, className, style, disabled, ...props }: JimboButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
-
export declare function JimboBackButton({ label, ...props }: Omit<JimboButtonProps, 'variant' | 'children'> & {
|
|
20
|
-
label?: string;
|
|
21
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
-
export type BalTone = 'orange' | 'red' | 'blue' | 'green' | 'gold' | 'grey';
|
|
23
|
-
export interface BalButtonProps {
|
|
24
|
-
tone?: BalTone;
|
|
25
|
-
size?: 'sm' | 'md' | 'lg';
|
|
26
|
-
fullWidth?: boolean;
|
|
27
16
|
disabled?: boolean;
|
|
28
17
|
onClick?: () => void;
|
|
29
18
|
style?: React.CSSProperties;
|
|
30
19
|
children?: React.ReactNode;
|
|
31
20
|
}
|
|
32
|
-
export declare function
|
|
33
|
-
export declare function
|
|
21
|
+
export declare function JimboButton({ tone, size, fullWidth, disabled, onClick, style, children, }: JimboButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function JimboBackButton({ onClick }: {
|
|
34
23
|
onClick?: () => void;
|
|
35
24
|
}): import("react/jsx-runtime").JSX.Element;
|
|
36
25
|
export interface JimboModalProps {
|
package/dist/ui/panel.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useRef, memo } from 'react';
|
|
4
4
|
import { JimboColorOption, JIMBO_ANIMATIONS } from './tokens.js';
|
|
5
|
-
export const JimboPanel = memo(({ children, className = '', sway = false, onBack,
|
|
5
|
+
export const JimboPanel = memo(({ children, className = '', sway = false, onBack, hideBack = false, style, ...props }) => {
|
|
6
6
|
const panelRef = useRef(null);
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
if (!sway || !panelRef.current)
|
|
@@ -23,51 +23,16 @@ export const JimboPanel = memo(({ children, className = '', sway = false, onBack
|
|
|
23
23
|
border: `3px solid ${JimboColorOption.BORDER_SILVER}`,
|
|
24
24
|
boxShadow: `0 3px 0 0 ${JimboColorOption.BORDER_SOUTH}`,
|
|
25
25
|
...style,
|
|
26
|
-
}, ...props, children: [_jsx("div", { className: "flex-1 overflow-auto", children: children }), onBack && !hideBack && (_jsx("div", { className: "mt-4 pt-2 shrink-0", children: _jsx(JimboBackButton, { onClick: onBack
|
|
26
|
+
}, ...props, children: [_jsx("div", { className: "flex-1 overflow-auto", children: children }), onBack && !hideBack && (_jsx("div", { className: "mt-4 pt-2 shrink-0", children: _jsx(JimboBackButton, { onClick: onBack }) }))] }));
|
|
27
27
|
});
|
|
28
28
|
JimboPanel.displayName = 'JimboPanel';
|
|
29
29
|
export const JimboInnerPanel = memo(({ children, className = '', style, ...props }) => (_jsx("div", { className: 'rounded-lg p-3 ' + className, style: { backgroundColor: JimboColorOption.INNER_BORDER, border: `2px solid ${JimboColorOption.PANEL_EDGE}`, ...style }, ...props, children: children })));
|
|
30
30
|
JimboInnerPanel.displayName = 'JimboInnerPanel';
|
|
31
|
-
// ───
|
|
32
|
-
|
|
33
|
-
primary: { bg: JimboColorOption.RED, hover: JimboColorOption.DARK_RED, text: '#fff' },
|
|
34
|
-
secondary: { bg: JimboColorOption.BLUE, hover: JimboColorOption.DARK_BLUE, text: '#fff' },
|
|
35
|
-
danger: { bg: JimboColorOption.RED, hover: JimboColorOption.DARK_RED, text: '#fff' },
|
|
36
|
-
back: { bg: JimboColorOption.ORANGE, hover: JimboColorOption.DARK_ORANGE, text: '#fff' },
|
|
37
|
-
ghost: { bg: 'transparent', hover: 'rgba(255,255,255,0.1)', text: '#fff' },
|
|
38
|
-
};
|
|
39
|
-
export function JimboButton({ children, variant = 'primary', size = 'md', fullWidth = false, className = '', style, disabled, ...props }) {
|
|
40
|
-
const [hovered, setHovered] = useState(false);
|
|
41
|
-
const [pressed, setPressed] = useState(false);
|
|
42
|
-
const c = VARIANT_COLORS[variant];
|
|
43
|
-
const pad = { xs: '0.2rem 0.5rem', sm: '0.25rem 0.75rem', md: '0.375rem 1rem', lg: '0.5rem 1.5rem' }[size];
|
|
44
|
-
return (_jsx("button", { disabled: disabled, onMouseEnter: () => { if (!disabled)
|
|
45
|
-
setHovered(true); }, onMouseLeave: () => { setHovered(false); setPressed(false); }, onMouseDown: () => { if (!disabled)
|
|
46
|
-
setPressed(true); }, onMouseUp: () => setPressed(false), className: className, style: {
|
|
47
|
-
fontFamily: 'm6x11plus, monospace',
|
|
48
|
-
backgroundColor: hovered ? c.hover : c.bg,
|
|
49
|
-
color: c.text,
|
|
50
|
-
padding: pad,
|
|
51
|
-
borderRadius: '0.5rem',
|
|
52
|
-
border: 'none',
|
|
53
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
54
|
-
width: fullWidth ? '100%' : undefined,
|
|
55
|
-
opacity: disabled ? 0.5 : 1,
|
|
56
|
-
transform: pressed ? 'translateY(3px)' : 'none',
|
|
57
|
-
boxShadow: pressed ? 'none' : '0 3px 0 0 rgba(0,0,0,0.5)',
|
|
58
|
-
textShadow: '1px 1px 0 rgba(0,0,0,0.8)',
|
|
59
|
-
userSelect: 'none',
|
|
60
|
-
...style,
|
|
61
|
-
}, ...props, children: children }));
|
|
62
|
-
}
|
|
63
|
-
export function JimboBackButton({ label = 'Back', ...props }) {
|
|
64
|
-
return _jsx(JimboButton, { variant: "back", size: "sm", fullWidth: true, ...props, children: label });
|
|
65
|
-
}
|
|
66
|
-
// ─── BalButton ────────────────────────────────────────────────────────────────
|
|
67
|
-
// Canonical Balatro-style flat 2D button.
|
|
31
|
+
// ─── JimboButton ──────────────────────────────────────────────────────────────
|
|
32
|
+
// Canonical flat 2D Balatro-style button.
|
|
68
33
|
// Two-layer: separate shadow div (3px south + 1px east) that disappears on press.
|
|
69
34
|
// Press translates the face onto the shadow. No gradients, no hover color change.
|
|
70
|
-
const
|
|
35
|
+
const JIMBO_TONE_PAIRS = {
|
|
71
36
|
orange: [JimboColorOption.ORANGE, JimboColorOption.DARK_ORANGE],
|
|
72
37
|
red: [JimboColorOption.RED, JimboColorOption.DARK_RED],
|
|
73
38
|
blue: [JimboColorOption.BLUE, JimboColorOption.DARK_BLUE],
|
|
@@ -75,11 +40,11 @@ const BAL_PAIRS = {
|
|
|
75
40
|
gold: [JimboColorOption.GOLD, '#8a6a1e'],
|
|
76
41
|
grey: [JimboColorOption.DARK_GREY, JimboColorOption.DARKEST],
|
|
77
42
|
};
|
|
78
|
-
export function
|
|
43
|
+
export function JimboButton({ tone = 'orange', size = 'md', fullWidth = false, disabled = false, onClick, style, children, }) {
|
|
79
44
|
const [pressed, setPressed] = useState(false);
|
|
80
|
-
const [fg, sh] =
|
|
81
|
-
const pad = size === 'sm' ? '4px 10px' : size === 'lg' ? '14px 18px' : '9px 14px';
|
|
82
|
-
const fs = size === 'sm' ? 12 : size === 'lg' ? 18 : 14;
|
|
45
|
+
const [fg, sh] = JIMBO_TONE_PAIRS[tone] ?? JIMBO_TONE_PAIRS.orange;
|
|
46
|
+
const pad = size === 'xs' ? '2px 8px' : size === 'sm' ? '4px 10px' : size === 'lg' ? '14px 18px' : '9px 14px';
|
|
47
|
+
const fs = size === 'xs' ? 10 : size === 'sm' ? 12 : size === 'lg' ? 18 : 14;
|
|
83
48
|
return (_jsxs("div", { onMouseDown: () => { if (!disabled)
|
|
84
49
|
setPressed(true); }, onMouseUp: () => setPressed(false), onMouseLeave: () => setPressed(false), onTouchStart: () => { if (!disabled)
|
|
85
50
|
setPressed(true); }, onTouchEnd: () => setPressed(false), onClick: () => { if (!disabled)
|
|
@@ -93,8 +58,8 @@ export function BalButton({ tone = 'orange', size = 'md', fullWidth = false, dis
|
|
|
93
58
|
textTransform: 'uppercase', lineHeight: 1.1,
|
|
94
59
|
}, children: children })] }));
|
|
95
60
|
}
|
|
96
|
-
export function
|
|
97
|
-
return (_jsx("div", { style: { display: 'flex', justifyContent: 'center', width: '100%', padding: '8px 10px 10px' }, children: _jsx(
|
|
61
|
+
export function JimboBackButton({ onClick }) {
|
|
62
|
+
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" }) }));
|
|
98
63
|
}
|
|
99
64
|
export function JimboModal({ children, open, onClose, title, className }) {
|
|
100
65
|
const [visible, setVisible] = useState(open);
|
|
@@ -112,5 +77,5 @@ export function JimboModal({ children, open, onClose, title, className }) {
|
|
|
112
77
|
}, [open]);
|
|
113
78
|
if (!visible)
|
|
114
79
|
return null;
|
|
115
|
-
return (_jsx("div", { style: { position: 'fixed', inset: 0, zIndex: 50, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem', background: 'rgba(0,0,0,0.7)', opacity, transition: `opacity ${JIMBO_ANIMATIONS.MENU_SINK_DURATION}ms ease` }, onClick: onClose, children: _jsxs(JimboPanel, { sway: true, onBack: onClose,
|
|
80
|
+
return (_jsx("div", { style: { position: 'fixed', inset: 0, zIndex: 50, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem', background: 'rgba(0,0,0,0.7)', opacity, transition: `opacity ${JIMBO_ANIMATIONS.MENU_SINK_DURATION}ms ease` }, onClick: onClose, children: _jsxs(JimboPanel, { sway: true, onBack: onClose, className: 'w-full flex flex-col max-h-[90vh] ' + (className ?? 'max-w-lg'), onClick: (e) => e.stopPropagation(), children: [title && _jsx("h2", { style: { fontFamily: 'm6x11plus, monospace', color: '#fff', textAlign: 'center', margin: '0 0 1rem', fontSize: '1.25rem' }, children: title }), children] }) }));
|
|
116
81
|
}
|
package/dist/ui/showcase.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { JimboColorOption } from './tokens.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { JimboButton } from './panel.js';
|
|
5
|
+
import { JimboSprite } from './sprites.js';
|
|
6
6
|
const TONE_COLOR = {
|
|
7
7
|
blue: JimboColorOption.BLUE,
|
|
8
8
|
red: JimboColorOption.RED,
|
|
@@ -34,7 +34,7 @@ export function Showcase({ hotFilters = [], recentFinds = [], stats = DEFAULT_ST
|
|
|
34
34
|
background: C.DARK_GREY, borderRadius: 6, padding: 10,
|
|
35
35
|
border: `2px solid ${tColor}`, boxShadow: `0 2px 0 ${C.BLACK}`,
|
|
36
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(
|
|
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
38
|
fontSize: 13, color: C.WHITE, letterSpacing: 1,
|
|
39
39
|
textShadow: '1px 1px 0 rgba(0,0,0,.8)',
|
|
40
40
|
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
@@ -50,5 +50,5 @@ export function Showcase({ hotFilters = [], recentFinds = [], stats = DEFAULT_ST
|
|
|
50
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
51
|
padding: '8px 10px 10px', borderTop: `2px solid ${C.BLACK}`, background: C.DARK_GREY,
|
|
52
52
|
display: 'flex', flexDirection: 'column', gap: 6,
|
|
53
|
-
}, children: [_jsx(
|
|
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" })] })] }));
|
|
54
54
|
}
|
package/dist/ui/sprites.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type SpriteSheetType } from '../sprites/spriteMapper.js';
|
|
3
|
-
export interface
|
|
3
|
+
export interface JimboSpriteProps {
|
|
4
4
|
name: string;
|
|
5
5
|
sheet?: SpriteSheetType;
|
|
6
6
|
width?: number;
|
|
7
7
|
height?: number;
|
|
8
8
|
style?: React.CSSProperties;
|
|
9
9
|
}
|
|
10
|
-
export declare function
|
|
10
|
+
export declare function JimboSprite({ name, sheet, width, height, style }: JimboSpriteProps): import("react/jsx-runtime").JSX.Element | null;
|
package/dist/ui/sprites.js
CHANGED
|
@@ -12,7 +12,7 @@ const SHEET_META = {
|
|
|
12
12
|
Enhancers: { cols: 7, rows: 5, assetKey: 'enhancers' },
|
|
13
13
|
Editions: { cols: 5, rows: 1, assetKey: 'editions' },
|
|
14
14
|
};
|
|
15
|
-
export function
|
|
15
|
+
export function JimboSprite({ name, sheet, width = 40, height, style }) {
|
|
16
16
|
const sprite = getSpriteData(name);
|
|
17
17
|
const resolvedSheet = sheet ?? sprite?.type ?? 'Jokers';
|
|
18
18
|
const meta = SHEET_META[resolvedSheet];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaml-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|
|
@@ -26,13 +26,11 @@
|
|
|
26
26
|
"types": "./dist/r3f.d.ts",
|
|
27
27
|
"import": "./dist/r3f.js"
|
|
28
28
|
},
|
|
29
|
-
"./assets/*": "./assets/*",
|
|
30
29
|
"./package.json": "./package.json"
|
|
31
30
|
},
|
|
32
31
|
"sideEffects": false,
|
|
33
32
|
"files": [
|
|
34
33
|
"dist",
|
|
35
|
-
"assets",
|
|
36
34
|
"README.md",
|
|
37
35
|
"LICENSE"
|
|
38
36
|
],
|
package/assets/8BitDeck.png
DELETED
|
Binary file
|
package/assets/BlindChips.png
DELETED
|
Binary file
|
package/assets/Boosters.png
DELETED
|
Binary file
|
package/assets/Editions.png
DELETED
|
Binary file
|
package/assets/Enhancers.png
DELETED
|
Binary file
|
package/assets/Jokers.png
DELETED
|
Binary file
|
package/assets/Tarots.png
DELETED
|
Binary file
|
package/assets/Vouchers.png
DELETED
|
Binary file
|
|
Binary file
|
package/assets/stickers.png
DELETED
|
Binary file
|
package/assets/tags.png
DELETED
|
Binary file
|