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.
@@ -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 declare function JamlIde({ jaml, onChange, defaultMode, searchResults, className, title, actions, codePlaceholder, onSearch, isSearching, }: JamlIdeProps): import("react/jsx-runtime").JSX.Element;
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",
@@ -1,4 +1,4 @@
1
- export type JamlIdeMode = "code" | "map" | "results";
1
+ export type JamlIdeMode = "visual" | "code" | "map" | "results";
2
2
  export interface JamlIdeToolbarProps {
3
3
  mode: JamlIdeMode;
4
4
  onModeChange: (mode: JamlIdeMode) => void;
@@ -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";
@@ -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
@@ -2,3 +2,5 @@ export * from './ui/tokens.js';
2
2
  export * from './ui/panel.js';
3
3
  export * from './ui/codeBlock.js';
4
4
  export * from './ui/footer.js';
5
+ export * from './ui/sprites.js';
6
+ export * from './ui/showcase.js';
package/dist/ui.js CHANGED
@@ -2,3 +2,5 @@ export * from './ui/tokens.js';
2
2
  export * from './ui/panel.js';
3
3
  export * from './ui/codeBlock.js';
4
4
  export * from './ui/footer.js';
5
+ export * from './ui/sprites.js';
6
+ export * from './ui/showcase.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaml-ui",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",