jaml-ui 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +9 -11
- package/dist/assets.d.ts +6 -0
- package/dist/assets.js +9 -0
- package/dist/components/AnalyzerExplorer.d.ts +4 -1
- package/dist/components/AnalyzerExplorer.js +14 -48
- package/dist/components/GameCard.js +8 -7
- package/dist/components/JamlAestheticSelector.d.ts +4 -0
- package/dist/components/JamlAestheticSelector.js +6 -19
- package/dist/components/JamlAnalyzerFullscreen.d.ts +7 -1
- package/dist/components/JamlAnalyzerFullscreen.js +18 -47
- package/dist/components/JamlIde.js +12 -24
- package/dist/components/JamlIdeVisual.js +3 -56
- package/dist/components/JamlMapPreview.d.ts +6 -1
- package/dist/components/JamlMapPreview.js +99 -21
- package/dist/components/JamlSeedInput.d.ts +5 -0
- package/dist/components/JamlSeedInput.js +11 -14
- package/dist/components/JamlSpeedometer.d.ts +8 -8
- package/dist/components/JamlSpeedometer.js +24 -46
- package/dist/components/MotelyVersionBadge.d.ts +1 -3
- package/dist/components/MotelyVersionBadge.js +4 -16
- package/dist/components/jamlMap/JamlMapEditorDemo.d.ts +8 -0
- package/dist/components/jamlMap/JamlMapEditorDemo.js +170 -0
- package/dist/components/jamlMap/JokerPicker.d.ts +7 -0
- package/dist/components/jamlMap/JokerPicker.js +258 -0
- package/dist/components/jamlMap/MysterySlot.d.ts +32 -0
- package/dist/components/jamlMap/MysterySlot.js +109 -0
- package/dist/components/jamlMap/index.d.ts +3 -0
- package/dist/components/jamlMap/index.js +3 -0
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -2
- package/dist/decode/motelyItemDecoder.d.ts +10 -23
- package/dist/decode/motelyItemDecoder.js +103 -272
- package/dist/decode/motelySprite.d.ts +4 -0
- package/dist/decode/motelySprite.js +57 -0
- package/dist/hooks/analyzerStreamRegistry.js +30 -82
- package/dist/hooks/useAnalyzer.d.ts +10 -3
- package/dist/hooks/useAnalyzer.js +11 -6
- package/dist/hooks/useIntersectionObserver.d.ts +14 -0
- package/dist/hooks/useIntersectionObserver.js +50 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +4 -7
- package/dist/motely.d.ts +2 -2
- package/dist/motely.js +2 -2
- package/dist/motelyDisplay.d.ts +4 -623
- package/dist/motelyDisplay.js +26 -165
- package/dist/r3f/Card3D.d.ts +2 -2
- package/dist/r3f/Card3D.js +13 -48
- package/dist/r3f/JimboText3D.js +3 -2
- package/dist/render/CanvasRenderer.js +7 -171
- package/dist/sprites/spriteMapper.d.ts +71 -0
- package/dist/sprites/spriteMapper.js +40 -0
- package/dist/ui/JimboBadge.d.ts +8 -2
- package/dist/ui/JimboBadge.js +6 -22
- package/dist/ui/JimboToggleList.js +2 -7
- package/dist/ui/codeBlock.js +2 -3
- package/dist/ui/footer.d.ts +4 -0
- package/dist/ui/footer.js +6 -4
- package/dist/ui/hooks.d.ts +89 -0
- package/dist/ui/hooks.js +551 -0
- package/dist/ui/jimboBackground.js +2 -131
- package/dist/ui/jimboCopyRow.d.ts +4 -0
- package/dist/ui/jimboCopyRow.js +5 -22
- package/dist/ui/jimboFilterBar.d.ts +1 -4
- package/dist/ui/jimboFilterBar.js +2 -61
- package/dist/ui/jimboFlankNav.d.ts +1 -2
- package/dist/ui/jimboFlankNav.js +5 -30
- package/dist/ui/jimboTabs.d.ts +1 -5
- package/dist/ui/jimboTabs.js +6 -41
- package/dist/ui/jimboText.d.ts +1 -1
- package/dist/ui/jimboText.js +15 -32
- package/dist/ui/jimboTooltip.d.ts +1 -12
- package/dist/ui/jimboTooltip.js +6 -82
- package/dist/ui/panel.d.ts +2 -1
- package/dist/ui/panel.js +11 -47
- package/dist/ui/showcase.d.ts +4 -0
- package/dist/ui/showcase.js +9 -36
- package/dist/ui/sprites.js +3 -2
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +2 -0
- package/package.json +7 -6
- package/dist/decode/packedBalatroItem.d.ts +0 -13
- package/dist/decode/packedBalatroItem.js +0 -26
- package/dist/hooks/loadMotelyWasm.d.ts +0 -7
- package/dist/hooks/loadMotelyWasm.js +0 -16
- package/dist/utils/itemUtils.d.ts +0 -11
- package/dist/utils/itemUtils.js +0 -71
|
@@ -6,9 +6,9 @@ import { JimboColorOption } from "../ui/tokens.js";
|
|
|
6
6
|
import { extractVisualJamlItems, } from "../utils/jamlMapPreview.js";
|
|
7
7
|
const C = JimboColorOption;
|
|
8
8
|
const ZONES = {
|
|
9
|
-
must: { label: "MUST", color: C.BLUE },
|
|
10
|
-
should: { label: "SHOULD", color: C.RED },
|
|
11
|
-
mustNot: { label: "MUST NOT", color: C.ORANGE },
|
|
9
|
+
must: { label: "MUST", color: C.BLUE, glow: C.BLUE },
|
|
10
|
+
should: { label: "SHOULD", color: C.RED, glow: C.GOLD },
|
|
11
|
+
mustNot: { label: "MUST NOT", color: C.ORANGE, glow: C.ORANGE },
|
|
12
12
|
};
|
|
13
13
|
const SECTION_ORDER = ["must", "should", "mustNot"];
|
|
14
14
|
const SHEET_FOR_VISUAL = {
|
|
@@ -18,48 +18,126 @@ const SHEET_FOR_VISUAL = {
|
|
|
18
18
|
tag: "tags",
|
|
19
19
|
boss: "BlindChips",
|
|
20
20
|
};
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Pulsing glow animation for hits.
|
|
23
|
+
* Design ref: assets/...DesignsV2/src/v2/GlowRing.css
|
|
24
|
+
*/
|
|
25
|
+
const GLOW_ANIMATION = `
|
|
26
|
+
@keyframes j-glow-pulse {
|
|
27
|
+
0% { box-shadow: 0 0 0 1px var(--glow-color), 0 0 4px var(--glow-color); opacity: 0.8; }
|
|
28
|
+
50% { box-shadow: 0 0 0 2px var(--glow-color), 0 0 12px var(--glow-color); opacity: 1; }
|
|
29
|
+
100% { box-shadow: 0 0 0 1px var(--glow-color), 0 0 4px var(--glow-color); opacity: 0.8; }
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
function ClausePill({ item, color, glow, matchCount }) {
|
|
33
|
+
const isHit = matchCount > 0;
|
|
34
|
+
const hasData = matchCount !== undefined && matchCount >= 0;
|
|
22
35
|
return (_jsxs("div", { style: {
|
|
23
36
|
display: "flex",
|
|
24
37
|
alignItems: "center",
|
|
25
38
|
gap: 6,
|
|
26
|
-
background: C.
|
|
27
|
-
border: `2px solid ${
|
|
28
|
-
borderRadius:
|
|
29
|
-
padding: "
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
background: isHit ? `${glow}33` : C.DARKEST,
|
|
40
|
+
border: `2px solid ${isHit ? glow : C.PANEL_EDGE}`,
|
|
41
|
+
borderRadius: 4,
|
|
42
|
+
padding: "3px 8px",
|
|
43
|
+
position: "relative",
|
|
44
|
+
opacity: isHit ? 1 : 0.6,
|
|
45
|
+
// @ts-ignore -- CSS custom property
|
|
46
|
+
"--glow-color": glow,
|
|
47
|
+
animation: isHit ? "j-glow-pulse 1.6s ease-in-out infinite" : "none",
|
|
48
|
+
}, title: `${item.clauseKey}: ${item.value}${hasData ? ` (Found: ${matchCount})` : ""}`, children: [_jsx("style", { children: GLOW_ANIMATION }), _jsx(JimboSprite, { name: item.value, sheet: SHEET_FOR_VISUAL[item.visualType], width: 26 }), _jsx("div", { style: {
|
|
32
49
|
fontSize: 10,
|
|
33
50
|
color: C.WHITE,
|
|
34
|
-
letterSpacing:
|
|
51
|
+
letterSpacing: 0.5,
|
|
35
52
|
textShadow: "1px 1px 0 rgba(0,0,0,.8)",
|
|
36
|
-
}, children: item.value })
|
|
53
|
+
}, children: item.value }), isHit && (_jsx("div", { style: {
|
|
54
|
+
position: "absolute",
|
|
55
|
+
top: -6,
|
|
56
|
+
right: -6,
|
|
57
|
+
background: C.GREEN,
|
|
58
|
+
color: C.WHITE,
|
|
59
|
+
fontSize: 7,
|
|
60
|
+
padding: "1px 3px",
|
|
61
|
+
borderRadius: 3,
|
|
62
|
+
border: `1px solid ${C.BLACK}`,
|
|
63
|
+
boxShadow: `0 1px 0 ${C.BLACK}`,
|
|
64
|
+
}, children: matchCount > 1 ? `x${matchCount}` : "✓" }))] }));
|
|
65
|
+
}
|
|
66
|
+
function VisualChip({ item, matchCount, compact }) {
|
|
67
|
+
const isHit = matchCount > 0;
|
|
68
|
+
const hasData = matchCount !== undefined;
|
|
69
|
+
const glow = ZONES[item.section].glow;
|
|
70
|
+
return (_jsxs("div", { style: {
|
|
71
|
+
display: "flex",
|
|
72
|
+
alignItems: "center",
|
|
73
|
+
gap: compact ? 3 : 6,
|
|
74
|
+
background: isHit ? `${glow}33` : C.DARKEST,
|
|
75
|
+
border: `2px solid ${isHit ? glow : C.PANEL_EDGE}`,
|
|
76
|
+
borderRadius: 4,
|
|
77
|
+
padding: compact ? "2px 4px" : "3px 8px",
|
|
78
|
+
position: "relative",
|
|
79
|
+
opacity: isHit ? 1 : 0.6,
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
"--glow-color": glow,
|
|
82
|
+
animation: isHit ? "j-glow-pulse 1.6s ease-in-out infinite" : "none",
|
|
83
|
+
}, title: `${item.clauseKey}: ${item.value}${hasData ? ` (Found: ${matchCount})` : ""}`, children: [_jsx("style", { children: GLOW_ANIMATION }), _jsx("div", { style: { color: C.GREY, fontSize: 10, lineHeight: 1, padding: "0 2px" }, children: "\u22EE\u22EE" }), _jsx(JimboSprite, { name: item.value, sheet: SHEET_FOR_VISUAL[item.visualType], width: compact ? 20 : 26 }), _jsx("div", { style: {
|
|
84
|
+
fontSize: compact ? 9 : 10,
|
|
85
|
+
color: C.WHITE,
|
|
86
|
+
letterSpacing: 0.5,
|
|
87
|
+
textShadow: "1px 1px 0 rgba(0,0,0,.8)",
|
|
88
|
+
}, children: item.value }), isHit && (_jsx("div", { style: {
|
|
89
|
+
position: "absolute",
|
|
90
|
+
top: compact ? -4 : -6,
|
|
91
|
+
right: compact ? -4 : -6,
|
|
92
|
+
background: C.GREEN,
|
|
93
|
+
color: C.WHITE,
|
|
94
|
+
fontSize: 7,
|
|
95
|
+
padding: "1px 3px",
|
|
96
|
+
borderRadius: 3,
|
|
97
|
+
border: `1px solid ${C.BLACK}`,
|
|
98
|
+
boxShadow: `0 1px 0 ${C.BLACK}`,
|
|
99
|
+
}, children: matchCount > 1 ? `x${matchCount}` : "✓" }))] }));
|
|
37
100
|
}
|
|
38
|
-
function ZoneRail({ zone, items }) {
|
|
101
|
+
function ZoneRail({ zone, items, matchMap, compact = false }) {
|
|
39
102
|
const meta = ZONES[zone];
|
|
40
103
|
return (_jsxs("div", { style: {
|
|
41
104
|
border: `2px dashed ${meta.color}55`,
|
|
42
105
|
borderRadius: 6,
|
|
43
|
-
padding: 8,
|
|
106
|
+
padding: compact ? 4 : 8,
|
|
44
107
|
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }, children: [_jsx("div", { style: {
|
|
45
|
-
fontSize: 10,
|
|
108
|
+
fontSize: compact ? 8 : 10,
|
|
46
109
|
letterSpacing: 2,
|
|
47
110
|
padding: "2px 8px",
|
|
48
111
|
background: meta.color,
|
|
49
112
|
color: C.WHITE,
|
|
50
113
|
borderRadius: 3,
|
|
51
114
|
textShadow: "1px 1px 0 rgba(0,0,0,.8)",
|
|
52
|
-
}, children: meta.label }), _jsx("div", { style: { flex: 1, height: 1, background: `${meta.color}44` } }), _jsx("div", { style: { fontSize: 8, color: C.GREY }, children: items.length })] }), _jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: items.length === 0 ? (_jsx("div", { style: { fontSize: 10, color: C.GREY, padding: 10, fontStyle: "italic" }, children: "drop clauses here" })) : (items.map((item) =>
|
|
115
|
+
}, children: meta.label }), _jsx("div", { style: { flex: 1, height: 1, background: `${meta.color}44` } }), _jsx("div", { style: { fontSize: 8, color: C.GREY }, children: items.length })] }), _jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: items.length === 0 ? (_jsx("div", { style: { fontSize: 10, color: C.GREY, padding: 10, fontStyle: "italic" }, children: "drop clauses here" })) : (items.map((item) => {
|
|
116
|
+
// Match logic: the engine labels usually look like "must: joker: Blueprint"
|
|
117
|
+
// or "must: rareJoker: Blueprint". We try to match the item value and section.
|
|
118
|
+
const labelKey = `${item.section}: ${item.clauseKey}: ${item.value}`;
|
|
119
|
+
const count = matchMap[labelKey] ?? -1;
|
|
120
|
+
return (_jsx(ClausePill, { item: item, color: meta.color, glow: meta.glow, matchCount: count }, item.id));
|
|
121
|
+
})) })] }));
|
|
53
122
|
}
|
|
54
|
-
export function JamlMapPreview({ jaml, className = "", emptyMessage = "No visual JAML clauses found yet.", }) {
|
|
123
|
+
export function JamlMapPreview({ jaml, className = "", emptyMessage = "No visual JAML clauses found yet.", tallyColumns, tallyLabels, compact = false, }) {
|
|
55
124
|
const groups = useMemo(() => extractVisualJamlItems(jaml), [jaml]);
|
|
56
125
|
const totalItems = SECTION_ORDER.reduce((sum, s) => sum + groups[s].length, 0);
|
|
126
|
+
const matchMap = useMemo(() => {
|
|
127
|
+
const map = {};
|
|
128
|
+
if (tallyColumns && tallyLabels) {
|
|
129
|
+
tallyLabels.forEach((label, i) => {
|
|
130
|
+
map[label] = tallyColumns[i] ?? 0;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return map;
|
|
134
|
+
}, [tallyColumns, tallyLabels]);
|
|
57
135
|
if (totalItems === 0) {
|
|
58
136
|
return (_jsx("div", { className: className, style: {
|
|
59
137
|
background: C.DARKEST,
|
|
60
138
|
border: `2px solid ${C.PANEL_EDGE}`,
|
|
61
139
|
borderRadius: 6,
|
|
62
|
-
padding: 16,
|
|
140
|
+
padding: compact ? 8 : 16,
|
|
63
141
|
color: C.GREY,
|
|
64
142
|
fontSize: 11,
|
|
65
143
|
fontStyle: "italic",
|
|
@@ -69,9 +147,9 @@ export function JamlMapPreview({ jaml, className = "", emptyMessage = "No visual
|
|
|
69
147
|
return (_jsx("div", { className: className, style: {
|
|
70
148
|
display: "flex",
|
|
71
149
|
flexDirection: "column",
|
|
72
|
-
gap: 10,
|
|
73
|
-
padding: 10,
|
|
150
|
+
gap: compact ? 6 : 10,
|
|
151
|
+
padding: compact ? 6 : 10,
|
|
74
152
|
background: C.DARKEST,
|
|
75
153
|
color: C.WHITE,
|
|
76
|
-
}, children: SECTION_ORDER.map((section) => (_jsx(ZoneRail, { zone: section, items: groups[section] }, section))) }));
|
|
154
|
+
}, children: SECTION_ORDER.map((section) => (_jsx(ZoneRail, { zone: section, items: groups[section], matchMap: matchMap, compact: compact }, section))) }));
|
|
77
155
|
}
|
|
@@ -6,4 +6,9 @@ export interface JamlSeedInputProps {
|
|
|
6
6
|
className?: string;
|
|
7
7
|
style?: React.CSSProperties;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Balatro-styled seed input field.
|
|
11
|
+
* Validates 1-8 uppercase alphanumeric characters.
|
|
12
|
+
* All styling via jimbo.css `.j-seed-input` classes — zero inline styles.
|
|
13
|
+
*/
|
|
9
14
|
export declare function JamlSeedInput({ value, onChange, placeholder, className, style }: JamlSeedInputProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,29 +1,26 @@
|
|
|
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 { JimboColorOption } from "../ui/tokens.js";
|
|
5
4
|
import { JimboText } from "../ui/jimboText.js";
|
|
6
5
|
const SEED_PATTERN = /^[A-Z0-9]{0,8}$/;
|
|
6
|
+
/**
|
|
7
|
+
* Balatro-styled seed input field.
|
|
8
|
+
* Validates 1-8 uppercase alphanumeric characters.
|
|
9
|
+
* All styling via jimbo.css `.j-seed-input` classes — zero inline styles.
|
|
10
|
+
*/
|
|
7
11
|
export function JamlSeedInput({ value, onChange, placeholder = "Enter seed (e.g. J4SPZMWW)", className, style }) {
|
|
8
12
|
const [internal, setInternal] = useState(value ?? "");
|
|
9
13
|
const display = value ?? internal;
|
|
10
14
|
const isValid = display.length === 0 || SEED_PATTERN.test(display);
|
|
15
|
+
// Validation state drives data-valid attribute for CSS border color
|
|
16
|
+
const validState = display.length === 0 ? "partial"
|
|
17
|
+
: !isValid ? "false"
|
|
18
|
+
: display.length === 8 ? "true"
|
|
19
|
+
: "partial";
|
|
11
20
|
const handleChange = (e) => {
|
|
12
21
|
const raw = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8);
|
|
13
22
|
setInternal(raw);
|
|
14
23
|
onChange?.(raw);
|
|
15
24
|
};
|
|
16
|
-
return (_jsxs("div", { className:
|
|
17
|
-
padding: "6px 10px",
|
|
18
|
-
borderRadius: 6,
|
|
19
|
-
border: `2px solid ${!isValid ? JimboColorOption.RED : display.length === 8 ? JimboColorOption.GREEN : JimboColorOption.PANEL_EDGE}`,
|
|
20
|
-
background: JimboColorOption.DARKEST,
|
|
21
|
-
color: JimboColorOption.GOLD_TEXT,
|
|
22
|
-
fontSize: 16,
|
|
23
|
-
fontFamily: "m6x11plus, monospace",
|
|
24
|
-
letterSpacing: 2,
|
|
25
|
-
textTransform: "uppercase",
|
|
26
|
-
outline: "none",
|
|
27
|
-
transition: "border-color 100ms",
|
|
28
|
-
} }), display.length > 0 && display.length < 8 && (_jsxs(JimboText, { size: "xs", tone: "grey", children: [8 - display.length, " more characters"] }))] }));
|
|
25
|
+
return (_jsxs("div", { className: `j-seed-input ${className ?? ""}`, style: style, children: [_jsx(JimboText, { size: "xs", tone: "grey", children: "Seed" }), _jsx("input", { type: "text", className: "j-seed-input__field", "data-valid": validState, value: display, onChange: handleChange, placeholder: placeholder, maxLength: 8, spellCheck: false, autoComplete: "off" }), display.length > 0 && display.length < 8 && (_jsxs("span", { className: "j-seed-input__hint", children: [8 - display.length, " more characters"] }))] }));
|
|
29
26
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import type { SearchStatus } from "../hooks/useSearch.js";
|
|
1
|
+
export type JamlSpeedometerStatus = "idle" | "booting" | "running" | "completed" | "cancelled" | "error";
|
|
3
2
|
export interface JamlSpeedometerProps {
|
|
4
3
|
seedsPerSecond: number;
|
|
5
|
-
totalSearched: bigint;
|
|
6
|
-
matchingSeeds: bigint;
|
|
7
|
-
status:
|
|
8
|
-
className?: string;
|
|
9
|
-
style?: React.CSSProperties;
|
|
4
|
+
totalSearched: bigint | number;
|
|
5
|
+
matchingSeeds: bigint | number;
|
|
6
|
+
status: JamlSpeedometerStatus;
|
|
10
7
|
}
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Compact live-search stats strip for MCP/app chrome.
|
|
10
|
+
*/
|
|
11
|
+
export declare function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, }: JamlSpeedometerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,54 +1,32 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
4
|
-
|
|
5
|
-
function formatCount(
|
|
6
|
-
|
|
7
|
-
return `${(Number(n / 1000000n) / 1000).toFixed(1)}B`;
|
|
8
|
-
if (n >= 1000000n)
|
|
9
|
-
return `${(Number(n / 1000n) / 1000).toFixed(1)}M`;
|
|
10
|
-
if (n >= 1000n)
|
|
11
|
-
return `${(Number(n) / 1000).toFixed(1)}K`;
|
|
12
|
-
return n.toString();
|
|
4
|
+
const C = JimboColorOption;
|
|
5
|
+
function formatCount(value) {
|
|
6
|
+
return Number(value).toLocaleString();
|
|
13
7
|
}
|
|
14
|
-
function formatSpeed(
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
17
|
-
if (
|
|
18
|
-
return `${(
|
|
19
|
-
|
|
8
|
+
function formatSpeed(value) {
|
|
9
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
10
|
+
return "0/s";
|
|
11
|
+
if (value >= 1_000_000)
|
|
12
|
+
return `${(value / 1_000_000).toFixed(1)}M/s`;
|
|
13
|
+
if (value >= 1_000)
|
|
14
|
+
return `${(value / 1_000).toFixed(1)}K/s`;
|
|
15
|
+
return `${Math.round(value)}/s`;
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, className, style, }) {
|
|
30
|
-
const isActive = status === "running" || status === "booting";
|
|
31
|
-
const angle = needleAngle(seedsPerSecond);
|
|
32
|
-
const speedColor = seedsPerSecond >= 500_000
|
|
33
|
-
? JimboColorOption.GREEN
|
|
34
|
-
: seedsPerSecond >= 100_000
|
|
35
|
-
? JimboColorOption.GOLD
|
|
36
|
-
: seedsPerSecond > 0
|
|
37
|
-
? JimboColorOption.ORANGE
|
|
38
|
-
: JimboColorOption.GREY;
|
|
39
|
-
return (_jsxs("div", { className: className, style: {
|
|
17
|
+
/**
|
|
18
|
+
* Compact live-search stats strip for MCP/app chrome.
|
|
19
|
+
*/
|
|
20
|
+
export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, }) {
|
|
21
|
+
const active = status === "running" || status === "booting";
|
|
22
|
+
const tone = status === "error" ? C.RED : active ? C.GOLD : C.GREY;
|
|
23
|
+
return (_jsxs("div", { style: {
|
|
40
24
|
display: "flex",
|
|
41
|
-
flexDirection: "column",
|
|
42
25
|
alignItems: "center",
|
|
43
|
-
gap:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}, children: [_jsx("div", { style: { position: "relative", width: 120, height: 68, overflow: "hidden" }, children: _jsxs("svg", { viewBox: "0 0 120 68", width: 120, height: 68, children: [_jsx("path", { d: "M 10 65 A 50 50 0 0 1 110 65", fill: "none", stroke: JimboColorOption.DARK_GREY, strokeWidth: 6, strokeLinecap: "round" }), isActive && (_jsx("path", { d: "M 10 65 A 50 50 0 0 1 110 65", fill: "none", stroke: speedColor, strokeWidth: 6, strokeLinecap: "round", strokeDasharray: "157", strokeDashoffset: 157 - (157 * ((angle + 90) / 180)), style: { transition: "stroke-dashoffset 300ms ease, stroke 300ms ease" } })), _jsx("line", { x1: 60, y1: 65, x2: 60, y2: 20, stroke: isActive ? JimboColorOption.RED : JimboColorOption.GREY, strokeWidth: 2, strokeLinecap: "round", style: {
|
|
50
|
-
transformOrigin: "60px 65px",
|
|
51
|
-
transform: `rotate(${angle}deg)`,
|
|
52
|
-
transition: "transform 300ms ease",
|
|
53
|
-
} }), _jsx("circle", { cx: 60, cy: 65, r: 4, fill: JimboColorOption.RED })] }) }), _jsxs("div", { style: { textAlign: "center" }, children: [_jsx("div", { style: { fontSize: 20, fontFamily: "m6x11plus, monospace", color: isActive ? speedColor : JimboColorOption.GREY }, children: isActive ? formatSpeed(seedsPerSecond) : "---" }), _jsx(JimboText, { size: "xs", tone: "grey", children: "seeds / sec" })] }), _jsxs("div", { style: { display: "flex", gap: 16, marginTop: 2 }, children: [_jsxs("div", { style: { textAlign: "center" }, children: [_jsx("div", { style: { fontSize: 13, fontFamily: "m6x11plus, monospace", color: JimboColorOption.WHITE }, children: formatCount(totalSearched) }), _jsx(JimboText, { size: "xs", tone: "grey", children: "searched" })] }), _jsxs("div", { style: { textAlign: "center" }, children: [_jsx("div", { style: { fontSize: 13, fontFamily: "m6x11plus, monospace", color: JimboColorOption.GREEN_TEXT }, children: formatCount(matchingSeeds) }), _jsx(JimboText, { size: "xs", tone: "grey", children: "matches" })] })] })] }));
|
|
26
|
+
gap: 8,
|
|
27
|
+
color: tone,
|
|
28
|
+
fontSize: 11,
|
|
29
|
+
fontFamily: "var(--font-sans, m6x11plus), monospace",
|
|
30
|
+
whiteSpace: "nowrap",
|
|
31
|
+
}, children: [_jsx("span", { children: status }), _jsx("span", { children: formatSpeed(seedsPerSecond) }), _jsxs("span", { children: [formatCount(totalSearched), " searched"] }), _jsxs("span", { children: [formatCount(matchingSeeds), " matches"] })] }));
|
|
54
32
|
}
|
|
@@ -23,8 +23,6 @@ export interface MotelyVersionBadgeProps {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Badge showing the loaded motely-wasm version + optional SIMD / threads
|
|
26
|
-
* capability indicators.
|
|
27
|
-
* weejoker's lib/api — the consumer owns capability fetching and passes
|
|
28
|
-
* the result in.
|
|
26
|
+
* capability indicators. All styling via jimbo.css `.j-motely-badge`.
|
|
29
27
|
*/
|
|
30
28
|
export declare function MotelyVersionBadge({ caps, version, minimal, loading, className, style, }: MotelyVersionBadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { JimboColorOption } from '../ui/tokens.js';
|
|
4
3
|
import { JimboText } from '../ui/jimboText.js';
|
|
5
4
|
/**
|
|
6
5
|
* Badge showing the loaded motely-wasm version + optional SIMD / threads
|
|
7
|
-
* capability indicators.
|
|
8
|
-
* weejoker's lib/api — the consumer owns capability fetching and passes
|
|
9
|
-
* the result in.
|
|
6
|
+
* capability indicators. All styling via jimbo.css `.j-motely-badge`.
|
|
10
7
|
*/
|
|
11
8
|
export function MotelyVersionBadge({ caps, version, minimal = false, loading = false, className = '', style, }) {
|
|
12
9
|
if (loading) {
|
|
13
|
-
return (_jsx("span", { className: className
|
|
10
|
+
return (_jsx("span", { className: `j-motely-badge ${className}`, style: style, children: _jsx(JimboText, { size: "xs", tone: "grey", children: "Initializing\u2026" }) }));
|
|
14
11
|
}
|
|
15
12
|
const resolved = caps?.version ?? version ?? '?';
|
|
16
13
|
const simd = caps?.simd;
|
|
17
14
|
const threads = caps?.threads;
|
|
18
15
|
if (minimal) {
|
|
19
|
-
return (_jsxs("span", { className: className
|
|
16
|
+
return (_jsxs("span", { className: `j-motely-badge ${className}`, style: style, children: [_jsxs(JimboText, { size: "xs", tone: "grey", children: ["v", resolved] }), simd ? _jsx(JimboText, { size: "xs", tone: "blue", title: "SIMD enabled", children: "\u26A1" }) : null, threads ? _jsx(JimboText, { size: "xs", tone: "green", title: "Multi-threaded", children: "\uD83E\uDDF5" }) : null] }));
|
|
20
17
|
}
|
|
21
|
-
return (_jsxs("div", { className: className
|
|
22
|
-
display: 'inline-flex',
|
|
23
|
-
alignItems: 'center',
|
|
24
|
-
gap: 6,
|
|
25
|
-
padding: '3px 8px',
|
|
26
|
-
borderRadius: 4,
|
|
27
|
-
background: JimboColorOption.DARKEST,
|
|
28
|
-
border: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
29
|
-
...style,
|
|
30
|
-
}, children: [_jsxs(JimboText, { size: "xs", tone: "gold", uppercase: true, children: ["motely v", resolved] }), simd ? _jsx(JimboText, { size: "xs", tone: "blue", title: "SIMD enabled", children: "\u26A1" }) : null, threads ? _jsx(JimboText, { size: "xs", tone: "green", title: "Multi-threaded", children: "\uD83E\uDDF5" }) : null] }));
|
|
18
|
+
return (_jsxs("div", { className: `j-motely-badge j-motely-badge--chip ${className}`, style: style, children: [_jsxs(JimboText, { size: "xs", tone: "gold", uppercase: true, children: ["motely v", resolved] }), simd ? _jsx(JimboText, { size: "xs", tone: "blue", title: "SIMD enabled", children: "\u26A1" }) : null, threads ? _jsx(JimboText, { size: "xs", tone: "green", title: "Multi-threaded", children: "\uD83E\uDDF5" }) : null] }));
|
|
31
19
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SlotSelection, type JamlZone } from "./MysterySlot.js";
|
|
2
|
+
export interface JamlMapEditorDemoProps {
|
|
3
|
+
/** Initial zone for the demo. */
|
|
4
|
+
zone?: JamlZone;
|
|
5
|
+
/** Callback when a selection changes, to update JAML text. */
|
|
6
|
+
onChange?: (slots: (SlotSelection | null)[]) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function JamlMapEditorDemo({ zone: initialZone, onChange, }: JamlMapEditorDemoProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { MysterySlot } from "./MysterySlot.js";
|
|
5
|
+
import { JokerPicker } from "./JokerPicker.js";
|
|
6
|
+
import { JimboColorOption, withAlpha } from "../../ui/tokens.js";
|
|
7
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
8
|
+
const C = JimboColorOption;
|
|
9
|
+
const INITIAL_SLOTS = 8; // 8 shop item slots like in Balatro ante
|
|
10
|
+
export function JamlMapEditorDemo({ zone: initialZone = "must", onChange, }) {
|
|
11
|
+
const [zone, setZone] = useState(initialZone);
|
|
12
|
+
const [slots, setSlots] = useState(Array(INITIAL_SLOTS).fill(null));
|
|
13
|
+
const [activeSlot, setActiveSlot] = useState(null);
|
|
14
|
+
const handleSlotTap = useCallback((index) => {
|
|
15
|
+
setActiveSlot(index);
|
|
16
|
+
}, []);
|
|
17
|
+
const handleSlotClear = useCallback((index) => {
|
|
18
|
+
setSlots((prev) => {
|
|
19
|
+
const next = [...prev];
|
|
20
|
+
next[index] = null;
|
|
21
|
+
onChange?.(next);
|
|
22
|
+
return next;
|
|
23
|
+
});
|
|
24
|
+
}, [onChange]);
|
|
25
|
+
const handleJokerSelect = useCallback((selection) => {
|
|
26
|
+
if (activeSlot === null)
|
|
27
|
+
return;
|
|
28
|
+
setSlots((prev) => {
|
|
29
|
+
const next = [...prev];
|
|
30
|
+
next[activeSlot] = selection;
|
|
31
|
+
onChange?.(next);
|
|
32
|
+
return next;
|
|
33
|
+
});
|
|
34
|
+
setActiveSlot(null);
|
|
35
|
+
}, [activeSlot, onChange]);
|
|
36
|
+
const handlePickerCancel = useCallback(() => {
|
|
37
|
+
setActiveSlot(null);
|
|
38
|
+
}, []);
|
|
39
|
+
const filledCount = slots.filter(Boolean).length;
|
|
40
|
+
return (_jsxs("div", { style: styles.wrapper, children: [_jsx("div", { style: styles.zoneBar, children: ["must", "should", "mustnot"].map((z) => (_jsx("button", { onClick: () => setZone(z), style: {
|
|
41
|
+
...styles.zoneBtn,
|
|
42
|
+
borderColor: zone === z ? ZONE_COLORS[z] : C.TEAL_GREY,
|
|
43
|
+
color: zone === z ? ZONE_COLORS[z] : C.GREY,
|
|
44
|
+
background: zone === z ? withAlpha(ZONE_COLORS[z], 0.1) : "transparent",
|
|
45
|
+
}, children: z.toUpperCase() }, z))) }), _jsxs("div", { style: styles.sectionLabel, children: [_jsx("span", { style: { color: ZONE_COLORS[zone] }, children: "Shop Items" }), _jsxs("span", { style: { color: C.GREY, fontSize: 11 }, children: [filledCount, "/", INITIAL_SLOTS, " defined"] })] }), _jsx("div", { style: styles.slotRow, children: slots.map((selection, i) => (_jsx(MysterySlot, { zone: zone, sheetType: "Jokers", selection: selection ?? undefined, width: 48, onTap: () => handleSlotTap(i), onClear: selection ? () => handleSlotClear(i) : undefined }, i))) }), _jsx("div", { style: styles.scrollHint, children: "\u25C0 swipe \u25B6" }), activeSlot !== null && (_jsxs("div", { style: styles.overlay, children: [_jsx("div", { style: styles.overlayBackdrop, onClick: handlePickerCancel }), _jsx("div", { style: styles.pickerWrapper, children: _jsx(JokerPicker, { onSelect: handleJokerSelect, onCancel: handlePickerCancel }) })] })), filledCount > 0 && (_jsxs("div", { style: styles.jamlPreview, children: [_jsx("div", { style: styles.jamlHeader, children: "Generated JAML" }), _jsx("pre", { style: styles.jamlCode, children: generateJamlSnippet(zone, slots) })] }))] }));
|
|
46
|
+
}
|
|
47
|
+
// ─── JAML generation ─────────────────────────────────────────────────────────
|
|
48
|
+
function generateJamlSnippet(zone, slots) {
|
|
49
|
+
const filled = slots.filter(Boolean);
|
|
50
|
+
if (filled.length === 0)
|
|
51
|
+
return "# empty";
|
|
52
|
+
const jamlZone = zone === "mustnot" ? "mustNot" : zone;
|
|
53
|
+
const lines = [`${jamlZone}:`];
|
|
54
|
+
// Group by clauseKey
|
|
55
|
+
const groups = new Map();
|
|
56
|
+
for (const s of filled) {
|
|
57
|
+
const existing = groups.get(s.clauseKey) ?? [];
|
|
58
|
+
existing.push(s.value);
|
|
59
|
+
groups.set(s.clauseKey, existing);
|
|
60
|
+
}
|
|
61
|
+
for (const [key, values] of groups) {
|
|
62
|
+
if (values.length === 1) {
|
|
63
|
+
lines.push(` - ${key}: ${values[0]}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
lines.push(` - ${key}: [${values.join(", ")}]`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
// ─── Constants & styles ──────────────────────────────────────────────────────
|
|
72
|
+
const ZONE_COLORS = {
|
|
73
|
+
must: C.BLUE,
|
|
74
|
+
should: C.RED,
|
|
75
|
+
mustnot: C.ORANGE,
|
|
76
|
+
};
|
|
77
|
+
const styles = {
|
|
78
|
+
wrapper: {
|
|
79
|
+
position: "relative",
|
|
80
|
+
padding: 16,
|
|
81
|
+
background: C.DARKEST,
|
|
82
|
+
borderRadius: 8,
|
|
83
|
+
border: `1px solid ${C.TEAL_GREY}`,
|
|
84
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
85
|
+
},
|
|
86
|
+
zoneBar: {
|
|
87
|
+
display: "flex",
|
|
88
|
+
gap: 6,
|
|
89
|
+
marginBottom: 12,
|
|
90
|
+
},
|
|
91
|
+
zoneBtn: {
|
|
92
|
+
padding: "5px 12px",
|
|
93
|
+
border: "2px solid",
|
|
94
|
+
borderRadius: 4,
|
|
95
|
+
background: "none",
|
|
96
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
97
|
+
fontSize: 12,
|
|
98
|
+
fontWeight: "bold",
|
|
99
|
+
cursor: "pointer",
|
|
100
|
+
transition: "all 150ms",
|
|
101
|
+
letterSpacing: 1,
|
|
102
|
+
},
|
|
103
|
+
sectionLabel: {
|
|
104
|
+
display: "flex",
|
|
105
|
+
justifyContent: "space-between",
|
|
106
|
+
alignItems: "center",
|
|
107
|
+
marginBottom: 8,
|
|
108
|
+
fontSize: 14,
|
|
109
|
+
fontWeight: "bold",
|
|
110
|
+
},
|
|
111
|
+
slotRow: {
|
|
112
|
+
display: "flex",
|
|
113
|
+
gap: 6,
|
|
114
|
+
overflowX: "auto",
|
|
115
|
+
paddingBottom: 8,
|
|
116
|
+
WebkitOverflowScrolling: "touch",
|
|
117
|
+
},
|
|
118
|
+
scrollHint: {
|
|
119
|
+
textAlign: "center",
|
|
120
|
+
fontSize: 10,
|
|
121
|
+
color: C.GREY,
|
|
122
|
+
opacity: 0.5,
|
|
123
|
+
marginTop: 2,
|
|
124
|
+
marginBottom: 8,
|
|
125
|
+
},
|
|
126
|
+
overlay: {
|
|
127
|
+
position: "fixed",
|
|
128
|
+
inset: 0,
|
|
129
|
+
zIndex: 9999,
|
|
130
|
+
display: "flex",
|
|
131
|
+
alignItems: "center",
|
|
132
|
+
justifyContent: "center",
|
|
133
|
+
},
|
|
134
|
+
overlayBackdrop: {
|
|
135
|
+
position: "absolute",
|
|
136
|
+
inset: 0,
|
|
137
|
+
background: withAlpha(C.BLACK, 0.6),
|
|
138
|
+
backdropFilter: "blur(4px)",
|
|
139
|
+
},
|
|
140
|
+
pickerWrapper: {
|
|
141
|
+
position: "relative",
|
|
142
|
+
zIndex: 1,
|
|
143
|
+
width: "90%",
|
|
144
|
+
maxWidth: 400,
|
|
145
|
+
},
|
|
146
|
+
jamlPreview: {
|
|
147
|
+
marginTop: 12,
|
|
148
|
+
borderRadius: 4,
|
|
149
|
+
border: `1px solid ${C.TEAL_GREY}`,
|
|
150
|
+
overflow: "hidden",
|
|
151
|
+
},
|
|
152
|
+
jamlHeader: {
|
|
153
|
+
padding: "6px 10px",
|
|
154
|
+
fontSize: 11,
|
|
155
|
+
color: C.GREY,
|
|
156
|
+
background: withAlpha(C.DARK_GREY, 0.5),
|
|
157
|
+
borderBottom: `1px solid ${C.TEAL_GREY}`,
|
|
158
|
+
letterSpacing: 1,
|
|
159
|
+
textTransform: "uppercase",
|
|
160
|
+
},
|
|
161
|
+
jamlCode: {
|
|
162
|
+
margin: 0,
|
|
163
|
+
padding: "8px 10px",
|
|
164
|
+
fontSize: 12,
|
|
165
|
+
color: C.GREEN_TEXT,
|
|
166
|
+
background: withAlpha(C.DARKEST, 0.8),
|
|
167
|
+
lineHeight: "1.5",
|
|
168
|
+
whiteSpace: "pre-wrap",
|
|
169
|
+
},
|
|
170
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SlotSelection } from "./MysterySlot.js";
|
|
2
|
+
export type JokerRarity = "common" | "uncommon" | "rare" | "legendary";
|
|
3
|
+
export interface JokerPickerProps {
|
|
4
|
+
onSelect: (selection: SlotSelection) => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function JokerPicker({ onSelect, onCancel }: JokerPickerProps): import("react/jsx-runtime").JSX.Element;
|