jaml-ui 0.5.2 → 0.6.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/dist/components/JamlIde.d.ts +6 -1
- package/dist/components/JamlIde.js +3 -2
- package/dist/components/JamlIdeToolbar.d.ts +1 -1
- package/dist/components/JamlIdeToolbar.js +1 -0
- package/dist/components/JamlIdeVisual.d.ts +27 -0
- package/dist/components/JamlIdeVisual.js +113 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/ui/panel.d.ts +14 -0
- package/dist/ui/panel.js +33 -0
- package/dist/ui/showcase.d.ts +26 -0
- package/dist/ui/showcase.js +54 -0
- package/dist/ui/sprites.d.ts +10 -0
- package/dist/ui/sprites.js +36 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { type JamlIdeMode } from "./JamlIdeToolbar.js";
|
|
3
|
+
import { type JamlVisualFilter } from "./JamlIdeVisual.js";
|
|
3
4
|
export interface JamlIdeSearchResult {
|
|
4
5
|
seed: string;
|
|
5
6
|
score?: number;
|
|
@@ -17,5 +18,9 @@ export interface JamlIdeProps {
|
|
|
17
18
|
codePlaceholder?: string;
|
|
18
19
|
onSearch?: () => void;
|
|
19
20
|
isSearching?: boolean;
|
|
21
|
+
visualFilter?: JamlVisualFilter;
|
|
22
|
+
onVisualFilterChange?: (filter: JamlVisualFilter) => void;
|
|
20
23
|
}
|
|
21
|
-
export
|
|
24
|
+
export type { JamlVisualFilter } from "./JamlIdeVisual.js";
|
|
25
|
+
export type { JamlVisualClause, JamlZone } from "./JamlIdeVisual.js";
|
|
26
|
+
export declare function JamlIde({ jaml, onChange, defaultMode, searchResults, className, title, actions, codePlaceholder, onSearch, isSearching, visualFilter, onVisualFilterChange, }: JamlIdeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
5
5
|
import { JamlIdeToolbar } from "./JamlIdeToolbar.js";
|
|
6
|
+
import { JamlIdeVisual } from "./JamlIdeVisual.js";
|
|
6
7
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
7
8
|
function TallyBar({ value, max }) {
|
|
8
9
|
const pct = max > 0 ? Math.min(1, value / max) : 0;
|
|
@@ -88,7 +89,7 @@ function ResultsView({ results }) {
|
|
|
88
89
|
}) })) : null] }, result.seed));
|
|
89
90
|
}) }));
|
|
90
91
|
}
|
|
91
|
-
export function JamlIde({ jaml, onChange, defaultMode = "code", searchResults = [], className = "", title = "JAML IDE", actions, codePlaceholder = "Enter JAML...", onSearch, isSearching = false, }) {
|
|
92
|
+
export function JamlIde({ jaml, onChange, defaultMode = "code", searchResults = [], className = "", title = "JAML IDE", actions, codePlaceholder = "Enter JAML...", onSearch, isSearching = false, visualFilter, onVisualFilterChange, }) {
|
|
92
93
|
const [mode, setMode] = useState(defaultMode);
|
|
93
94
|
const results = useMemo(() => searchResults, [searchResults]);
|
|
94
95
|
return (_jsxs("div", { className: className, style: {
|
|
@@ -109,7 +110,7 @@ export function JamlIde({ jaml, onChange, defaultMode = "code", searchResults =
|
|
|
109
110
|
padding: "10px 14px",
|
|
110
111
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
111
112
|
background: JimboColorOption.TEAL_GREY,
|
|
112
|
-
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, fontWeight: 800, 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 === "code" ? (_jsx("textarea", { title: "JAML IDE Editor", value: jaml, onChange: (event) => onChange(event.target.value), placeholder: codePlaceholder, spellCheck: false, autoCapitalize: "off", autoCorrect: "off", style: {
|
|
113
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, fontWeight: 800, 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" && visualFilter && onVisualFilterChange ? (_jsx(JamlIdeVisual, { filter: visualFilter, onChange: onVisualFilterChange })) : mode === "visual" ? (_jsxs("div", { style: { padding: 16, color: JimboColorOption.GREY, fontSize: 12, textAlign: "center" }, children: ["Pass ", _jsx("code", { children: "visualFilter" }), " and ", _jsx("code", { children: "onVisualFilterChange" }), " props to enable the visual editor."] })) : null, mode === "code" ? (_jsx("textarea", { title: "JAML IDE Editor", value: jaml, onChange: (event) => onChange(event.target.value), placeholder: codePlaceholder, spellCheck: false, autoCapitalize: "off", autoCorrect: "off", style: {
|
|
113
114
|
width: "100%",
|
|
114
115
|
minHeight: 320,
|
|
115
116
|
resize: "vertical",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
4
4
|
const TABS = [
|
|
5
|
+
{ id: "visual", label: "Visual" },
|
|
5
6
|
{ id: "code", label: "Code" },
|
|
6
7
|
{ id: "map", label: "Map" },
|
|
7
8
|
{ id: "results", label: "Results" },
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type JamlZone = "must" | "should" | "mustnot";
|
|
2
|
+
export interface JamlVisualClause {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
value: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
antes?: number[];
|
|
8
|
+
score?: number;
|
|
9
|
+
edition?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface JamlVisualFilter {
|
|
12
|
+
name?: string;
|
|
13
|
+
author?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
deck?: string;
|
|
16
|
+
stake?: string;
|
|
17
|
+
must: JamlVisualClause[];
|
|
18
|
+
should: JamlVisualClause[];
|
|
19
|
+
mustnot: JamlVisualClause[];
|
|
20
|
+
}
|
|
21
|
+
export interface JamlIdeVisualProps {
|
|
22
|
+
filter: JamlVisualFilter;
|
|
23
|
+
onChange: (filter: JamlVisualFilter) => void;
|
|
24
|
+
onSave?: () => void;
|
|
25
|
+
onBack?: () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function JamlIdeVisual({ filter, onChange, onSave, onBack }: JamlIdeVisualProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
|
+
import { BalSprite } from "../ui/sprites.js";
|
|
6
|
+
const ZONE_META = {
|
|
7
|
+
must: { label: "MUST", color: JimboColorOption.BLUE },
|
|
8
|
+
should: { label: "SHOULD", color: JimboColorOption.RED },
|
|
9
|
+
mustnot: { label: "MUST NOT", color: JimboColorOption.ORANGE },
|
|
10
|
+
};
|
|
11
|
+
function clauseSpriteSheet(type) {
|
|
12
|
+
if (type === "joker" || type === "souljoker" || type === "rareJoker" || type === "commonJoker" || type === "uncommonJoker" || type === "legendaryJoker" || type === "mixedJoker")
|
|
13
|
+
return "Jokers";
|
|
14
|
+
if (type === "voucher")
|
|
15
|
+
return "Vouchers";
|
|
16
|
+
if (type === "smallblindtag" || type === "bigblindtag" || type === "tag")
|
|
17
|
+
return "tags";
|
|
18
|
+
if (type === "boss")
|
|
19
|
+
return "BlindChips";
|
|
20
|
+
if (type === "tarot" || type === "spectral")
|
|
21
|
+
return "Tarots";
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function ClauseSprite({ clause, size = 26 }) {
|
|
25
|
+
const sheet = clauseSpriteSheet(clause.type);
|
|
26
|
+
if (!sheet)
|
|
27
|
+
return null;
|
|
28
|
+
return _jsx(BalSprite, { name: clause.value, sheet: sheet, width: size });
|
|
29
|
+
}
|
|
30
|
+
function DragClausePill({ clause, zone, onDragStart, }) {
|
|
31
|
+
const z = ZONE_META[zone];
|
|
32
|
+
return (_jsxs("div", { onMouseDown: (e) => onDragStart(e, clause, zone), onTouchStart: (e) => onDragStart(e, clause, zone), style: {
|
|
33
|
+
display: "flex", alignItems: "center", gap: 6,
|
|
34
|
+
background: JimboColorOption.DARK_GREY, border: `2px solid ${z.color}`,
|
|
35
|
+
borderRadius: 6, padding: "5px 8px 5px 4px",
|
|
36
|
+
boxShadow: `0 2px 0 ${JimboColorOption.BLACK}`,
|
|
37
|
+
cursor: "grab", userSelect: "none", touchAction: "none",
|
|
38
|
+
}, children: [_jsx("div", { style: { color: JimboColorOption.GREY, fontSize: 12, lineHeight: 1, padding: "0 2px" }, children: "\u22EE\u22EE" }), _jsx(ClauseSprite, { clause: clause, size: 26 }), _jsx("div", { style: { fontSize: 10, color: JimboColorOption.WHITE, letterSpacing: 1, textShadow: "1px 1px 0 rgba(0,0,0,.8)" }, children: clause.label || clause.value }), clause.antes && clause.antes.length > 0 && (_jsxs("div", { style: { display: "flex", gap: 2 }, children: [clause.antes.slice(0, 3).map((a) => (_jsx("div", { style: { fontSize: 8, padding: "0 3px", background: JimboColorOption.DARKEST, color: z.color, borderRadius: 2 }, children: a }, a))), clause.antes.length > 3 && _jsxs("div", { style: { fontSize: 8, color: JimboColorOption.GREY }, children: ["+", clause.antes.length - 3] })] })), clause.score != null && (_jsxs("div", { style: { fontSize: 9, padding: "0 4px", background: JimboColorOption.RED, color: JimboColorOption.WHITE, borderRadius: 2 }, children: ["+", clause.score] }))] }));
|
|
39
|
+
}
|
|
40
|
+
function ZoneDropRail({ zone, clauses, onDragStart, highlight, }) {
|
|
41
|
+
const z = ZONE_META[zone];
|
|
42
|
+
return (_jsxs("div", { "data-zone": zone, style: {
|
|
43
|
+
border: `2px dashed ${highlight ? z.color : `${z.color}55`}`,
|
|
44
|
+
background: highlight ? `${z.color}22` : "transparent",
|
|
45
|
+
borderRadius: 6, padding: 8,
|
|
46
|
+
transition: "background 100ms, border-color 100ms",
|
|
47
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }, children: [_jsx("div", { style: {
|
|
48
|
+
fontSize: 10, letterSpacing: 2, padding: "2px 8px",
|
|
49
|
+
background: z.color, color: JimboColorOption.WHITE, borderRadius: 3,
|
|
50
|
+
textShadow: "1px 1px 0 rgba(0,0,0,.8)",
|
|
51
|
+
}, children: z.label }), _jsx("div", { style: { flex: 1, height: 1, background: `${z.color}44` } }), _jsx("div", { style: { fontSize: 8, color: JimboColorOption.GREY }, children: clauses.length })] }), _jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: [clauses.map((c) => (_jsx(DragClausePill, { clause: c, zone: zone, onDragStart: onDragStart }, c.id))), clauses.length === 0 && (_jsx("div", { style: { fontSize: 10, color: JimboColorOption.GREY, padding: 10, fontStyle: "italic" }, children: "drop clauses here" }))] })] }));
|
|
52
|
+
}
|
|
53
|
+
export function JamlIdeVisual({ filter, onChange, onSave, onBack }) {
|
|
54
|
+
const [drag, setDrag] = useState(null);
|
|
55
|
+
const [hoverZone, setHoverZone] = useState(null);
|
|
56
|
+
const rootRef = useRef(null);
|
|
57
|
+
const onDragStart = (e, clause, fromZone) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
const t = "touches" in e ? e.touches[0] : e;
|
|
60
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
61
|
+
setDrag({ clause, fromZone, x: t.clientX, y: t.clientY, offX: t.clientX - rect.left, offY: t.clientY - rect.top, w: rect.width, h: rect.height });
|
|
62
|
+
};
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!drag)
|
|
65
|
+
return;
|
|
66
|
+
const move = (e) => {
|
|
67
|
+
const t = "touches" in e ? e.touches[0] : e;
|
|
68
|
+
setDrag((d) => d && { ...d, x: t.clientX, y: t.clientY });
|
|
69
|
+
const rails = rootRef.current?.querySelectorAll("[data-zone]") ?? [];
|
|
70
|
+
let found = null;
|
|
71
|
+
for (const r of rails) {
|
|
72
|
+
const rc = r.getBoundingClientRect();
|
|
73
|
+
if (t.clientX >= rc.left && t.clientX <= rc.right && t.clientY >= rc.top && t.clientY <= rc.bottom) {
|
|
74
|
+
found = r.getAttribute("data-zone");
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
setHoverZone(found);
|
|
79
|
+
};
|
|
80
|
+
const up = () => {
|
|
81
|
+
if (hoverZone && hoverZone !== drag.fromZone) {
|
|
82
|
+
const from = hoverZone;
|
|
83
|
+
onChange({
|
|
84
|
+
...filter,
|
|
85
|
+
[drag.fromZone]: filter[drag.fromZone].filter((c) => c.id !== drag.clause.id),
|
|
86
|
+
[from]: [...filter[from], { ...drag.clause }],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
setDrag(null);
|
|
90
|
+
setHoverZone(null);
|
|
91
|
+
};
|
|
92
|
+
window.addEventListener("mousemove", move);
|
|
93
|
+
window.addEventListener("mouseup", up);
|
|
94
|
+
window.addEventListener("touchmove", move, { passive: false });
|
|
95
|
+
window.addEventListener("touchend", up);
|
|
96
|
+
return () => {
|
|
97
|
+
window.removeEventListener("mousemove", move);
|
|
98
|
+
window.removeEventListener("mouseup", up);
|
|
99
|
+
window.removeEventListener("touchmove", move);
|
|
100
|
+
window.removeEventListener("touchend", up);
|
|
101
|
+
};
|
|
102
|
+
}, [drag, hoverZone, filter, onChange]);
|
|
103
|
+
return (_jsxs("div", { ref: rootRef, style: { display: "flex", flexDirection: "column", gap: 10, padding: 10 }, children: [_jsxs("div", { style: {
|
|
104
|
+
background: JimboColorOption.DARK_GREY, border: `2px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
105
|
+
borderRadius: 6, padding: 8, boxShadow: `0 2px 0 ${JimboColorOption.BLACK}`,
|
|
106
|
+
}, children: [_jsx("div", { style: { fontSize: 9, color: JimboColorOption.GREY, letterSpacing: 2 }, children: "FILE" }), _jsxs("div", { style: { fontSize: 14, color: JimboColorOption.WHITE, textShadow: "1px 1px 0 rgba(0,0,0,.8)" }, children: [filter.name || "Untitled", ".jaml"] }), filter.author && (_jsxs("div", { style: { fontSize: 9, color: JimboColorOption.GOLD_TEXT, marginTop: 2 }, children: ["by ", filter.author] }))] }), _jsx("div", { style: { fontSize: 9, color: JimboColorOption.GREY, letterSpacing: 1, textAlign: "center" }, children: "\u22EE\u22EE drag clauses between zones \u00B7 tap to edit" }), _jsx(ZoneDropRail, { zone: "must", clauses: filter.must, onDragStart: onDragStart, highlight: hoverZone === "must" }), _jsx(ZoneDropRail, { zone: "should", clauses: filter.should, onDragStart: onDragStart, highlight: hoverZone === "should" }), _jsx(ZoneDropRail, { zone: "mustnot", clauses: filter.mustnot, onDragStart: onDragStart, highlight: hoverZone === "mustnot" }), drag && (_jsx("div", { style: {
|
|
107
|
+
position: "fixed",
|
|
108
|
+
left: drag.x - drag.offX, top: drag.y - drag.offY,
|
|
109
|
+
pointerEvents: "none", zIndex: 999,
|
|
110
|
+
transform: "rotate(-2deg) scale(1.05)",
|
|
111
|
+
filter: "drop-shadow(0 4px 6px rgba(0,0,0,.6))", opacity: 0.92,
|
|
112
|
+
}, children: _jsx(DragClausePill, { clause: drag.clause, zone: drag.fromZone, onDragStart: () => { } }) }))] }));
|
|
113
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export { JamlCardRenderer, type JamlCardRendererProps } from "./render/CanvasRen
|
|
|
4
4
|
export { JamlGameCard, JamlVoucher, JamlTag, JamlBoss, resolveAnalyzerShopItem, type JamlGameCardProps, type AnalyzerShopItem, type AnalyzerResolvedItem, } from "./components/GameCard.js";
|
|
5
5
|
export { AnalyzerExplorer, type AnalyzerAnteView, type AnalyzerBadge, type AnalyzerExplorerProps, type AnalyzerFact, type AnalyzerHighlight, type AnalyzerItem, } from "./components/AnalyzerExplorer.js";
|
|
6
6
|
export { JamlMapPreview, type JamlMapPreviewProps } from "./components/JamlMapPreview.js";
|
|
7
|
-
export { JamlIde, type JamlIdeProps, type JamlIdeSearchResult, } from "./components/JamlIde.js";
|
|
7
|
+
export { JamlIde, type JamlIdeProps, type JamlIdeSearchResult, type JamlVisualFilter, type JamlVisualClause, type JamlZone, } from "./components/JamlIde.js";
|
|
8
|
+
export { JamlIdeVisual, type JamlIdeVisualProps, } from "./components/JamlIdeVisual.js";
|
|
8
9
|
export { JamlIdeToolbar, type JamlIdeMode, type JamlIdeToolbarProps, } from "./components/JamlIdeToolbar.js";
|
|
9
10
|
export { CardList, type CardListProps } from "./components/CardList.js";
|
|
10
11
|
export { extractVisualJamlItems, type JamlPreviewGroups, type JamlPreviewItem, type JamlPreviewSection, type JamlPreviewVisualType, } from "./utils/jamlMapPreview.js";
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { JamlGameCard, JamlVoucher, JamlTag, JamlBoss, resolveAnalyzerShopItem,
|
|
|
6
6
|
export { AnalyzerExplorer, } from "./components/AnalyzerExplorer.js";
|
|
7
7
|
export { JamlMapPreview } from "./components/JamlMapPreview.js";
|
|
8
8
|
export { JamlIde, } from "./components/JamlIde.js";
|
|
9
|
+
export { JamlIdeVisual, } from "./components/JamlIdeVisual.js";
|
|
9
10
|
export { JamlIdeToolbar, } from "./components/JamlIdeToolbar.js";
|
|
10
11
|
export { CardList } from "./components/CardList.js";
|
|
11
12
|
export { extractVisualJamlItems, } from "./utils/jamlMapPreview.js";
|
package/dist/ui/panel.d.ts
CHANGED
|
@@ -19,6 +19,20 @@ export declare function JimboButton({ children, variant, size, fullWidth, classN
|
|
|
19
19
|
export declare function JimboBackButton({ label, ...props }: Omit<JimboButtonProps, 'variant' | 'children'> & {
|
|
20
20
|
label?: string;
|
|
21
21
|
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export type BalTone = 'orange' | 'red' | 'blue' | 'green' | 'gold' | 'grey';
|
|
23
|
+
export interface BalButtonProps {
|
|
24
|
+
tone?: BalTone;
|
|
25
|
+
size?: 'sm' | 'md' | 'lg';
|
|
26
|
+
fullWidth?: boolean;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
onClick?: () => void;
|
|
29
|
+
style?: React.CSSProperties;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
export declare function BalButton({ tone, size, fullWidth, disabled, onClick, style, children, }: BalButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
export declare function BalBackButton({ onClick }: {
|
|
34
|
+
onClick?: () => void;
|
|
35
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
36
|
export interface JimboModalProps {
|
|
23
37
|
children: React.ReactNode;
|
|
24
38
|
open: boolean;
|
package/dist/ui/panel.js
CHANGED
|
@@ -63,6 +63,39 @@ export function JimboButton({ children, variant = 'primary', size = 'md', fullWi
|
|
|
63
63
|
export function JimboBackButton({ label = 'Back', ...props }) {
|
|
64
64
|
return _jsx(JimboButton, { variant: "back", size: "sm", fullWidth: true, ...props, children: label });
|
|
65
65
|
}
|
|
66
|
+
// ─── BalButton ────────────────────────────────────────────────────────────────
|
|
67
|
+
// Canonical Balatro-style flat 2D button.
|
|
68
|
+
// Two-layer: separate shadow div (3px south + 1px east) that disappears on press.
|
|
69
|
+
// Press translates the face onto the shadow. No gradients, no hover color change.
|
|
70
|
+
const BAL_PAIRS = {
|
|
71
|
+
orange: [JimboColorOption.ORANGE, JimboColorOption.DARK_ORANGE],
|
|
72
|
+
red: [JimboColorOption.RED, JimboColorOption.DARK_RED],
|
|
73
|
+
blue: [JimboColorOption.BLUE, JimboColorOption.DARK_BLUE],
|
|
74
|
+
green: [JimboColorOption.GREEN, JimboColorOption.DARK_GREEN],
|
|
75
|
+
gold: [JimboColorOption.GOLD, '#8a6a1e'],
|
|
76
|
+
grey: [JimboColorOption.DARK_GREY, JimboColorOption.DARKEST],
|
|
77
|
+
};
|
|
78
|
+
export function BalButton({ tone = 'orange', size = 'md', fullWidth = false, disabled = false, onClick, style, children, }) {
|
|
79
|
+
const [pressed, setPressed] = useState(false);
|
|
80
|
+
const [fg, sh] = BAL_PAIRS[tone] ?? BAL_PAIRS.orange;
|
|
81
|
+
const pad = size === 'sm' ? '4px 10px' : size === 'lg' ? '14px 18px' : '9px 14px';
|
|
82
|
+
const fs = size === 'sm' ? 12 : size === 'lg' ? 18 : 14;
|
|
83
|
+
return (_jsxs("div", { onMouseDown: () => { if (!disabled)
|
|
84
|
+
setPressed(true); }, onMouseUp: () => setPressed(false), onMouseLeave: () => setPressed(false), onTouchStart: () => { if (!disabled)
|
|
85
|
+
setPressed(true); }, onTouchEnd: () => setPressed(false), onClick: () => { if (!disabled)
|
|
86
|
+
onClick?.(); }, style: { display: 'inline-block', width: fullWidth ? '100%' : undefined, position: 'relative', cursor: disabled ? 'not-allowed' : 'pointer', userSelect: 'none', opacity: disabled ? 0.55 : 1, ...style }, children: [_jsx("div", { style: { position: 'absolute', left: 1, top: 3, right: -1, bottom: -3, background: sh, borderRadius: 6, opacity: pressed ? 0 : 1 } }), _jsx("div", { style: {
|
|
87
|
+
position: 'relative', background: fg, borderRadius: 6, padding: pad,
|
|
88
|
+
transform: pressed ? 'translate(1px, 3px)' : 'translate(0,0)',
|
|
89
|
+
transition: 'transform 55ms linear',
|
|
90
|
+
textAlign: 'center',
|
|
91
|
+
fontFamily: 'm6x11plus, monospace', fontSize: fs, letterSpacing: 2,
|
|
92
|
+
color: '#fff', textShadow: '1px 1px 0 rgba(0,0,0,0.8)',
|
|
93
|
+
textTransform: 'uppercase', lineHeight: 1.1,
|
|
94
|
+
}, children: children })] }));
|
|
95
|
+
}
|
|
96
|
+
export function BalBackButton({ onClick }) {
|
|
97
|
+
return (_jsx("div", { style: { display: 'flex', justifyContent: 'center', width: '100%', padding: '8px 10px 10px' }, children: _jsx(BalButton, { tone: "orange", size: "md", onClick: onClick, style: { width: '66.666%' }, children: "Back" }) }));
|
|
98
|
+
}
|
|
66
99
|
export function JimboModal({ children, open, onClose, title, className }) {
|
|
67
100
|
const [visible, setVisible] = useState(open);
|
|
68
101
|
const [opacity, setOpacity] = useState(open ? 1 : 0);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ShowcaseFilter {
|
|
2
|
+
name: string;
|
|
3
|
+
author: string;
|
|
4
|
+
hits: string;
|
|
5
|
+
tone: 'blue' | 'red' | 'gold' | 'green';
|
|
6
|
+
sample: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface ShowcaseRecentFind {
|
|
9
|
+
seed: string;
|
|
10
|
+
filterName: string;
|
|
11
|
+
score: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ShowcaseLiveStats {
|
|
14
|
+
searched: string;
|
|
15
|
+
matches: string;
|
|
16
|
+
speed: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ShowcaseProps {
|
|
19
|
+
hotFilters?: ShowcaseFilter[];
|
|
20
|
+
recentFinds?: ShowcaseRecentFind[];
|
|
21
|
+
stats?: ShowcaseLiveStats;
|
|
22
|
+
onNewSearch?: () => void;
|
|
23
|
+
onBrowseFilters?: () => void;
|
|
24
|
+
onBack?: () => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function Showcase({ hotFilters, recentFinds, stats, onNewSearch, onBrowseFilters, onBack, }: ShowcaseProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { JimboColorOption } from './tokens.js';
|
|
4
|
+
import { BalButton } from './panel.js';
|
|
5
|
+
import { BalSprite } from './sprites.js';
|
|
6
|
+
const TONE_COLOR = {
|
|
7
|
+
blue: JimboColorOption.BLUE,
|
|
8
|
+
red: JimboColorOption.RED,
|
|
9
|
+
gold: JimboColorOption.GOLD,
|
|
10
|
+
green: JimboColorOption.GREEN,
|
|
11
|
+
};
|
|
12
|
+
const DEFAULT_STATS = { searched: '15.6B', matches: '2,847', speed: '5.4M/s' };
|
|
13
|
+
export function Showcase({ hotFilters = [], recentFinds = [], stats = DEFAULT_STATS, onNewSearch, onBrowseFilters, onBack, }) {
|
|
14
|
+
const C = JimboColorOption;
|
|
15
|
+
return (_jsxs("div", { style: {
|
|
16
|
+
width: '100%', height: '100%', background: C.DARKEST,
|
|
17
|
+
display: 'flex', flexDirection: 'column',
|
|
18
|
+
fontFamily: 'm6x11plus, monospace', color: C.WHITE, overflow: 'hidden',
|
|
19
|
+
}, children: [_jsxs("div", { style: { flex: 1, minHeight: 0, overflowY: 'auto', padding: '18px 14px 10px' }, children: [_jsxs("div", { style: { textAlign: 'center', marginBottom: 18 }, children: [_jsx("div", { style: { fontSize: 32, letterSpacing: 3, lineHeight: 1, color: C.GOLD, textShadow: '2px 2px 0 rgba(0,0,0,.8)' }, children: "Balatro" }), _jsx("div", { style: { fontSize: 14, letterSpacing: 4, color: C.GREY, marginTop: 4, textShadow: '1px 1px 0 rgba(0,0,0,.8)' }, children: "SEED \u00B7 CURATOR" })] }), _jsx("div", { style: {
|
|
20
|
+
background: C.DARK_GREY, borderRadius: 6, padding: 10,
|
|
21
|
+
border: `2px solid ${C.PANEL_EDGE}`, boxShadow: `0 2px 0 ${C.BLACK}`,
|
|
22
|
+
display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8, textAlign: 'center', marginBottom: 16,
|
|
23
|
+
}, children: [
|
|
24
|
+
[stats.searched, 'searched'],
|
|
25
|
+
[stats.matches, 'matches'],
|
|
26
|
+
[stats.speed, 'speed'],
|
|
27
|
+
].map(([n, l]) => (_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, color: C.GOLD, textShadow: '1px 1px 0 rgba(0,0,0,.8)' }, children: n }), _jsx("div", { style: { fontSize: 9, color: C.GREY, letterSpacing: 2, marginTop: 2 }, children: l.toUpperCase() })] }, l))) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }, children: [_jsx("div", { style: {
|
|
28
|
+
fontSize: 11, letterSpacing: 2, padding: '2px 8px',
|
|
29
|
+
background: C.BLUE, color: C.WHITE, borderRadius: 3,
|
|
30
|
+
textShadow: '1px 1px 0 rgba(0,0,0,.8)',
|
|
31
|
+
}, children: "HOT FILTERS" }), _jsx("div", { style: { flex: 1, height: 2, background: `${C.BLUE}55`, borderRadius: 1 } })] }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }, children: hotFilters.map((f, i) => {
|
|
32
|
+
const tColor = TONE_COLOR[f.tone];
|
|
33
|
+
return (_jsxs("div", { style: {
|
|
34
|
+
background: C.DARK_GREY, borderRadius: 6, padding: 10,
|
|
35
|
+
border: `2px solid ${tColor}`, boxShadow: `0 2px 0 ${C.BLACK}`,
|
|
36
|
+
display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
|
|
37
|
+
}, children: [_jsx("div", { style: { display: 'flex', gap: 2 }, children: f.sample.map((name, j) => (_jsx("div", { style: { width: 30, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx(BalSprite, { name: name, width: 28 }) }, j))) }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: {
|
|
38
|
+
fontSize: 13, color: C.WHITE, letterSpacing: 1,
|
|
39
|
+
textShadow: '1px 1px 0 rgba(0,0,0,.8)',
|
|
40
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
41
|
+
}, children: f.name }), _jsxs("div", { style: { fontSize: 9, color: C.GOLD_TEXT, letterSpacing: 1, marginTop: 2 }, children: ["by ", f.author] })] }), _jsxs("div", { style: { textAlign: 'right' }, children: [_jsx("div", { style: { fontSize: 14, color: tColor, textShadow: '1px 1px 0 rgba(0,0,0,.8)' }, children: f.hits }), _jsx("div", { style: { fontSize: 8, color: C.GREY, letterSpacing: 1 }, children: "seeds" })] })] }, i));
|
|
42
|
+
}) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }, children: [_jsx("div", { style: {
|
|
43
|
+
fontSize: 11, letterSpacing: 2, padding: '2px 8px',
|
|
44
|
+
background: C.GREEN, color: C.WHITE, borderRadius: 3,
|
|
45
|
+
textShadow: '1px 1px 0 rgba(0,0,0,.8)',
|
|
46
|
+
}, children: "RECENT FINDS" }), _jsx("div", { style: { flex: 1, height: 2, background: `${C.GREEN}55`, borderRadius: 1 } })] }), _jsx("div", { style: {
|
|
47
|
+
background: C.DARK_GREY, borderRadius: 6, padding: '8px 10px',
|
|
48
|
+
border: `2px solid ${C.PANEL_EDGE}`, boxShadow: `0 2px 0 ${C.BLACK}`,
|
|
49
|
+
fontSize: 11, color: C.GREY, letterSpacing: 1, lineHeight: 1.7,
|
|
50
|
+
}, children: recentFinds.length === 0 ? (_jsx("div", { style: { color: C.GREY }, children: "No recent finds yet." })) : recentFinds.map((r, i) => (_jsxs("div", { children: [_jsx("span", { style: { color: C.GOLD_TEXT }, children: r.seed }), ' · ', r.filterName, r.score > 0 && _jsxs("span", { style: { color: C.GREEN_TEXT }, children: [" +", r.score] })] }, i))) }), _jsx("div", { style: { height: 16 } })] }), _jsxs("div", { style: {
|
|
51
|
+
padding: '8px 10px 10px', borderTop: `2px solid ${C.BLACK}`, background: C.DARK_GREY,
|
|
52
|
+
display: 'flex', flexDirection: 'column', gap: 6,
|
|
53
|
+
}, children: [_jsx(BalButton, { tone: "green", fullWidth: true, size: "md", onClick: onNewSearch, children: "New Search" }), _jsx(BalButton, { tone: "blue", fullWidth: true, size: "md", onClick: onBrowseFilters, children: "Browse Filters" }), _jsx(BalButton, { tone: "orange", fullWidth: true, size: "md", onClick: onBack, children: "Back" })] })] }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type SpriteSheetType } from '../sprites/spriteMapper.js';
|
|
3
|
+
export interface BalSpriteProps {
|
|
4
|
+
name: string;
|
|
5
|
+
sheet?: SpriteSheetType;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
}
|
|
10
|
+
export declare function BalSprite({ name, sheet, width, height, style }: BalSpriteProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { resolveJamlAssetUrl } from '../assets.js';
|
|
4
|
+
import { getSpriteData } from '../sprites/spriteMapper.js';
|
|
5
|
+
const SHEET_META = {
|
|
6
|
+
Jokers: { cols: 10, rows: 16, assetKey: 'jokers' },
|
|
7
|
+
Tarots: { cols: 10, rows: 6, assetKey: 'tarots' },
|
|
8
|
+
Vouchers: { cols: 9, rows: 4, assetKey: 'vouchers' },
|
|
9
|
+
Boosters: { cols: 4, rows: 9, assetKey: 'boosters' },
|
|
10
|
+
BlindChips: { cols: 21, rows: 31, assetKey: 'blinds' },
|
|
11
|
+
tags: { cols: 6, rows: 5, assetKey: 'tags' },
|
|
12
|
+
Enhancers: { cols: 7, rows: 5, assetKey: 'enhancers' },
|
|
13
|
+
Editions: { cols: 5, rows: 1, assetKey: 'editions' },
|
|
14
|
+
};
|
|
15
|
+
export function BalSprite({ name, sheet, width = 40, height, style }) {
|
|
16
|
+
const sprite = getSpriteData(name);
|
|
17
|
+
const resolvedSheet = sheet ?? sprite?.type ?? 'Jokers';
|
|
18
|
+
const meta = SHEET_META[resolvedSheet];
|
|
19
|
+
const pos = sprite?.pos ?? { x: 0, y: 0 };
|
|
20
|
+
const h = height ?? width;
|
|
21
|
+
if (!meta)
|
|
22
|
+
return null;
|
|
23
|
+
const bgW = width * meta.cols;
|
|
24
|
+
const bgH = h * meta.rows;
|
|
25
|
+
const bgX = -(pos.x * width);
|
|
26
|
+
const bgY = -(pos.y * h);
|
|
27
|
+
return (_jsx("div", { style: {
|
|
28
|
+
width, height: h, flexShrink: 0,
|
|
29
|
+
backgroundImage: `url(${resolveJamlAssetUrl(meta.assetKey)})`,
|
|
30
|
+
backgroundSize: `${bgW}px ${bgH}px`,
|
|
31
|
+
backgroundPosition: `${bgX}px ${bgY}px`,
|
|
32
|
+
backgroundRepeat: 'no-repeat',
|
|
33
|
+
imageRendering: 'pixelated',
|
|
34
|
+
...style,
|
|
35
|
+
} }));
|
|
36
|
+
}
|
package/dist/ui.d.ts
CHANGED
package/dist/ui.js
CHANGED