jaml-ui 0.17.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +16 -5
- package/dist/components/JamlAnalyzerFullscreen.d.ts +4 -1
- package/dist/components/JamlAnalyzerFullscreen.js +2 -2
- package/dist/components/JamlCurator.d.ts +4 -0
- package/dist/components/JamlCurator.js +63 -0
- package/dist/components/JamlCurator.stories.d.ts +6 -0
- package/dist/components/JamlCurator.stories.js +14 -0
- package/dist/components/JamlIde.js +1 -1
- package/dist/components/JamlIdeVisual.js +12 -20
- package/dist/components/jamlMap/CategoryPicker.js +38 -120
- package/dist/components/jamlMap/JamlMapEditor.d.ts +11 -0
- package/dist/components/jamlMap/JamlMapEditor.js +170 -0
- package/dist/components/jamlMap/JamlMapEditor.stories.d.ts +7 -0
- package/dist/components/jamlMap/JamlMapEditor.stories.js +26 -0
- package/dist/components/jamlMap/JokerPicker.js +28 -157
- package/dist/components/jamlMap/MysterySlot.js +32 -5
- package/dist/components/jamlMap/MysterySlot.stories.d.ts +7 -0
- package/dist/components/jamlMap/MysterySlot.stories.js +31 -0
- package/dist/components/jamlMap/index.d.ts +1 -1
- package/dist/components/jamlMap/index.js +1 -1
- package/dist/hooks/useAnalyzer.d.ts +4 -8
- package/dist/hooks/useAnalyzer.js +5 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/stories/Button.d.ts +15 -0
- package/dist/stories/Button.js +7 -0
- package/dist/stories/Button.stories.d.ts +24 -0
- package/dist/stories/Button.stories.js +50 -0
- package/dist/stories/Header.d.ts +12 -0
- package/dist/stories/Header.js +4 -0
- package/dist/stories/Header.stories.d.ts +18 -0
- package/dist/stories/Header.stories.js +26 -0
- package/dist/stories/Page.d.ts +3 -0
- package/dist/stories/Page.js +8 -0
- package/dist/stories/Page.stories.d.ts +12 -0
- package/dist/stories/Page.stories.js +24 -0
- package/dist/ui/Jimbo.stories.d.ts +7 -0
- package/dist/ui/Jimbo.stories.js +28 -0
- package/dist/ui/hooks.js +3 -1
- package/dist/ui/jimbo.css +39 -11
- package/dist/ui/jimboText.d.ts +5 -3
- package/dist/ui/jimboText.js +7 -2
- package/dist/ui/panel.d.ts +1 -1
- package/dist/ui/panel.js +7 -5
- package/package.json +16 -3
package/DESIGN.md
CHANGED
|
@@ -147,13 +147,17 @@ Must-clause items glow blue. Should-clause items glow gold/green. Non-matching i
|
|
|
147
147
|
|
|
148
148
|
## Typography
|
|
149
149
|
|
|
150
|
-
m6x11plus (m6x11plusplus.otf) is the ONLY font. It is a single-weight pixel font. NEVER apply font-weight bold, semibold, or any weight other than 400. Bold makes it look muddy.
|
|
150
|
+
m6x11plus (m6x11plusplus.otf) is the ONLY font. It is a single-weight pixel font. NEVER apply font-weight bold, semibold, or any weight other than 400. Bold makes it look muddy. NEVER USE HEAVY!
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
Text is NEVER ALL CAPS. Use proper Title Case or Sentence case. Seed codes use the display size (26px) in gold (#e4b643).
|
|
153
|
+
|
|
154
|
+
Contrast is critical. NEVER make grey text on top of a grey background. If using a dark grey background, use white or light contrasting text.
|
|
153
155
|
|
|
154
156
|
## Layout
|
|
155
157
|
|
|
156
|
-
Target: Minimum 375px portrait width. Components must scale gracefully using relative units and flexible layouts. Avoid fixed widths that break at 375px.
|
|
158
|
+
Target: Minimum 375px portrait width. Components must scale gracefully using relative units and flexible layouts. Avoid fixed widths that break at 375px.
|
|
159
|
+
|
|
160
|
+
Vertical snap-scroll for ante pages using magnetic scroll-snapping (`scroll-snap-type: y mandatory`, `scroll-snap-align: start`). Horizontal swipe for seed navigation. NO visible scrollbars globally — use `::-webkit-scrollbar { display: none; }` and `-ms-overflow-style: none`.
|
|
157
161
|
|
|
158
162
|
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
163
|
|
|
@@ -181,6 +185,10 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
|
|
|
181
185
|
|
|
182
186
|
**GlowRing:** Pulsing outline around JAML-hit items. `0 0 0 2px [color], 0 0 10px [color]`. Animation: opacity 0.55 → 1.0 over 1.6s ease-in-out infinite.
|
|
183
187
|
|
|
188
|
+
**Card Hover Juice:** Cards receive a 3D magnetic tilt plus a "juice-up" scale animation (`scale(1.05) translateY(-2px)`) on hover to mimic a physical interaction.
|
|
189
|
+
|
|
190
|
+
**Font Dance:** Text can be made to wiggle with a staggered `translateY` offset animation across characters (`.j-font-dance-char`), creating a cozy pixel font animation.
|
|
191
|
+
|
|
184
192
|
**SeedPagerHeader:** Three columns: [left stride arrow] [identity panel with seed + copy + deck/stake] [right stride arrow]. Stride arrows are tall red bars with dark-red inset shadow. Identity panel is translucent with counter pip.
|
|
185
193
|
|
|
186
194
|
## Do's and Don'ts
|
|
@@ -188,8 +196,11 @@ JAML-hit items get a GlowRing: `box-shadow: 0 0 0 2px [color], 0 0 10px [color]`
|
|
|
188
196
|
- DO use m6x11plus for everything except code/monospace.
|
|
189
197
|
- DO design for 375px portrait.
|
|
190
198
|
- DO use translateY + box-shadow for button depth. Not CSS 3D transforms.
|
|
191
|
-
- DON'T use font-weight bold. m6x11plus is single-weight.
|
|
199
|
+
- DON'T use font-weight bold or heavy. m6x11plus is single-weight.
|
|
200
|
+
- DON'T use ALL CAPS. It is considered an embellishment and ruins the aesthetic.
|
|
201
|
+
- DON'T put grey text on top of a grey background.
|
|
192
202
|
- DON'T use fat padding or margins. Balatro UI is dense and cozy.
|
|
193
|
-
- DON'T add
|
|
203
|
+
- DON'T add visible scrollbars. Vertical magnetic snap-scroll + horizontal swipe only.
|
|
194
204
|
- DON'T use rounded corners larger than 10px. Balatro is chunky, not bubbly.
|
|
195
205
|
- DON'T use blur-based shadows for depth. Use solid colored box-shadows 80% opaque.
|
|
206
|
+
- DON'T use redundant JS wrappers for `motely-wasm`. Import globally and `motely.boot()` once.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import type { AnalyzerAnteView, AnalyzerItem } from "./AnalyzerExplorer.js";
|
|
2
3
|
import type { AnalyzerLive } from "../hooks/useAnalyzer.js";
|
|
3
4
|
import { type AnalyzerStreamKey } from "../hooks/analyzerStreamRegistry.js";
|
|
@@ -21,7 +22,9 @@ export interface JamlAnalyzerFullscreenProps {
|
|
|
21
22
|
/** Pull size on each lazy load. */
|
|
22
23
|
chunkSize?: number;
|
|
23
24
|
className?: string;
|
|
25
|
+
/** Custom top page to render as Slide 0 */
|
|
26
|
+
topPage?: React.ReactNode;
|
|
24
27
|
}
|
|
25
|
-
export declare function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, onEnabledStreamsChange, hidePicker, chunkSize, className, }: JamlAnalyzerFullscreenProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, onEnabledStreamsChange, hidePicker, chunkSize, className, topPage, }: JamlAnalyzerFullscreenProps): import("react/jsx-runtime").JSX.Element;
|
|
26
29
|
export type { AnalyzerItem };
|
|
27
30
|
export { ANALYZER_STREAM_META, type AnalyzerStreamKey } from "../hooks/analyzerStreamRegistry.js";
|
|
@@ -17,7 +17,7 @@ const TONE_COLORS = {
|
|
|
17
17
|
default: C.GOLD_TEXT,
|
|
18
18
|
};
|
|
19
19
|
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
20
|
-
export function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, onEnabledStreamsChange, hidePicker = false, chunkSize = 12, className = "", }) {
|
|
20
|
+
export function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyLabels, enabledStreams, onEnabledStreamsChange, hidePicker = false, chunkSize = 12, className = "", topPage, }) {
|
|
21
21
|
const [internalEnabled, setInternalEnabled] = useState(enabledStreams ?? DEFAULT_ENABLED_STREAMS);
|
|
22
22
|
const effectiveEnabled = enabledStreams ?? internalEnabled;
|
|
23
23
|
const setEnabled = useCallback((next) => {
|
|
@@ -26,7 +26,7 @@ export function JamlAnalyzerFullscreen({ antes, live, jaml, tallyColumns, tallyL
|
|
|
26
26
|
}, [onEnabledStreamsChange]);
|
|
27
27
|
const { currentAnte, scrollRef, scrollToAnte, registerAnteRef } = useAnteTracker(antes);
|
|
28
28
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
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) }))] }));
|
|
29
|
+
return (_jsxs("div", { className: className, style: styles.root, children: [_jsxs("div", { ref: scrollRef, style: styles.scroller, children: [topPage ? topPage : 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) }))] }));
|
|
30
30
|
}
|
|
31
31
|
function AnteSection({ ante, live, enabledStreams, chunkSize, registerRef }) {
|
|
32
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) => {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { JimboButton, JimboPanel } from "../ui/panel.js";
|
|
5
|
+
import { JimboText } from "../ui/jimboText.js";
|
|
6
|
+
import { JimboColorOption } from "../ui/tokens.js";
|
|
7
|
+
import { JimboFlankNav } from "../ui/jimboFlankNav.js";
|
|
8
|
+
import { JamlMapEditor } from "./jamlMap/JamlMapEditor.js";
|
|
9
|
+
import { JamlAnalyzerFullscreen } from "./JamlAnalyzerFullscreen.js";
|
|
10
|
+
import { useSearch } from "../hooks/useSearch.js";
|
|
11
|
+
import { useAnalyzer } from "../hooks/useAnalyzer.js";
|
|
12
|
+
import { JamlSpeedometer } from "./JamlSpeedometer.js";
|
|
13
|
+
const C = JimboColorOption;
|
|
14
|
+
export function JamlCurator({ motelyWasmUrl }) {
|
|
15
|
+
// Use map editor by default to generate JAML
|
|
16
|
+
const [jamlText, setJamlText] = useState("");
|
|
17
|
+
const search = useSearch(motelyWasmUrl);
|
|
18
|
+
const analyzer = useAnalyzer();
|
|
19
|
+
// Search results pagination
|
|
20
|
+
const [resultIndex, setResultIndex] = useState(0);
|
|
21
|
+
const isSearching = search.status === "running";
|
|
22
|
+
const handleSearch = () => {
|
|
23
|
+
if (isSearching) {
|
|
24
|
+
search.cancel();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
setResultIndex(0);
|
|
28
|
+
search.start(jamlText, 1_000_000);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const currentSeed = search.results[resultIndex]?.seed;
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (currentSeed) {
|
|
34
|
+
analyzer.analyze(currentSeed, "Red", "White", jamlText);
|
|
35
|
+
}
|
|
36
|
+
}, [currentSeed, jamlText]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
37
|
+
// Map Editor changes
|
|
38
|
+
const handleMapChange = (jamlString) => {
|
|
39
|
+
setJamlText(jamlString);
|
|
40
|
+
};
|
|
41
|
+
return (_jsx("div", { style: {
|
|
42
|
+
width: "100%",
|
|
43
|
+
maxWidth: 375,
|
|
44
|
+
height: "100svh",
|
|
45
|
+
margin: "0 auto",
|
|
46
|
+
position: "relative",
|
|
47
|
+
background: C.DARKEST,
|
|
48
|
+
overflow: "hidden",
|
|
49
|
+
borderLeft: `1px solid ${C.PANEL_EDGE}`,
|
|
50
|
+
borderRight: `1px solid ${C.PANEL_EDGE}`,
|
|
51
|
+
boxShadow: `0 0 20px rgba(0,0,0,0.5)`,
|
|
52
|
+
}, children: _jsx(JamlAnalyzerFullscreen, { antes: analyzer.antes, live: analyzer.live, hidePicker: true, topPage: _jsxs("section", { style: {
|
|
53
|
+
width: "100%",
|
|
54
|
+
height: "100svh",
|
|
55
|
+
scrollSnapAlign: "start",
|
|
56
|
+
display: "flex",
|
|
57
|
+
flexDirection: "column",
|
|
58
|
+
gap: 12,
|
|
59
|
+
padding: "16px 12px 24px",
|
|
60
|
+
boxSizing: "border-box",
|
|
61
|
+
borderBottom: `2px solid ${C.GOLD}`,
|
|
62
|
+
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(JimboText, { size: "lg", tone: "gold", children: "JAML Curator" }), _jsx(JimboButton, { tone: isSearching ? "red" : "green", size: "sm", onClick: handleSearch, children: isSearching ? "STOP" : "SEARCH" })] }), _jsx("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto' }, className: "hide-scrollbar", children: _jsx(JamlMapEditor, { onChange: handleMapChange }) }), _jsx("div", { style: { flexShrink: 0 }, children: _jsx(JamlSpeedometer, { status: search.status, seedsPerSecond: search.seedsPerSecond, totalSearched: search.totalSearched, matchingSeeds: search.matchingSeeds }) }), _jsx("div", { style: { flexShrink: 0 }, children: _jsx(JimboPanel, { children: search.results.length === 0 ? (_jsx(JimboText, { size: "sm", tone: "grey", className: "j-text-center", children: isSearching ? "Searching..." : "No results yet." })) : (_jsxs("div", { className: "j-flex-col j-gap-sm", children: [_jsxs("div", { className: "j-flex j-items-center j-justify-between", children: [_jsx(JimboText, { size: "xs", tone: "grey", children: "SEED MATCHES" }), _jsxs(JimboText, { size: "xs", tone: "gold", children: [search.matchingSeeds, " FOUND"] })] }), _jsx(JimboFlankNav, { canPrev: resultIndex > 0, canNext: resultIndex < search.results.length - 1, onPrev: () => setResultIndex(i => Math.max(0, i - 1)), onNext: () => setResultIndex(i => Math.min(search.results.length - 1, i + 1)), children: _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "lg", tone: "gold", style: { letterSpacing: 2 }, children: currentSeed }), _jsx(JimboButton, { tone: "grey", size: "xs", children: "Copy Seed" })] }) }), _jsx(JimboText, { size: "micro", tone: "grey", className: "j-text-center", style: { opacity: 0.7, marginTop: 8 }, children: "\u25BC SWIPE DOWN FOR ANTES \u25BC" })] })) }) })] }) }) }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { JamlCurator } from "./JamlCurator.js";
|
|
2
|
+
const meta = {
|
|
3
|
+
title: "JAML/JamlCurator",
|
|
4
|
+
component: JamlCurator,
|
|
5
|
+
parameters: {
|
|
6
|
+
layout: "fullscreen",
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
export const Default = {
|
|
11
|
+
args: {
|
|
12
|
+
motelyWasmUrl: "https://unpkg.com/@nims11/motely@0.2.2/motely_bg.wasm",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -69,7 +69,7 @@ function ResultsView({ results, jaml }) {
|
|
|
69
69
|
display: "flex",
|
|
70
70
|
flexDirection: "column",
|
|
71
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:
|
|
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: 9, color: JimboColorOption.WHITE, opacity: 0.8 }, children: "Raw Tally Data" }), (result.tallyLabels ?? []).map((label, i) => {
|
|
73
73
|
const val = result.tallyColumns[i] ?? 0;
|
|
74
74
|
if (val === 0)
|
|
75
75
|
return null;
|
|
@@ -6,9 +6,9 @@ import { JimboColorOption } from "../ui/tokens.js";
|
|
|
6
6
|
import { JimboSprite } from "../ui/sprites.js";
|
|
7
7
|
const C = JimboColorOption;
|
|
8
8
|
const ZONE_META = {
|
|
9
|
-
must: { label: "
|
|
10
|
-
should: { label: "
|
|
11
|
-
mustnot: { label: "
|
|
9
|
+
must: { label: "Must", hint: "Seed must contain all of these.", color: C.BLUE, accent: "#4db5ff" },
|
|
10
|
+
should: { label: "Should", hint: "Bonus points per match.", color: C.RED, accent: "#ff8076" },
|
|
11
|
+
mustnot: { label: "Must Not", hint: "Rejected if any appear.", color: C.ORANGE, accent: "#ffb84d" },
|
|
12
12
|
};
|
|
13
13
|
function clauseSpriteSheet(type) {
|
|
14
14
|
if (type === "joker" ||
|
|
@@ -127,8 +127,7 @@ function MysteryAddTile({ zone, onTap }) {
|
|
|
127
127
|
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
128
128
|
fontSize: 12,
|
|
129
129
|
color: z.accent,
|
|
130
|
-
|
|
131
|
-
}, children: ["ADD TO ", z.label] })] }));
|
|
130
|
+
}, children: ["Add to ", z.label] })] }));
|
|
132
131
|
}
|
|
133
132
|
function ZoneRail({ zone, clauses, onAdd, onRemove, onEdit, onDragStart, highlight, }) {
|
|
134
133
|
const z = ZONE_META[zone];
|
|
@@ -140,23 +139,16 @@ function ZoneRail({ zone, clauses, onAdd, onRemove, onEdit, onDragStart, highlig
|
|
|
140
139
|
transition: "background 100ms, border-color 100ms",
|
|
141
140
|
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [_jsx("div", { style: {
|
|
142
141
|
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
143
|
-
fontSize:
|
|
144
|
-
padding: "2px
|
|
142
|
+
fontSize: 12,
|
|
143
|
+
padding: "2px 6px",
|
|
145
144
|
background: z.color,
|
|
146
145
|
color: C.WHITE,
|
|
147
146
|
borderRadius: 3,
|
|
148
|
-
letterSpacing: 2,
|
|
149
147
|
boxShadow: `0 2px 0 ${C.BLACK}`,
|
|
150
148
|
}, children: z.label }), _jsx("div", { style: { flex: 1, height: 2, background: `${z.color}55`, borderRadius: 1 } }), _jsx("div", { style: { fontFamily: "m6x11plus, ui-monospace, monospace", fontSize: 8, color: C.GREY }, children: clauses.length })] }), _jsx("div", { style: { fontFamily: "m6x11plus, ui-monospace, monospace", fontSize: 9, color: C.GREY, letterSpacing: 0.5, marginBottom: 8 }, children: z.hint }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [clauses.map((c) => (_jsx(ClauseCard, { clause: c, zone: zone, onRemove: () => onRemove(c.id), onEdit: () => onEdit(c), onDragStart: onDragStart }, c.id))), _jsx(MysteryAddTile, { zone: zone, onTap: onAdd })] })] }));
|
|
151
149
|
}
|
|
152
150
|
function TopMatter({ filter, onChange, }) {
|
|
153
|
-
return (_jsxs("div", { style: {
|
|
154
|
-
background: C.DARK_GREY,
|
|
155
|
-
borderRadius: 6,
|
|
156
|
-
padding: 10,
|
|
157
|
-
border: `2px solid ${C.PANEL_EDGE}`,
|
|
158
|
-
boxShadow: `0 2px 0 ${C.BLACK}`,
|
|
159
|
-
}, children: [_jsx("input", { value: filter.name ?? "", placeholder: "Untitled", onChange: (e) => onChange({ ...filter, name: e.target.value }), style: {
|
|
151
|
+
return (_jsxs("div", { className: "j-inner-panel", style: { padding: 10 }, children: [_jsx("input", { value: filter.name ?? "", placeholder: "Untitled", onChange: (e) => onChange({ ...filter, name: e.target.value }), style: {
|
|
160
152
|
display: "block",
|
|
161
153
|
width: "100%",
|
|
162
154
|
background: "transparent",
|
|
@@ -168,7 +160,7 @@ function TopMatter({ filter, onChange, }) {
|
|
|
168
160
|
letterSpacing: 1,
|
|
169
161
|
padding: 0,
|
|
170
162
|
marginBottom: 4,
|
|
171
|
-
} }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("div", { style: { fontFamily: "m6x11plus, ui-monospace, monospace", fontSize:
|
|
163
|
+
} }), _jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [_jsx("div", { style: { fontFamily: "m6x11plus, ui-monospace, monospace", fontSize: 10, color: C.WHITE }, children: "By" }), _jsx("input", { value: filter.author ?? "", placeholder: "anonymous", onChange: (e) => onChange({ ...filter, author: e.target.value }), style: {
|
|
172
164
|
flex: 1,
|
|
173
165
|
background: "transparent",
|
|
174
166
|
border: "none",
|
|
@@ -176,7 +168,6 @@ function TopMatter({ filter, onChange, }) {
|
|
|
176
168
|
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
177
169
|
fontSize: 12,
|
|
178
170
|
color: C.GOLD_TEXT,
|
|
179
|
-
letterSpacing: 1,
|
|
180
171
|
padding: 0,
|
|
181
172
|
} })] }), _jsx("input", { value: filter.description ?? "", placeholder: "description", onChange: (e) => onChange({ ...filter, description: e.target.value }), style: {
|
|
182
173
|
display: "block",
|
|
@@ -186,8 +177,9 @@ function TopMatter({ filter, onChange, }) {
|
|
|
186
177
|
border: "none",
|
|
187
178
|
outline: "none",
|
|
188
179
|
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
189
|
-
fontSize:
|
|
190
|
-
color: C.
|
|
180
|
+
fontSize: 11,
|
|
181
|
+
color: C.WHITE,
|
|
182
|
+
opacity: 0.8,
|
|
191
183
|
lineHeight: 1.35,
|
|
192
184
|
padding: 0,
|
|
193
185
|
} })] }));
|
|
@@ -205,7 +197,7 @@ export function JamlIdeVisual({ filter, onChange, onEditClause, onAddClause }) {
|
|
|
205
197
|
padding: 10,
|
|
206
198
|
background: C.DARKEST,
|
|
207
199
|
color: C.WHITE,
|
|
208
|
-
}, children: [_jsx(TopMatter, { filter: filter, onChange: onChange }),
|
|
200
|
+
}, children: [_jsx(TopMatter, { filter: filter, onChange: onChange }), _jsxs("div", { style: { display: "flex", gap: 10 }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(ZoneRail, { zone: "must", clauses: filter.must, onAdd: onAddClause ? () => onAddClause("must") : undefined, onRemove: (id) => removeClause("must", id), onEdit: (c) => onEditClause?.("must", c), onDragStart: onDragStart, highlight: hoverZone === "must" }) }), _jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(ZoneRail, { zone: "mustnot", clauses: filter.mustnot, onAdd: onAddClause ? () => onAddClause("mustnot") : undefined, onRemove: (id) => removeClause("mustnot", id), onEdit: (c) => onEditClause?.("mustnot", c), onDragStart: onDragStart, highlight: hoverZone === "mustnot" }) })] }), _jsx(ZoneRail, { zone: "should", clauses: filter.should, onAdd: onAddClause ? () => onAddClause("should") : undefined, onRemove: (id) => removeClause("should", id), onEdit: (c) => onEditClause?.("should", c), onDragStart: onDragStart, highlight: hoverZone === "should" }), drag && (_jsx("div", { style: {
|
|
209
201
|
position: "fixed",
|
|
210
202
|
left: drag.x - drag.offX,
|
|
211
203
|
top: drag.y - drag.offY,
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { JimboSprite } from "../../ui/sprites.js";
|
|
5
|
-
import { JimboColorOption
|
|
5
|
+
import { JimboColorOption } from "../../ui/tokens.js";
|
|
6
|
+
import { JimboButton } from "../../ui/panel.js";
|
|
7
|
+
import { JimboText } from "../../ui/jimboText.js";
|
|
6
8
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
9
|
+
const C = JimboColorOption;
|
|
7
10
|
export function CategoryPicker({ config, onSelect, onCancel }) {
|
|
8
11
|
const [search, setSearch] = useState("");
|
|
9
12
|
const filtered = useMemo(() => {
|
|
@@ -12,6 +15,19 @@ export function CategoryPicker({ config, onSelect, onCancel }) {
|
|
|
12
15
|
const q = search.toLowerCase();
|
|
13
16
|
return config.items.filter((item) => item.name.toLowerCase().includes(q));
|
|
14
17
|
}, [config.items, search]);
|
|
18
|
+
const pairedVouchers = useMemo(() => {
|
|
19
|
+
if (config.category !== "voucher")
|
|
20
|
+
return null;
|
|
21
|
+
const bases = config.items.filter((item) => item.pos.y % 2 === 0);
|
|
22
|
+
const pairs = bases.map((base) => {
|
|
23
|
+
const upgrade = config.items.find((u) => u.pos.x === base.pos.x && u.pos.y === base.pos.y + 1);
|
|
24
|
+
return { base, upgrade };
|
|
25
|
+
});
|
|
26
|
+
if (!search)
|
|
27
|
+
return pairs;
|
|
28
|
+
const q = search.toLowerCase();
|
|
29
|
+
return pairs.filter(p => p.base.name.toLowerCase().includes(q) || p.upgrade?.name.toLowerCase().includes(q));
|
|
30
|
+
}, [config.items, search, config.category]);
|
|
15
31
|
const handleSelect = useCallback((item) => {
|
|
16
32
|
onSelect({
|
|
17
33
|
category: config.category,
|
|
@@ -26,8 +42,26 @@ export function CategoryPicker({ config, onSelect, onCancel }) {
|
|
|
26
42
|
clauseKey: config.clauseKey,
|
|
27
43
|
});
|
|
28
44
|
}, [onSelect, config]);
|
|
29
|
-
const
|
|
30
|
-
|
|
45
|
+
const renderItem = (item, isMuted = false) => (_jsxs("div", { onClick: () => handleSelect(item), title: item.name, style: {
|
|
46
|
+
display: "flex",
|
|
47
|
+
flexDirection: "column",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
gap: 3,
|
|
50
|
+
padding: 4,
|
|
51
|
+
borderRadius: 4,
|
|
52
|
+
cursor: "pointer",
|
|
53
|
+
opacity: isMuted ? 0.3 : 1,
|
|
54
|
+
}, children: [_jsx(JimboSprite, { name: item.name, sheet: config.sheet, width: 48 }), _jsx(JimboText, { size: "micro", tone: "grey", style: { maxWidth: 60, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: item.name })] }, item.name));
|
|
55
|
+
return (_jsxs("div", { style: { padding: 0, maxWidth: 420, maxHeight: "80vh", display: "flex", flexDirection: "column" }, children: [_jsxs("div", { className: "j-flex j-gap-sm", style: { padding: "8px 10px 4px" }, children: [_jsx("input", { className: "j-seed-input__field", type: "text", placeholder: `Search ${config.title.toLowerCase()}...`, value: search, onChange: (e) => setSearch(e.target.value), style: { fontSize: 13, padding: "6px 10px", textTransform: "none", letterSpacing: "0.04em" } }), _jsx(JimboButton, { tone: "gold", size: "sm", onClick: handleAny, children: "Any" })] }), config.hint && (_jsx("div", { className: "j-inner-panel", style: { margin: "4px 10px 6px", padding: "6px 10px" }, children: _jsx(JimboText, { size: "xs", tone: "grey", children: config.hint }) })), _jsxs("div", { style: {
|
|
56
|
+
display: "flex",
|
|
57
|
+
flexWrap: "wrap",
|
|
58
|
+
gap: 6,
|
|
59
|
+
padding: "8px 10px 10px",
|
|
60
|
+
overflowY: "auto",
|
|
61
|
+
flex: 1,
|
|
62
|
+
alignContent: "flex-start",
|
|
63
|
+
}, children: [config.category === "voucher" && pairedVouchers ? (pairedVouchers.map((pair) => (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4, width: 64 }, children: [renderItem(pair.base, search ? !pair.base.name.toLowerCase().includes(search.toLowerCase()) : false), pair.upgrade && renderItem(pair.upgrade, search ? !pair.upgrade.name.toLowerCase().includes(search.toLowerCase()) : false)] }, pair.base.name)))) : (filtered.map((item) => (_jsx("div", { style: { width: 64 }, children: renderItem(item) }, item.name)))), ((config.category === "voucher" && pairedVouchers?.length === 0) ||
|
|
64
|
+
(config.category !== "voucher" && filtered.length === 0)) && (_jsx("div", { style: { width: "100%", padding: 20, textAlign: "center" }, children: _jsxs(JimboText, { size: "sm", tone: "grey", children: ["No matches for \"", search, "\""] }) }))] })] }));
|
|
31
65
|
}
|
|
32
66
|
// ─── Pre-built configs ───────────────────────────────────────────────────────
|
|
33
67
|
import { VOUCHERS, TAGS, BOSSES, BOOSTER_PACKS, TAROTS_AND_PLANETS } from "../../sprites/spriteData.js";
|
|
@@ -36,18 +70,16 @@ const TAROT_CARDS = TAROTS_AND_PLANETS.filter((c) => {
|
|
|
36
70
|
const y = c.pos.y;
|
|
37
71
|
return y <= 2 && c.name !== "The Soul" && c.name !== "Black Hole";
|
|
38
72
|
}).filter((c) => {
|
|
39
|
-
// Tarots are y=0..1 + Judgement (y=2,x=0) + The World (y=2,x=1)
|
|
40
73
|
return c.pos.y <= 1 || (c.pos.y === 2 && c.pos.x <= 1);
|
|
41
74
|
});
|
|
42
75
|
const PLANET_CARDS = TAROTS_AND_PLANETS.filter((c) => {
|
|
43
|
-
return c.pos.y === 3 ||
|
|
76
|
+
return c.pos.y === 3 ||
|
|
44
77
|
c.name === "Planet X" || c.name === "Ceres" || c.name === "Eris" ||
|
|
45
78
|
c.name === "Black Hole";
|
|
46
79
|
});
|
|
47
80
|
const SPECTRAL_CARDS = TAROTS_AND_PLANETS.filter((c) => {
|
|
48
81
|
return c.pos.y >= 4 || c.name === "The Soul";
|
|
49
82
|
});
|
|
50
|
-
const C = JimboColorOption;
|
|
51
83
|
export const VOUCHER_PICKER_CONFIG = {
|
|
52
84
|
title: "Vouchers",
|
|
53
85
|
category: "voucher",
|
|
@@ -108,117 +140,3 @@ export const PACK_PICKER_CONFIG = {
|
|
|
108
140
|
items: BOOSTER_PACKS,
|
|
109
141
|
accent: C.ORANGE,
|
|
110
142
|
};
|
|
111
|
-
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
112
|
-
const styles = {
|
|
113
|
-
container: {
|
|
114
|
-
background: JimboColorOption.DARKEST,
|
|
115
|
-
border: `2px solid ${JimboColorOption.TEAL_GREY}`,
|
|
116
|
-
borderRadius: 8,
|
|
117
|
-
padding: 0,
|
|
118
|
-
maxWidth: 420,
|
|
119
|
-
maxHeight: "80vh",
|
|
120
|
-
overflow: "hidden",
|
|
121
|
-
display: "flex",
|
|
122
|
-
flexDirection: "column",
|
|
123
|
-
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
124
|
-
boxShadow: `0 8px 32px ${withAlpha(JimboColorOption.BLACK, 0.6)}`,
|
|
125
|
-
},
|
|
126
|
-
header: {
|
|
127
|
-
display: "flex",
|
|
128
|
-
alignItems: "center",
|
|
129
|
-
justifyContent: "space-between",
|
|
130
|
-
padding: "10px 12px",
|
|
131
|
-
borderBottom: `1px solid ${JimboColorOption.TEAL_GREY}`,
|
|
132
|
-
background: withAlpha(JimboColorOption.DARK_GREY, 0.5),
|
|
133
|
-
},
|
|
134
|
-
backBtn: {
|
|
135
|
-
background: "none",
|
|
136
|
-
border: "none",
|
|
137
|
-
color: JimboColorOption.GREY,
|
|
138
|
-
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
139
|
-
fontSize: 14,
|
|
140
|
-
cursor: "pointer",
|
|
141
|
-
padding: "4px 8px",
|
|
142
|
-
},
|
|
143
|
-
title: {
|
|
144
|
-
fontSize: 16,
|
|
145
|
-
fontWeight: "bold",
|
|
146
|
-
letterSpacing: 0.5,
|
|
147
|
-
},
|
|
148
|
-
searchRow: {
|
|
149
|
-
display: "flex",
|
|
150
|
-
gap: 8,
|
|
151
|
-
padding: "10px 12px 6px",
|
|
152
|
-
},
|
|
153
|
-
searchInput: {
|
|
154
|
-
flex: 1,
|
|
155
|
-
padding: "6px 10px",
|
|
156
|
-
borderRadius: 4,
|
|
157
|
-
border: `1px solid ${JimboColorOption.TEAL_GREY}`,
|
|
158
|
-
background: withAlpha(JimboColorOption.DARK_GREY, 0.8),
|
|
159
|
-
color: JimboColorOption.WHITE,
|
|
160
|
-
fontSize: 13,
|
|
161
|
-
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
162
|
-
outline: "none",
|
|
163
|
-
},
|
|
164
|
-
anyBtn: {
|
|
165
|
-
padding: "6px 14px",
|
|
166
|
-
borderRadius: 4,
|
|
167
|
-
border: `1px solid ${JimboColorOption.GOLD}`,
|
|
168
|
-
background: withAlpha(JimboColorOption.GOLD, 0.15),
|
|
169
|
-
color: JimboColorOption.GOLD,
|
|
170
|
-
fontSize: 13,
|
|
171
|
-
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
172
|
-
cursor: "pointer",
|
|
173
|
-
fontWeight: "bold",
|
|
174
|
-
},
|
|
175
|
-
hint: {
|
|
176
|
-
display: "flex",
|
|
177
|
-
alignItems: "flex-start",
|
|
178
|
-
gap: 6,
|
|
179
|
-
margin: "4px 12px 8px",
|
|
180
|
-
padding: "8px 10px",
|
|
181
|
-
borderRadius: 4,
|
|
182
|
-
background: withAlpha(JimboColorOption.TEAL_GREY, 0.12),
|
|
183
|
-
border: `1px solid ${withAlpha(JimboColorOption.TEAL_GREY, 0.3)}`,
|
|
184
|
-
color: JimboColorOption.GREY,
|
|
185
|
-
fontSize: 11,
|
|
186
|
-
lineHeight: "1.4",
|
|
187
|
-
},
|
|
188
|
-
grid: {
|
|
189
|
-
display: "grid",
|
|
190
|
-
gridTemplateColumns: "repeat(auto-fill, minmax(64px, 1fr))",
|
|
191
|
-
gap: 6,
|
|
192
|
-
padding: "8px 12px 12px",
|
|
193
|
-
overflowY: "auto",
|
|
194
|
-
flex: 1,
|
|
195
|
-
},
|
|
196
|
-
cell: {
|
|
197
|
-
display: "flex",
|
|
198
|
-
flexDirection: "column",
|
|
199
|
-
alignItems: "center",
|
|
200
|
-
gap: 3,
|
|
201
|
-
padding: 4,
|
|
202
|
-
borderRadius: 4,
|
|
203
|
-
cursor: "pointer",
|
|
204
|
-
transition: "background 120ms",
|
|
205
|
-
background: "transparent",
|
|
206
|
-
},
|
|
207
|
-
label: {
|
|
208
|
-
fontSize: 9,
|
|
209
|
-
color: JimboColorOption.GREY,
|
|
210
|
-
textAlign: "center",
|
|
211
|
-
lineHeight: "1.2",
|
|
212
|
-
maxWidth: 60,
|
|
213
|
-
overflow: "hidden",
|
|
214
|
-
textOverflow: "ellipsis",
|
|
215
|
-
whiteSpace: "nowrap",
|
|
216
|
-
},
|
|
217
|
-
emptyState: {
|
|
218
|
-
gridColumn: "1 / -1",
|
|
219
|
-
textAlign: "center",
|
|
220
|
-
color: JimboColorOption.GREY,
|
|
221
|
-
fontSize: 13,
|
|
222
|
-
padding: 20,
|
|
223
|
-
},
|
|
224
|
-
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type SlotSelection, type JamlZone } from "./MysterySlot.js";
|
|
2
|
+
export interface JamlMapEditorProps {
|
|
3
|
+
/** Initial zone for the demo. */
|
|
4
|
+
zone?: JamlZone;
|
|
5
|
+
/** Callback when selections change. Returns JAML string. */
|
|
6
|
+
onChange?: (jamlString: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface MapSlotSelection extends SlotSelection {
|
|
9
|
+
zone: JamlZone;
|
|
10
|
+
}
|
|
11
|
+
export declare function JamlMapEditor({ zone: initialZone, onChange, }: JamlMapEditorProps): import("react/jsx-runtime").JSX.Element;
|