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
package/DESIGN.md
CHANGED
|
@@ -124,7 +124,7 @@ components:
|
|
|
124
124
|
|
|
125
125
|
Jimbo is the design system for Balatro seed finder tools (JAML-UI, WeeJoker, Seed Finder). It recreates the cozy, tactile, chunky feel of LocalThunk's Balatro — dark panels with silver borders, 3D-press buttons, pixel typography, juice animations. Everything feels like a physical object you can poke.
|
|
126
126
|
|
|
127
|
-
The system is built **Mobile First**. The absolute minimum viewport width is **
|
|
127
|
+
The system is built **Mobile First**. The absolute minimum viewport width is **375px**. All components must be accessible and usable at 375px without breaking layouts or horizontal scrolling. No fat padding, no bloated margins — every pixel earns its place.
|
|
128
128
|
|
|
129
129
|
## Colors
|
|
130
130
|
|
|
@@ -133,12 +133,12 @@ All colors are eyedropped from Balatro's actual rendered shader output. Do NOT s
|
|
|
133
133
|
- **Red (#ff4c40):** Primary action, mult scoring, should-clause hits. The "play" color.
|
|
134
134
|
- **Blue (#0093ff):** Secondary action, chips scoring, must-clause gates. The "requirement" color.
|
|
135
135
|
- **Green (#429f79):** Success, positive state, money.
|
|
136
|
-
- **Orange (#ff9800):** Back/return actions, warning.
|
|
136
|
+
- **Orange (#ff9800):** Back/return actions, warning, configuration, misc.
|
|
137
137
|
- **Gold (#e4b643):** Seed text, premium highlights, active tab. The "treasure" color.
|
|
138
138
|
- **Purple (#9e74ce):** Joker rarity, tarot cards.
|
|
139
139
|
- **Dark Grey (#3a5055):** Panel backgrounds — the primary surface.
|
|
140
140
|
- **Darkest (#1e2b2d):** Deepest background, inset areas.
|
|
141
|
-
- **Grey (#708386):** Disabled text, labels
|
|
141
|
+
- **Grey (#708386):** Disabled text, labels.
|
|
142
142
|
- **Border Silver (#b9c2d2):** Panel top/side borders — the "silver frame."
|
|
143
143
|
- **Border South (#777e89):** Panel bottom border — creates the 3D depth illusion.
|
|
144
144
|
- **Panel Edge (#1e2e32):** Thin outer edge on panels.
|
|
@@ -153,13 +153,13 @@ All text is uppercase with generous letter-spacing (0.04em-0.1em) for labels and
|
|
|
153
153
|
|
|
154
154
|
## Layout
|
|
155
155
|
|
|
156
|
-
Target: Minimum
|
|
156
|
+
Target: Minimum 375px portrait width. Components must scale gracefully using relative units and flexible layouts. Avoid fixed widths that break at 375px. Vertical snap-scroll for ante pages. Horizontal swipe for seed navigation.
|
|
157
157
|
|
|
158
158
|
Panels use 2px solid borders with border-silver on top/sides and border-south on bottom, creating a subtle 3D card effect. Inner shadow: `inset 0 0 0 1px rgba(255,255,255,0.04)`. Outer shadow: `0 2px 0 #000`.
|
|
159
159
|
|
|
160
160
|
## Elevation & Depth
|
|
161
161
|
|
|
162
|
-
Buttons have a colored "underside" via box-shadow (not blur). On press, translateY increases by 2-3px and the shadow collapses — the button physically sinks. On hover,
|
|
162
|
+
Buttons have a colored "underside" via box-shadow (not blur). On press, translateY increases by 2-3px and the shadow collapses — the button physically sinks. On hover, apply a tiny brightness bump (no lift).
|
|
163
163
|
|
|
164
164
|
Panels sit on a dark south-shadow (`0 3px 0 rgba(0,0,0,0.55)`). Translucent panels (for swirl-background contexts) use `rgba(15, 24, 26, 0.78)` with `backdrop-filter: blur(2px)`.
|
|
165
165
|
|
|
@@ -167,7 +167,7 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
|
|
|
167
167
|
|
|
168
168
|
## Components
|
|
169
169
|
|
|
170
|
-
**Button:** Chunky 3D press. Colored underside via box-shadow. Hover
|
|
170
|
+
**Button:** Chunky 3D press. Colored underside via box-shadow. Hover brightness bump. Press sinks +2-3px + shadow collapse. Variants: primary (red), secondary (blue), back (orange). Sizes via padding, not font-size. Easing: `cubic-bezier(0.34, 1.56, 0.64, 1)`.
|
|
171
171
|
|
|
172
172
|
**Panel:** Dark grey (#3a5055) background, 2px solid border (silver top/sides, south bottom), border-radius 6px. Inner highlight: `inset 0 0 0 1px rgba(255,255,255,0.04)`. Drop: `0 2px 0 #000`.
|
|
173
173
|
|
|
@@ -185,13 +185,11 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
|
|
|
185
185
|
|
|
186
186
|
## Do's and Don'ts
|
|
187
187
|
|
|
188
|
-
- DO use m6x11plus for everything
|
|
189
|
-
- DO
|
|
190
|
-
- DO design for 320px portrait first. Desktop is an expanded view of the mobile baseline.
|
|
188
|
+
- DO use m6x11plus for everything except code/monospace.
|
|
189
|
+
- DO design for 375px portrait.
|
|
191
190
|
- DO use translateY + box-shadow for button depth. Not CSS 3D transforms.
|
|
192
|
-
- DO dim non-matching items (opacity 0.4 + grayscale 0.6). They stay visible for context.
|
|
193
191
|
- DON'T use font-weight bold. m6x11plus is single-weight. Bold = muddy.
|
|
194
192
|
- DON'T use fat padding or margins. Balatro UI is dense and cozy.
|
|
195
193
|
- DON'T add horizontal scroll. Vertical snap-scroll + horizontal swipe only.
|
|
196
194
|
- DON'T use rounded corners larger than 10px. Balatro is chunky, not bubbly.
|
|
197
|
-
- DON'T use blur-based shadows for depth. Use solid colored box-shadows.
|
|
195
|
+
- DON'T use blur-based shadows for depth. Use solid colored box-shadows 80% opaque.
|
package/dist/assets.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const JAML_ASSET_FILES: {
|
|
|
10
10
|
readonly stickers: "stickers.png";
|
|
11
11
|
readonly tags: "tags.png";
|
|
12
12
|
readonly stakes: "balatro-stake-chips.png";
|
|
13
|
+
readonly font: "fonts/m6x11plusplus.otf";
|
|
13
14
|
};
|
|
14
15
|
export type JamlAssetKey = keyof typeof JAML_ASSET_FILES;
|
|
15
16
|
export type JamlAssetFile = (typeof JAML_ASSET_FILES)[JamlAssetKey];
|
|
@@ -17,3 +18,8 @@ export declare function setJamlAssetBaseUrl(baseUrl: string | null | undefined):
|
|
|
17
18
|
export declare function clearJamlAssetBaseUrl(): void;
|
|
18
19
|
export declare function resolveJamlAssetUrl(asset: JamlAssetKey | JamlAssetFile): string;
|
|
19
20
|
export declare function getDefaultJamlAssetUrlMap(): Readonly<Record<JamlAssetKey, string>>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the versioned Vercel Blob URL for motely-wasm's Bootsharp module.
|
|
23
|
+
* Pass the same pinned motely-wasm version the app installed/uploaded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getMotelyWasmUrl(version: string): string;
|
package/dist/assets.js
CHANGED
|
@@ -10,6 +10,7 @@ export const JAML_ASSET_FILES = {
|
|
|
10
10
|
stickers: "stickers.png",
|
|
11
11
|
tags: "tags.png",
|
|
12
12
|
stakes: "balatro-stake-chips.png",
|
|
13
|
+
font: "fonts/m6x11plusplus.otf",
|
|
13
14
|
};
|
|
14
15
|
const assetKeyByFileName = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [fileName, key]));
|
|
15
16
|
const defaultAssetUrls = {
|
|
@@ -24,6 +25,7 @@ const defaultAssetUrls = {
|
|
|
24
25
|
stickers: new URL(`../assets/${JAML_ASSET_FILES.stickers}`, import.meta.url).href,
|
|
25
26
|
tags: new URL(`../assets/${JAML_ASSET_FILES.tags}`, import.meta.url).href,
|
|
26
27
|
stakes: new URL(`../assets/${JAML_ASSET_FILES.stakes}`, import.meta.url).href,
|
|
28
|
+
font: new URL(`../assets/${JAML_ASSET_FILES.font}`, import.meta.url).href,
|
|
27
29
|
};
|
|
28
30
|
let customAssetBaseUrl = null;
|
|
29
31
|
function normalizeBaseUrl(baseUrl) {
|
|
@@ -66,3 +68,10 @@ export function resolveJamlAssetUrl(asset) {
|
|
|
66
68
|
export function getDefaultJamlAssetUrlMap() {
|
|
67
69
|
return defaultAssetUrls;
|
|
68
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns the versioned Vercel Blob URL for motely-wasm's Bootsharp module.
|
|
73
|
+
* Pass the same pinned motely-wasm version the app installed/uploaded.
|
|
74
|
+
*/
|
|
75
|
+
export function getMotelyWasmUrl(version) {
|
|
76
|
+
return `https://cdn.seedfinder.app/motely-wasm/${version}/index.mjs`;
|
|
77
|
+
}
|
|
@@ -40,5 +40,8 @@ export interface AnalyzerExplorerProps {
|
|
|
40
40
|
visibleAntes?: number;
|
|
41
41
|
totalAntes?: number;
|
|
42
42
|
className?: string;
|
|
43
|
+
jaml?: string;
|
|
44
|
+
tallyColumns?: number[][];
|
|
45
|
+
tallyLabels?: string[];
|
|
43
46
|
}
|
|
44
|
-
export declare function AnalyzerExplorer({ antes, highlights, visibleAntes, totalAntes, className, }: AnalyzerExplorerProps): import("react/jsx-runtime").JSX.Element;
|
|
47
|
+
export declare function AnalyzerExplorer({ antes, highlights, visibleAntes, totalAntes, className, jaml, tallyColumns, tallyLabels, }: AnalyzerExplorerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,40 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { useAnteTracker } from "../ui/hooks.js";
|
|
4
5
|
import { JamlBoss, JamlGameCard, JamlTag, JamlVoucher, resolveAnalyzerShopItem, } from "./GameCard.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const scrollRef =
|
|
8
|
-
|
|
6
|
+
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
7
|
+
export function AnalyzerExplorer({ antes, highlights = [], visibleAntes, totalAntes, className = "", jaml, tallyColumns, tallyLabels, }) {
|
|
8
|
+
const { currentAnte, scrollRef, scrollToAnte, registerAnteRef } = useAnteTracker(antes, {
|
|
9
|
+
threshold: [0.45, 0.72, 0.9],
|
|
10
|
+
});
|
|
9
11
|
const highlightRefs = useRef(new Map());
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
setCurrentAnte(antes[0]?.ante ?? 0);
|
|
12
|
-
}, [antes]);
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
const root = scrollRef.current;
|
|
15
|
-
if (!root) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const observer = new IntersectionObserver((entries) => {
|
|
19
|
-
const mostVisibleEntry = entries
|
|
20
|
-
.filter((entry) => entry.isIntersecting)
|
|
21
|
-
.sort((left, right) => right.intersectionRatio - left.intersectionRatio)[0];
|
|
22
|
-
if (!mostVisibleEntry) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const ante = Number(mostVisibleEntry.target.dataset.ante);
|
|
26
|
-
if (!Number.isNaN(ante)) {
|
|
27
|
-
setCurrentAnte(ante);
|
|
28
|
-
}
|
|
29
|
-
}, {
|
|
30
|
-
root,
|
|
31
|
-
threshold: [0.45, 0.72, 0.9],
|
|
32
|
-
});
|
|
33
|
-
for (const [, element] of anteRefs.current) {
|
|
34
|
-
observer.observe(element);
|
|
35
|
-
}
|
|
36
|
-
return () => observer.disconnect();
|
|
37
|
-
}, [antes]);
|
|
38
12
|
useEffect(() => {
|
|
39
13
|
const activeHighlight = highlights.find((highlight) => highlight.ante === currentAnte);
|
|
40
14
|
if (!activeHighlight) {
|
|
@@ -47,18 +21,12 @@ export function AnalyzerExplorer({ antes, highlights = [], visibleAntes, totalAn
|
|
|
47
21
|
block: "nearest",
|
|
48
22
|
});
|
|
49
23
|
}, [currentAnte, highlights]);
|
|
50
|
-
const scrollToAnte = useCallback((ante) => {
|
|
51
|
-
anteRefs.current.get(ante)?.scrollIntoView({
|
|
52
|
-
behavior: "smooth",
|
|
53
|
-
block: "start",
|
|
54
|
-
});
|
|
55
|
-
}, []);
|
|
56
24
|
const currentAnteIndex = antes.findIndex((ante) => ante.ante === currentAnte);
|
|
57
25
|
const previousAnte = currentAnteIndex > 0 ? antes[currentAnteIndex - 1]?.ante ?? null : null;
|
|
58
26
|
const nextAnte = currentAnteIndex >= 0 && currentAnteIndex < antes.length - 1 ? antes[currentAnteIndex + 1]?.ante ?? null : null;
|
|
59
27
|
const shownAntes = visibleAntes ?? antes.length;
|
|
60
28
|
const availableAntes = totalAntes ?? shownAntes;
|
|
61
|
-
return (_jsxs("div", { className: className, style: styles.root, children: [highlights.length > 0 ? (_jsxs("section", { style: styles.highlightSection, children: [_jsxs("div", { style: styles.highlightHeader, children: [_jsx("span", { style: styles.highlightTitle, children: "Highlights" }), _jsx("span", { style: styles.highlightSubtitle, children: "Swipe, tap, jump" })] }), _jsx("div", { style: styles.highlightRail, children: highlights.map((highlight) => {
|
|
29
|
+
return (_jsxs("div", { className: className, style: styles.root, children: [jaml ? (_jsxs("section", { style: styles.jamlSection, children: [_jsx("div", { style: styles.highlightHeader, children: _jsx("span", { style: styles.highlightTitle, children: "JAML Map" }) }), _jsx(JamlMapPreview, { jaml: jaml, tallyColumns: tallyColumns?.[0], tallyLabels: tallyLabels, compact: true })] })) : null, highlights.length > 0 ? (_jsxs("section", { style: styles.highlightSection, children: [_jsxs("div", { style: styles.highlightHeader, children: [_jsx("span", { style: styles.highlightTitle, children: "Highlights" }), _jsx("span", { style: styles.highlightSubtitle, children: "Swipe, tap, jump" })] }), _jsx("div", { style: styles.highlightRail, children: highlights.map((highlight) => {
|
|
62
30
|
const isActive = highlight.ante === currentAnte;
|
|
63
31
|
return (_jsxs("button", { ref: (element) => {
|
|
64
32
|
if (element) {
|
|
@@ -74,14 +42,7 @@ export function AnalyzerExplorer({ antes, highlights = [], visibleAntes, totalAn
|
|
|
74
42
|
}, children: "\u25B2" }), _jsxs("div", { style: styles.navLabel, children: ["Ante ", currentAnte, _jsxs("span", { style: styles.navSubLabel, children: ["of ", shownAntes, availableAntes > shownAntes ? ` / ${availableAntes}` : ""] })] }), _jsx("button", { type: "button", onClick: () => nextAnte !== null && scrollToAnte(nextAnte), disabled: nextAnte === null, style: {
|
|
75
43
|
...styles.navButton,
|
|
76
44
|
opacity: nextAnte !== null ? 1 : 0.25,
|
|
77
|
-
}, children: "\u25BC" })] }), _jsx("div", { ref: scrollRef, style: styles.scrollRegion, children: antes.map((ante) => (_jsx("div", { "data-ante": ante.ante, ref: (element) => {
|
|
78
|
-
if (element) {
|
|
79
|
-
anteRefs.current.set(ante.ante, element);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
anteRefs.current.delete(ante.ante);
|
|
83
|
-
}
|
|
84
|
-
}, style: styles.antePage, children: _jsx(AnteSection, { ante: ante }) }, ante.ante))) })] }));
|
|
45
|
+
}, children: "\u25BC" })] }), _jsx("div", { ref: scrollRef, style: styles.scrollRegion, children: antes.map((ante) => (_jsx("div", { "data-ante": ante.ante, ref: (element) => registerAnteRef(ante.ante, element), style: styles.antePage, children: _jsx(AnteSection, { ante: ante }) }, ante.ante))) })] }));
|
|
85
46
|
}
|
|
86
47
|
function AnteSection({ ante }) {
|
|
87
48
|
return (_jsxs("section", { style: styles.anteSection, children: [_jsxs("div", { style: styles.anteHeader, children: [_jsxs("span", { style: styles.anteHeading, children: ["Ante ", ante.ante] }), ante.boss ? (_jsxs("div", { style: styles.bossRow, children: [_jsx(JamlBoss, { bossName: ante.boss, scale: 0.62 }), _jsx("span", { style: styles.bossName, children: ante.boss })] })) : null] }), ante.smallBlindTag || ante.bigBlindTag ? (_jsxs(AnalyzerRow, { label: "Tags", children: [ante.smallBlindTag ? (_jsx(CompactCard, { label: "Small", visual: _jsx(JamlTag, { tagName: ante.smallBlindTag, scale: 0.58, hoverTilt: true }), text: ante.smallBlindTag })) : null, ante.bigBlindTag ? (_jsx(CompactCard, { label: "Big", visual: _jsx(JamlTag, { tagName: ante.bigBlindTag, scale: 0.58, hoverTilt: true }), text: ante.bigBlindTag })) : null] })) : null, ante.voucher ? (_jsx(AnalyzerRow, { label: "Voucher", children: _jsx(CompactCard, { visual: _jsx(JamlVoucher, { voucherName: ante.voucher, scale: 0.58, hoverTilt: true }), text: ante.voucher }) })) : null, ante.shop && ante.shop.length > 0 ? (_jsx(AnalyzerRow, { label: "Shop", dense: true, children: ante.shop.map((item) => (_jsx(ResolvedItemCard, { item: item }, `${ante.ante}-${item.id}-${item.name}`))) })) : null, ante.packs && ante.packs.length > 0 ? (_jsx(AnalyzerRow, { label: "Packs", children: ante.packs.map((pack) => (_jsx("div", { style: styles.packChip, children: pack }, `${ante.ante}-${pack}`))) })) : null, ante.facts && ante.facts.length > 0 ? (_jsx(AnalyzerRow, { label: "Facts", children: ante.facts.map((fact) => (_jsxs("div", { style: styles.factCard, children: [_jsx("span", { style: styles.factLabel, children: fact.label }), _jsx("span", { style: styles.factValue, children: fact.value })] }, `${ante.ante}-${fact.label}-${fact.value}`))) })) : null] }));
|
|
@@ -147,6 +108,11 @@ const styles = {
|
|
|
147
108
|
borderBottom: "1px solid #1a1a34",
|
|
148
109
|
background: "#0f0f22",
|
|
149
110
|
},
|
|
111
|
+
jamlSection: {
|
|
112
|
+
padding: "6px 8px",
|
|
113
|
+
borderBottom: "1px solid #1a1a34",
|
|
114
|
+
background: "#0f0f22",
|
|
115
|
+
},
|
|
150
116
|
highlightHeader: {
|
|
151
117
|
display: "flex",
|
|
152
118
|
alignItems: "center",
|
|
@@ -3,7 +3,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { Layer } from "../render/Layer.js";
|
|
4
4
|
import { JamlCardRenderer } from "../render/CanvasRenderer.js";
|
|
5
5
|
import { JOKERS, JOKER_FACES, TAROTS_AND_PLANETS, CONSUMABLE_FACES, TAGS, VOUCHERS, BOSSES, EDITION_MAP, SPRITE_SHEETS, STICKER_MAP, RANK_MAP, SUIT_MAP, ENHANCER_MAP, SEAL_MAP, } from "../sprites/spriteData.js";
|
|
6
|
-
import { BalatroItemCategory, isPackedItemValid, packedItemCategory } from "../decode/packedBalatroItem.js";
|
|
7
6
|
function normalizeCardRank(raw) {
|
|
8
7
|
const value = raw.trim().toUpperCase();
|
|
9
8
|
if (value === "A" || value === "ACE")
|
|
@@ -106,21 +105,23 @@ function stripModifiers(name) {
|
|
|
106
105
|
return { baseName: remaining, edition, isEternal, isPerishable, isRental };
|
|
107
106
|
}
|
|
108
107
|
function resolvePackedAnalyzerItem(item, scale) {
|
|
109
|
-
if (typeof item.value !== "number" || !Number.isFinite(item.value)
|
|
108
|
+
if (typeof item.value !== "number" || !Number.isFinite(item.value)) {
|
|
110
109
|
return null;
|
|
111
110
|
}
|
|
112
111
|
const displayName = String(item.name || "").trim();
|
|
113
112
|
const { baseName, edition, isEternal, isPerishable, isRental } = stripModifiers(displayName);
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
// Use motely-wasm enum to determine category — no hand-rolled bitmasks
|
|
114
|
+
const itemType = item.value & 0xffff;
|
|
115
|
+
const catNibble = (itemType >> 12) & 0xf;
|
|
116
|
+
if (catNibble === 5 /* Joker */) {
|
|
116
117
|
const jokerName = JOKERS.some((joker) => joker.name === baseName) ? baseName : displayName;
|
|
117
118
|
if (JOKERS.some((joker) => joker.name === jokerName)) {
|
|
118
119
|
return { kind: "joker", type: "joker", card: { name: jokerName, edition, isEternal, isPerishable, isRental, scale } };
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
if (catNibble === 3 /* Tarot */ ||
|
|
123
|
+
catNibble === 4 /* Planet */ ||
|
|
124
|
+
catNibble === 2 /* Spectral */) {
|
|
124
125
|
const consumableName = TAROTS_AND_PLANETS.some((consumable) => consumable.name === baseName) ? baseName : displayName;
|
|
125
126
|
if (TAROTS_AND_PLANETS.some((consumable) => consumable.name === consumableName)) {
|
|
126
127
|
return { kind: "consumable", type: "consumable", card: { name: consumableName, edition, scale } };
|
|
@@ -6,4 +6,8 @@ export interface JamlAestheticSelectorProps {
|
|
|
6
6
|
className?: string;
|
|
7
7
|
style?: React.CSSProperties;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Pill-toggle selector for seed aesthetic filters.
|
|
11
|
+
* All styling via jimbo.css `.j-aesthetic-selector` / `.j-aesthetic-pill` — zero inline styles.
|
|
12
|
+
*/
|
|
9
13
|
export declare function JamlAestheticSelector({ value, onChange, className, style }: JamlAestheticSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,5 @@
|
|
|
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
|
const AESTHETICS = [
|
|
6
5
|
{ id: "Palindrome", value: 0, label: "Palindrome", desc: "Seeds that read the same forwards and backwards" },
|
|
@@ -10,25 +9,13 @@ const AESTHETICS = [
|
|
|
10
9
|
{ id: "Funny", value: 4, label: "Funny", desc: "Seeds that spell funny words" },
|
|
11
10
|
{ id: "Balatro", value: 5, label: "Balatro", desc: "Seeds referencing the game itself" },
|
|
12
11
|
];
|
|
12
|
+
/**
|
|
13
|
+
* Pill-toggle selector for seed aesthetic filters.
|
|
14
|
+
* All styling via jimbo.css `.j-aesthetic-selector` / `.j-aesthetic-pill` — zero inline styles.
|
|
15
|
+
*/
|
|
13
16
|
export function JamlAestheticSelector({ value, onChange, className, style }) {
|
|
14
|
-
return (_jsxs("div", { className: className
|
|
15
|
-
display: "flex",
|
|
16
|
-
flexDirection: "column",
|
|
17
|
-
gap: 4,
|
|
18
|
-
...style,
|
|
19
|
-
}, children: [_jsx(JimboText, { size: "xs", tone: "grey", children: "Seed Aesthetics" }), _jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: AESTHETICS.map((a) => {
|
|
17
|
+
return (_jsxs("div", { className: `j-aesthetic-selector ${className ?? ""}`, style: style, children: [_jsx(JimboText, { size: "xs", tone: "grey", children: "Seed Aesthetics" }), _jsx("div", { className: "j-aesthetic-selector__list", children: AESTHETICS.map((a) => {
|
|
20
18
|
const isActive = value === a.id;
|
|
21
|
-
return (_jsx("button", { type: "button", onClick: () => onChange(isActive ? null : a.id, a.value), title: a.desc,
|
|
22
|
-
padding: "4px 10px",
|
|
23
|
-
borderRadius: 6,
|
|
24
|
-
border: `2px solid ${isActive ? JimboColorOption.GOLD : JimboColorOption.PANEL_EDGE}`,
|
|
25
|
-
background: isActive ? `${JimboColorOption.GOLD}22` : JimboColorOption.DARKEST,
|
|
26
|
-
color: isActive ? JimboColorOption.GOLD_TEXT : JimboColorOption.GREY,
|
|
27
|
-
cursor: "pointer",
|
|
28
|
-
fontSize: 11,
|
|
29
|
-
fontFamily: "m6x11plus, monospace",
|
|
30
|
-
letterSpacing: 0.5,
|
|
31
|
-
transition: "border-color 100ms, background 100ms",
|
|
32
|
-
}, children: a.label }, a.id));
|
|
19
|
+
return (_jsx("button", { type: "button", className: "j-aesthetic-pill", "data-active": isActive, onClick: () => onChange(isActive ? null : a.id, a.value), title: a.desc, children: a.label }, a.id));
|
|
33
20
|
}) })] }));
|
|
34
21
|
}
|
|
@@ -6,6 +6,12 @@ export interface JamlAnalyzerFullscreenProps {
|
|
|
6
6
|
antes: AnalyzerAnteView[];
|
|
7
7
|
/** Live ctx from useAnalyzer.live; null disables additional stream lanes. */
|
|
8
8
|
live: AnalyzerLive | null;
|
|
9
|
+
/** JAML string for visual breakdown. */
|
|
10
|
+
jaml?: string;
|
|
11
|
+
/** Tally column data for JAML map highlighting. */
|
|
12
|
+
tallyColumns?: number[];
|
|
13
|
+
/** Tally labels mapping to columns. */
|
|
14
|
+
tallyLabels?: string[];
|
|
9
15
|
/** Stream lanes to surface. Defaults to shop + soul jokers. */
|
|
10
16
|
enabledStreams?: AnalyzerStreamKey[];
|
|
11
17
|
/** Called when the user toggles a stream in the picker. Owners persist if desired. */
|
|
@@ -16,6 +22,6 @@ export interface JamlAnalyzerFullscreenProps {
|
|
|
16
22
|
chunkSize?: number;
|
|
17
23
|
className?: string;
|
|
18
24
|
}
|
|
19
|
-
export declare function JamlAnalyzerFullscreen({ antes, live, enabledStreams, onEnabledStreamsChange, hidePicker, chunkSize, className, }: JamlAnalyzerFullscreenProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export declare function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, onEnabledStreamsChange, hidePicker, chunkSize, className, }: JamlAnalyzerFullscreenProps): import("react/jsx-runtime").JSX.Element;
|
|
20
26
|
export type { AnalyzerItem };
|
|
21
27
|
export { ANALYZER_STREAM_META, type AnalyzerStreamKey } from "../hooks/analyzerStreamRegistry.js";
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback,
|
|
3
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { JamlBoss, JamlGameCard, JamlTag, JamlVoucher, resolveAnalyzerShopItem } from "./GameCard.js";
|
|
5
5
|
import { useMotelyStream } from "../hooks/useShopStream.js";
|
|
6
|
+
import { useInfiniteScroll } from "../hooks/useIntersectionObserver.js";
|
|
7
|
+
import { useAnteTracker } from "../ui/hooks.js";
|
|
8
|
+
import { JimboText } from "../ui/jimboText.js";
|
|
6
9
|
import { ANALYZER_STREAM_META, DEFAULT_ENABLED_STREAMS, buildStreamHandle, } from "../hooks/analyzerStreamRegistry.js";
|
|
7
10
|
import { JimboColorOption, withAlpha } from "../ui/tokens.js";
|
|
8
11
|
const C = JimboColorOption;
|
|
@@ -13,43 +16,17 @@ const TONE_COLORS = {
|
|
|
13
16
|
spectral: C.SPECTRAL_BUTTON,
|
|
14
17
|
default: C.GOLD_TEXT,
|
|
15
18
|
};
|
|
16
|
-
|
|
19
|
+
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
20
|
+
export function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, onEnabledStreamsChange, hidePicker = false, chunkSize = 12, className = "", }) {
|
|
17
21
|
const [internalEnabled, setInternalEnabled] = useState(enabledStreams ?? DEFAULT_ENABLED_STREAMS);
|
|
18
22
|
const effectiveEnabled = enabledStreams ?? internalEnabled;
|
|
19
23
|
const setEnabled = useCallback((next) => {
|
|
20
24
|
setInternalEnabled(next);
|
|
21
25
|
onEnabledStreamsChange?.(next);
|
|
22
26
|
}, [onEnabledStreamsChange]);
|
|
23
|
-
const scrollRef =
|
|
24
|
-
const sectionRefs = useRef(new Map());
|
|
25
|
-
const [currentAnte, setCurrentAnte] = useState(antes[0]?.ante ?? 1);
|
|
27
|
+
const { currentAnte, scrollRef, scrollToAnte, registerAnteRef } = useAnteTracker(antes);
|
|
26
28
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
27
|
-
|
|
28
|
-
const root = scrollRef.current;
|
|
29
|
-
if (!root)
|
|
30
|
-
return;
|
|
31
|
-
const observer = new IntersectionObserver((entries) => {
|
|
32
|
-
const top = entries
|
|
33
|
-
.filter((e) => e.isIntersecting)
|
|
34
|
-
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
|
|
35
|
-
if (!top)
|
|
36
|
-
return;
|
|
37
|
-
const ante = Number(top.target.dataset.ante);
|
|
38
|
-
if (!Number.isNaN(ante))
|
|
39
|
-
setCurrentAnte(ante);
|
|
40
|
-
}, { root, threshold: [0.4, 0.6, 0.8] });
|
|
41
|
-
sectionRefs.current.forEach((el) => observer.observe(el));
|
|
42
|
-
return () => observer.disconnect();
|
|
43
|
-
}, [antes]);
|
|
44
|
-
const scrollToAnte = useCallback((ante) => {
|
|
45
|
-
sectionRefs.current.get(ante)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
46
|
-
}, []);
|
|
47
|
-
return (_jsxs("div", { className: className, style: styles.root, children: [_jsx("div", { ref: scrollRef, style: styles.scroller, children: antes.map((ante) => (_jsx(AnteSection, { ante: ante, live: live, enabledStreams: effectiveEnabled, chunkSize: chunkSize, registerRef: (el) => {
|
|
48
|
-
if (el)
|
|
49
|
-
sectionRefs.current.set(ante.ante, el);
|
|
50
|
-
else
|
|
51
|
-
sectionRefs.current.delete(ante.ante);
|
|
52
|
-
} }, ante.ante))) }), _jsx(SideRail, { antes: antes.map((a) => a.ante), currentAnte: currentAnte, onJump: scrollToAnte }), !hidePicker && (_jsx(StreamPicker, { enabled: effectiveEnabled, onChange: setEnabled, open: pickerOpen, onToggle: () => setPickerOpen((v) => !v) }))] }));
|
|
29
|
+
return (_jsxs("div", { className: className, style: styles.root, children: [_jsxs("div", { ref: scrollRef, style: styles.scroller, children: [jaml && (_jsxs("section", { style: { ...styles.section, scrollSnapAlign: "start", justifyContent: 'center' }, children: [_jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: styles.anteLabel, children: "JAML" }), _jsx("div", { style: styles.anteNumber, children: "MAP" })] }), _jsx(JamlMapPreview, { jaml: jaml, tallyColumns: tallyColumns, tallyLabels: tallyLabels }), _jsx("div", { style: { marginTop: 24, textAlign: 'center', opacity: 0.6 }, children: _jsx(JimboText, { size: "xs", tone: "grey", children: "Scroll down to explore seed details" }) })] })), antes.map((ante) => (_jsx(AnteSection, { ante: ante, live: live, enabledStreams: effectiveEnabled, chunkSize: chunkSize, registerRef: (el) => registerAnteRef(ante.ante, el) }, ante.ante)))] }), _jsx(SideRail, { antes: antes.map((a) => a.ante), currentAnte: currentAnte, onJump: scrollToAnte }), !hidePicker && (_jsx(StreamPicker, { enabled: effectiveEnabled, onChange: setEnabled, open: pickerOpen, onToggle: () => setPickerOpen((v) => !v) }))] }));
|
|
53
30
|
}
|
|
54
31
|
function AnteSection({ ante, live, enabledStreams, chunkSize, registerRef }) {
|
|
55
32
|
return (_jsxs("section", { ref: registerRef, "data-ante": ante.ante, style: styles.section, children: [_jsxs("header", { style: styles.header, children: [_jsxs("div", { children: [_jsx("div", { style: styles.anteLabel, children: "Ante" }), _jsx("div", { style: styles.anteNumber, children: ante.ante })] }), ante.voucher && (_jsxs("div", { style: styles.voucherBlock, children: [_jsx(JamlVoucher, { voucherName: ante.voucher, scale: 0.85 }), _jsx("div", { style: styles.voucherCaption, children: ante.voucher })] }))] }), _jsxs("div", { style: styles.blindRow, children: [_jsx(BlindCell, { label: "Small", tag: ante.smallBlindTag }), _jsx(BlindCell, { label: "Big", tag: ante.bigBlindTag }), ante.boss && (_jsxs("div", { style: styles.bossCell, children: [_jsx("div", { style: styles.cellLabel, children: "Boss" }), _jsx(JamlBoss, { bossName: ante.boss, scale: 0.7 }), _jsx("div", { style: styles.cellCaption, children: ante.boss })] }))] }), ante.packs && ante.packs.length > 0 && (_jsxs("div", { style: styles.streamLane, children: [_jsx("div", { style: styles.streamLabel, children: "Packs" }), _jsx("div", { style: styles.packRow, children: ante.packs.map((pack, i) => (_jsx("div", { style: styles.packPill, children: pack }, `${ante.ante}-pack-${i}`))) })] })), enabledStreams.map((key) => {
|
|
@@ -78,24 +55,18 @@ function BlindCell({ label, tag }) {
|
|
|
78
55
|
return (_jsxs("div", { style: styles.blindCell, children: [_jsx("div", { style: styles.cellLabel, children: label }), _jsx(JamlTag, { tagName: tag, scale: 0.7 }), _jsx("div", { style: styles.cellCaption, children: tag })] }));
|
|
79
56
|
}
|
|
80
57
|
function ShopRow({ items, desired, loadingMore, ready, onPullMore }) {
|
|
81
|
-
const sentinelRef = useRef(null);
|
|
82
58
|
const lastTriggerRef = useRef(0);
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
59
|
+
const throttlePull = useCallback(() => {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (now - lastTriggerRef.current < 200)
|
|
86
62
|
return;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
}, { root: el.parentElement, threshold: 0.1, rootMargin: "0px 200px 0px 0px" });
|
|
96
|
-
observer.observe(el);
|
|
97
|
-
return () => observer.disconnect();
|
|
98
|
-
}, [ready, loadingMore, onPullMore]);
|
|
63
|
+
lastTriggerRef.current = now;
|
|
64
|
+
onPullMore();
|
|
65
|
+
}, [onPullMore]);
|
|
66
|
+
const sentinelRef = useInfiniteScroll(throttlePull, {
|
|
67
|
+
threshold: 0.1,
|
|
68
|
+
rootMargin: "0px 200px 0px 0px",
|
|
69
|
+
}, ready && !loadingMore);
|
|
99
70
|
return (_jsxs("div", { style: styles.shopRow, children: [items.map((item) => (_jsx(ShopItem, { item: item, desired: desired.has(item.name.toLowerCase()) }, item.id))), _jsx("div", { ref: sentinelRef, style: styles.sentinel, children: loadingMore ? "…" : "" })] }));
|
|
100
71
|
}
|
|
101
72
|
function ShopItem({ item, desired }) {
|
|
@@ -17,7 +17,7 @@ function TallyBar({ value, max }) {
|
|
|
17
17
|
transition: "width 200ms ease",
|
|
18
18
|
} }) }));
|
|
19
19
|
}
|
|
20
|
-
function ResultsView({ results }) {
|
|
20
|
+
function ResultsView({ results, jaml }) {
|
|
21
21
|
const [expanded, setExpanded] = useState(null);
|
|
22
22
|
if (results.length === 0) {
|
|
23
23
|
return (_jsx("div", { style: {
|
|
@@ -30,11 +30,10 @@ function ResultsView({ results }) {
|
|
|
30
30
|
textAlign: "center",
|
|
31
31
|
}, children: "No results yet. Run a search to find seeds." }));
|
|
32
32
|
}
|
|
33
|
-
const labels = results[0]?.tallyLabels ?? [];
|
|
34
33
|
const maxScore = Math.max(...results.map((r) => r.score ?? 0));
|
|
35
34
|
return (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: results.map((result) => {
|
|
36
35
|
const isOpen = expanded === result.seed;
|
|
37
|
-
const hasTally = result.tallyColumns && result.tallyColumns.length > 0
|
|
36
|
+
const hasTally = result.tallyColumns && result.tallyColumns.length > 0;
|
|
38
37
|
return (_jsxs("div", { style: {
|
|
39
38
|
borderRadius: 10,
|
|
40
39
|
border: `1px solid ${isOpen ? JimboColorOption.GOLD + "55" : JimboColorOption.PANEL_EDGE}`,
|
|
@@ -64,29 +63,18 @@ function ResultsView({ results }) {
|
|
|
64
63
|
color: result.score > 0 ? JimboColorOption.GREEN_TEXT : JimboColorOption.GREY,
|
|
65
64
|
minWidth: 36,
|
|
66
65
|
textAlign: "right",
|
|
67
|
-
}, children: result.score })] })) : null, hasTally ? (_jsx("span", { style: { fontSize: 11, color: JimboColorOption.GREY, marginLeft: 2 }, children: isOpen ? "▲" : "▼" })) : null] }), isOpen && hasTally ? (
|
|
66
|
+
}, children: result.score })] })) : null, hasTally ? (_jsx("span", { style: { fontSize: 11, color: JimboColorOption.GREY, marginLeft: 2 }, children: isOpen ? "▲" : "▼" })) : null] }), isOpen && hasTally ? (_jsxs("div", { style: {
|
|
68
67
|
borderTop: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
69
|
-
padding: "
|
|
68
|
+
padding: "4px",
|
|
70
69
|
display: "flex",
|
|
71
70
|
flexDirection: "column",
|
|
72
|
-
gap:
|
|
73
|
-
}, children:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
minWidth: 140,
|
|
80
|
-
overflow: "hidden",
|
|
81
|
-
textOverflow: "ellipsis",
|
|
82
|
-
whiteSpace: "nowrap",
|
|
83
|
-
}, children: label }), _jsx(TallyBar, { value: val, max: maxVal }), _jsx("span", { style: {
|
|
84
|
-
fontSize: 11,
|
|
85
|
-
color: val > 0 ? JimboColorOption.GREEN_TEXT : JimboColorOption.DARK_GREY,
|
|
86
|
-
minWidth: 24,
|
|
87
|
-
textAlign: "right",
|
|
88
|
-
}, children: val })] }, label));
|
|
89
|
-
}) })) : null] }, result.seed));
|
|
71
|
+
gap: 8,
|
|
72
|
+
}, children: [_jsx(JamlMapPreview, { jaml: jaml, tallyColumns: result.tallyColumns, tallyLabels: result.tallyLabels }), _jsxs("div", { style: { padding: "4px 8px 8px", display: "flex", flexDirection: "column", gap: 5 }, children: [_jsx("span", { style: { fontSize: 8, color: JimboColorOption.GREY, letterSpacing: "0.08em", textTransform: "uppercase" }, children: "RAW TALLY DATA" }), (result.tallyLabels ?? []).map((label, i) => {
|
|
73
|
+
const val = result.tallyColumns[i] ?? 0;
|
|
74
|
+
if (val === 0)
|
|
75
|
+
return null;
|
|
76
|
+
return (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { fontSize: 10, color: JimboColorOption.WHITE, flex: 1 }, children: label }), _jsx("span", { style: { fontSize: 10, color: JimboColorOption.GREEN_TEXT }, children: val })] }, label));
|
|
77
|
+
})] })] })) : null] }, result.seed));
|
|
90
78
|
}) }));
|
|
91
79
|
}
|
|
92
80
|
export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", searchResults = [], className = "", style, title = "JAML IDE", actions, codePlaceholder = "Enter JAML...", onSearch, isSearching = false, visualFilter, onVisualFilterChange, }) {
|
|
@@ -159,5 +147,5 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
159
147
|
padding: "10px 14px",
|
|
160
148
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
161
149
|
background: JimboColorOption.TEAL_GREY,
|
|
162
|
-
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: "normal", fontFamily: "m6x11plus, monospace", color: JimboColorOption.GOLD_TEXT }, children: title }), _jsx("div", { style: { fontSize: 11, color: JimboColorOption.GREY }, children: "Jimbo's Ante Markup Language" })] }), actions ? _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: actions }) : null] }), _jsx(JamlIdeToolbar, { mode: mode, onModeChange: setMode, resultCount: results.length, onSearch: onSearch, isSearching: isSearching }), _jsxs("div", { style: { flex: 1, minHeight: 0, overflow: "auto", background: JimboColorOption.DARKEST }, children: [mode === "visual" ? (_jsx(JamlIdeVisual, { filter: activeFilter, onChange: handleVisualFilterChange })) : null, mode === "code" ? (_jsx(JamlCodeEditor, { value: text, onChange: handleTextChange, placeholder: codePlaceholder })) : null, mode === "map" ? _jsx(JamlMapPreview, { jaml: text }) : null, mode === "results" ? (_jsx("div", { style: { padding: 12 }, children: _jsx(ResultsView, { results: results }) })) : null] })] }));
|
|
150
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: "normal", fontFamily: "m6x11plus, monospace", color: JimboColorOption.GOLD_TEXT }, children: title }), _jsx("div", { style: { fontSize: 11, color: JimboColorOption.GREY }, children: "Jimbo's Ante Markup Language" })] }), actions ? _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: actions }) : null] }), _jsx(JamlIdeToolbar, { mode: mode, onModeChange: setMode, resultCount: results.length, onSearch: onSearch, isSearching: isSearching }), _jsxs("div", { style: { flex: 1, minHeight: 0, overflow: "auto", background: JimboColorOption.DARKEST }, children: [mode === "visual" ? (_jsx(JamlIdeVisual, { filter: activeFilter, onChange: handleVisualFilterChange })) : null, mode === "code" ? (_jsx(JamlCodeEditor, { value: text, onChange: handleTextChange, placeholder: codePlaceholder })) : null, mode === "map" ? _jsx(JamlMapPreview, { jaml: text }) : null, mode === "results" ? (_jsx("div", { style: { padding: 12 }, children: _jsx(ResultsView, { results: results, jaml: text }) })) : null] })] }));
|
|
163
151
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { useJamlIdeDrag } from "../ui/hooks.js";
|
|
4
5
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
6
|
import { JimboSprite } from "../ui/sprites.js";
|
|
6
7
|
const C = JimboColorOption;
|
|
@@ -192,65 +193,11 @@ function TopMatter({ filter, onChange, }) {
|
|
|
192
193
|
} })] }));
|
|
193
194
|
}
|
|
194
195
|
export function JamlIdeVisual({ filter, onChange, onEditClause, onAddClause }) {
|
|
195
|
-
const [drag, setDrag] = useState(null);
|
|
196
|
-
const [hoverZone, setHoverZone] = useState(null);
|
|
197
196
|
const rootRef = useRef(null);
|
|
197
|
+
const { drag, hoverZone, onDragStart } = useJamlIdeDrag(filter, onChange, rootRef);
|
|
198
198
|
const removeClause = (zone, id) => {
|
|
199
199
|
onChange({ ...filter, [zone]: filter[zone].filter((c) => c.id !== id) });
|
|
200
200
|
};
|
|
201
|
-
const onDragStart = (e, clause, fromZone) => {
|
|
202
|
-
// Don't preventDefault; we want clicks to still fire if there's no actual drag.
|
|
203
|
-
const t = "touches" in e ? e.touches[0] : e;
|
|
204
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
205
|
-
setDrag({
|
|
206
|
-
clause,
|
|
207
|
-
fromZone,
|
|
208
|
-
x: t.clientX,
|
|
209
|
-
y: t.clientY,
|
|
210
|
-
offX: t.clientX - rect.left,
|
|
211
|
-
offY: t.clientY - rect.top,
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
useEffect(() => {
|
|
215
|
-
if (!drag)
|
|
216
|
-
return;
|
|
217
|
-
const move = (e) => {
|
|
218
|
-
const t = "touches" in e ? e.touches[0] : e;
|
|
219
|
-
setDrag((d) => d && { ...d, x: t.clientX, y: t.clientY });
|
|
220
|
-
const rails = rootRef.current?.querySelectorAll("[data-zone]") ?? [];
|
|
221
|
-
let found = null;
|
|
222
|
-
for (const r of rails) {
|
|
223
|
-
const rc = r.getBoundingClientRect();
|
|
224
|
-
if (t.clientX >= rc.left && t.clientX <= rc.right && t.clientY >= rc.top && t.clientY <= rc.bottom) {
|
|
225
|
-
found = r.getAttribute("data-zone");
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
setHoverZone(found);
|
|
230
|
-
};
|
|
231
|
-
const up = () => {
|
|
232
|
-
if (hoverZone && hoverZone !== drag.fromZone) {
|
|
233
|
-
const to = hoverZone;
|
|
234
|
-
onChange({
|
|
235
|
-
...filter,
|
|
236
|
-
[drag.fromZone]: filter[drag.fromZone].filter((c) => c.id !== drag.clause.id),
|
|
237
|
-
[to]: [...filter[to], { ...drag.clause }],
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
setDrag(null);
|
|
241
|
-
setHoverZone(null);
|
|
242
|
-
};
|
|
243
|
-
window.addEventListener("mousemove", move);
|
|
244
|
-
window.addEventListener("mouseup", up);
|
|
245
|
-
window.addEventListener("touchmove", move, { passive: false });
|
|
246
|
-
window.addEventListener("touchend", up);
|
|
247
|
-
return () => {
|
|
248
|
-
window.removeEventListener("mousemove", move);
|
|
249
|
-
window.removeEventListener("mouseup", up);
|
|
250
|
-
window.removeEventListener("touchmove", move);
|
|
251
|
-
window.removeEventListener("touchend", up);
|
|
252
|
-
};
|
|
253
|
-
}, [drag, hoverZone, filter, onChange]);
|
|
254
201
|
return (_jsxs("div", { ref: rootRef, style: {
|
|
255
202
|
display: "flex",
|
|
256
203
|
flexDirection: "column",
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export interface JamlMapPreviewProps {
|
|
2
|
+
/** The raw JAML string to parse and visualize. */
|
|
2
3
|
jaml: string;
|
|
3
4
|
className?: string;
|
|
4
5
|
emptyMessage?: string;
|
|
6
|
+
tallyColumns?: number[];
|
|
7
|
+
tallyLabels?: string[];
|
|
8
|
+
/** Reduces padding and sizes for sidebar/explorer usage. */
|
|
9
|
+
compact?: boolean;
|
|
5
10
|
}
|
|
6
|
-
export declare function JamlMapPreview({ jaml, className, emptyMessage, }: JamlMapPreviewProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function JamlMapPreview({ jaml, className, emptyMessage, tallyColumns, tallyLabels, compact, }: JamlMapPreviewProps): import("react/jsx-runtime").JSX.Element;
|