jaml-ui 0.14.3 → 0.14.4
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 +197 -0
- package/package.json +5 -5
- package/assets/Balatro Seed Curator (DesignsV2)/.design-canvas.state.json +0 -1
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/BlindChips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/Boosters.json +0 -303
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/boosters.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/BlindChips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/blinds_metadata.json +0 -51
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/8BitDeck.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/Enhancers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/balatro-stake-chips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/enhancers_metadata.json +0 -52
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/playing_cards_metadata.json +0 -249
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/stakes.json +0 -19
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Editions.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Enhancers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Editions.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Jokers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/jokers.json +0 -1087
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers_metadata.json +0 -25
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.json +0 -191
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/Tarots.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/planets.json +0 -15
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/spectrals.json +0 -21
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/tarots.json +0 -163
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/Vouchers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/vouchers.json +0 -130
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/blinds.json +0 -51
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/boosters.json +0 -303
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/fonts/m6x11plusplus.otf +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/jokers.json +0 -1087
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/planets.json +0 -15
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/spectrals.json +0 -21
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/stakes.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/stickers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.json +0 -191
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/tarots.json +0 -163
- package/assets/Balatro Seed Curator (DesignsV2)/Assets/vouchers.json +0 -130
- package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail v2.html +0 -40
- package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail.html +0 -34
- package/assets/Balatro Seed Curator (DesignsV2)/public/fonts/m6x11plusplus.otf +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/src/AntePage.jsx +0 -228
- package/assets/Balatro Seed Curator (DesignsV2)/src/SeedDetail.jsx +0 -222
- package/assets/Balatro Seed Curator (DesignsV2)/src/app.jsx +0 -35
- package/assets/Balatro Seed Curator (DesignsV2)/src/mockData.js +0 -185
- package/assets/Balatro Seed Curator (DesignsV2)/src/sprites.jsx +0 -259
- package/assets/Balatro Seed Curator (DesignsV2)/src/tokens.js +0 -49
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/AntePageV2.jsx +0 -290
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/BalButton.jsx +0 -107
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlBuilderV2.jsx +0 -594
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlIde.jsx +0 -302
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SearchResultsV2.jsx +0 -286
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedDetailV2.jsx +0 -336
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedOGCard.jsx +0 -251
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/Showcase.jsx +0 -131
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/app.jsx +0 -55
- package/assets/Balatro Seed Curator (DesignsV2)/src/v2/data.js +0 -296
- package/assets/Balatro Seed Curator (DesignsV2)/starters/design-canvas.jsx +0 -622
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/8BitDeck.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/BlindChips.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Boosters.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Editions.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Enhancers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Jokers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/Tarots.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749540653-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749644934-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749661871-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749674748-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749703076-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749882759-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750354200-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750733265-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776751928925-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776800975060-0.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/stickers.png +0 -0
- package/assets/Balatro Seed Curator (DesignsV2)/uploads/tags.png +0 -0
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
// JamlBuilderV2 — mobile JAML filter builder.
|
|
2
|
-
//
|
|
3
|
-
// ┌──────────────────────┐
|
|
4
|
-
// │ ◂ BACK (full-width orange)
|
|
5
|
-
// ├──────────────────────┤
|
|
6
|
-
// │ Filter name · author │ editable top matter
|
|
7
|
-
// │ │
|
|
8
|
-
// │ MUST ━━━━━━━━━━━━━━━━│ blue zone rail
|
|
9
|
-
// │ [card] [card] [ ? ]│ clause cards + mystery-slot ADD tile
|
|
10
|
-
// │ │
|
|
11
|
-
// │ SHOULD ━━━━━━━━━━━━━━│ red zone rail
|
|
12
|
-
// │ [card] [card] [ ? ]│
|
|
13
|
-
// │ │
|
|
14
|
-
// │ MUST NOT ━━━━━━━━━━━━│ orange zone rail
|
|
15
|
-
// │ [ ? ] │
|
|
16
|
-
// │ │
|
|
17
|
-
// ├──────────────────────┤
|
|
18
|
-
// │ SEARCH (full-width green)
|
|
19
|
-
// └──────────────────────┘
|
|
20
|
-
//
|
|
21
|
-
// Bottom sheet cascade picker:
|
|
22
|
-
// Level 1 (category): Joker / Voucher / Tag / Boss / Tarot / Pack
|
|
23
|
-
// Level 2 (item): grid of sprites from that sheet — tap to commit
|
|
24
|
-
// Level 3 (settings): antes toggles 1-8, score (should-only), commit
|
|
25
|
-
//
|
|
26
|
-
// Snapshot: this is the mockup — no state persisted upward, but structure is real.
|
|
27
|
-
|
|
28
|
-
const { useState: bUS, useRef: bUR, useEffect: bUE, useMemo: bUM } = React;
|
|
29
|
-
const Col = window.JimboColor;
|
|
30
|
-
|
|
31
|
-
// ── ZONE META ──
|
|
32
|
-
const ZONES = {
|
|
33
|
-
must: { label: 'MUST', hint: 'Seed must contain all of these.', color: Col.BLUE, accent: '#4db5ff' },
|
|
34
|
-
should: { label: 'SHOULD', hint: 'Bonus points per match.', color: Col.RED, accent: '#ff8076' },
|
|
35
|
-
mustnot: { label: 'MUST NOT', hint: 'Seed is rejected if any appear.', color: Col.ORANGE, accent: '#ffb84d' },
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// ── CATEGORY META (for the picker) ──
|
|
39
|
-
const CATEGORIES = [
|
|
40
|
-
{ id: 'joker', label: 'JOKER', sheet: 'jokers', render: (n,s) => <JokerMini name={n} size={s} />, detect: 'jokers' },
|
|
41
|
-
{ id: 'souljoker', label: 'SOUL', sheet: 'jokers', render: (n,s) => <JokerMini name={n} size={s} />, filter: ['perkeo','triboulet','yorick','chicot','canio'] },
|
|
42
|
-
{ id: 'voucher', label: 'VOUCHER', sheet: 'vouchers', render: (n,s) => <VoucherMini name={n} size={s} /> },
|
|
43
|
-
{ id: 'smallblindtag', label: 'SMALL TAG',sheet: 'tags', render: (n,s) => <TagChip name={n} size={s} /> },
|
|
44
|
-
{ id: 'bigblindtag', label: 'BIG TAG', sheet: 'tags', render: (n,s) => <TagChip name={n} size={s} /> },
|
|
45
|
-
{ id: 'boss', label: 'BOSS', sheet: 'blinds', render: (n,s) => <BossChip name={n} size={s} /> },
|
|
46
|
-
{ id: 'tarot', label: 'TAROT', sheet: 'tarots', render: (n,s) => <TarotMini name={n} size={s} /> },
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
// ── Chunky 3D Button (DEPRECATED — use window.BalButton; kept only for zone commit color overrides) ──
|
|
50
|
-
function ChunkyButton({ color = Col.ORANGE, shadow = Col.DARK_ORANGE, children, onClick, style, fullWidth, small }) {
|
|
51
|
-
const [pressed, setPressed] = bUS(false);
|
|
52
|
-
return (
|
|
53
|
-
<div
|
|
54
|
-
onMouseDown={() => setPressed(true)}
|
|
55
|
-
onMouseUp={() => setPressed(false)}
|
|
56
|
-
onMouseLeave={() => setPressed(false)}
|
|
57
|
-
onTouchStart={() => setPressed(true)}
|
|
58
|
-
onTouchEnd={() => setPressed(false)}
|
|
59
|
-
onClick={onClick}
|
|
60
|
-
style={{
|
|
61
|
-
display: 'inline-block',
|
|
62
|
-
width: fullWidth ? '100%' : 'auto',
|
|
63
|
-
cursor: 'pointer', userSelect: 'none',
|
|
64
|
-
paddingBottom: pressed ? 0 : 4,
|
|
65
|
-
transition: 'padding-bottom 60ms ease-out',
|
|
66
|
-
...style,
|
|
67
|
-
}}
|
|
68
|
-
>
|
|
69
|
-
<div style={{
|
|
70
|
-
position: 'relative',
|
|
71
|
-
background: shadow,
|
|
72
|
-
borderRadius: 6,
|
|
73
|
-
paddingBottom: pressed ? 0 : 4,
|
|
74
|
-
boxShadow: `inset 0 -4px 0 ${shadow}`,
|
|
75
|
-
}}>
|
|
76
|
-
<div style={{
|
|
77
|
-
background: color,
|
|
78
|
-
borderRadius: 6,
|
|
79
|
-
padding: small ? '6px 12px' : '12px 16px',
|
|
80
|
-
transform: pressed ? 'translateY(4px)' : 'translateY(0)',
|
|
81
|
-
transition: 'transform 60ms ease-out',
|
|
82
|
-
fontFamily: 'm6x11plus, monospace',
|
|
83
|
-
color: Col.WHITE,
|
|
84
|
-
letterSpacing: 2,
|
|
85
|
-
fontSize: small ? 12 : 16,
|
|
86
|
-
textAlign: 'center',
|
|
87
|
-
textShadow: `0 1px 0 ${shadow}`,
|
|
88
|
-
boxShadow: `inset 0 2px 0 rgba(255,255,255,.18), inset 0 -2px 0 rgba(0,0,0,.22)`,
|
|
89
|
-
}}>{children}</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Ante chip pill (1..8) ──
|
|
96
|
-
function AnteChip({ n, active, onToggle }) {
|
|
97
|
-
return (
|
|
98
|
-
<div
|
|
99
|
-
onClick={onToggle}
|
|
100
|
-
style={{
|
|
101
|
-
width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
102
|
-
background: active ? Col.GOLD : Col.DARKEST,
|
|
103
|
-
border: `2px solid ${active ? Col.GOLD_TEXT : Col.PANEL_EDGE}`,
|
|
104
|
-
borderRadius: 14,
|
|
105
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 13,
|
|
106
|
-
color: active ? Col.BLACK : Col.GREY,
|
|
107
|
-
cursor: 'pointer', userSelect: 'none',
|
|
108
|
-
boxShadow: active ? `0 0 6px ${Col.GOLD}aa` : 'none',
|
|
109
|
-
}}
|
|
110
|
-
>{n}</div>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ── Preview renderer for a clause item ──
|
|
115
|
-
function clauseSprite(c, size = 40) {
|
|
116
|
-
const cat = CATEGORIES.find(x => x.id === c.type);
|
|
117
|
-
if (!cat) return null;
|
|
118
|
-
return cat.render(c.value, size);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── ClauseCard — horizontal compact card ──
|
|
122
|
-
function ClauseCard({ clause, zoneKey, onRemove, onEdit }) {
|
|
123
|
-
const z = ZONES[zoneKey];
|
|
124
|
-
return (
|
|
125
|
-
<div
|
|
126
|
-
onClick={onEdit}
|
|
127
|
-
style={{
|
|
128
|
-
position: 'relative',
|
|
129
|
-
background: Col.DARK_GREY,
|
|
130
|
-
border: `2px solid ${z.color}`,
|
|
131
|
-
borderRadius: 6,
|
|
132
|
-
padding: '8px 8px 8px 6px',
|
|
133
|
-
display: 'flex', alignItems: 'center', gap: 8,
|
|
134
|
-
minWidth: 0, cursor: 'pointer',
|
|
135
|
-
boxShadow: `0 2px 0 ${Col.BLACK}`,
|
|
136
|
-
}}
|
|
137
|
-
>
|
|
138
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 44, height: 50, flexShrink: 0 }}>
|
|
139
|
-
{clauseSprite(clause, 40)}
|
|
140
|
-
</div>
|
|
141
|
-
<div style={{ flex: 1, minWidth: 0 }}>
|
|
142
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 13, color: Col.WHITE, letterSpacing: 0.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
143
|
-
{clause.label || clause.value}
|
|
144
|
-
</div>
|
|
145
|
-
<div style={{ display: 'flex', gap: 3, marginTop: 3, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
146
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 8, color: Col.GREY, letterSpacing: 1 }}>A</div>
|
|
147
|
-
{clause.antes.map(a => (
|
|
148
|
-
<div key={a} style={{
|
|
149
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 9,
|
|
150
|
-
padding: '1px 4px', background: Col.DARKEST, color: z.accent,
|
|
151
|
-
borderRadius: 3, letterSpacing: 0.5, lineHeight: 1,
|
|
152
|
-
}}>{a}</div>
|
|
153
|
-
))}
|
|
154
|
-
{zoneKey === 'should' && clause.score != null && (
|
|
155
|
-
<div style={{
|
|
156
|
-
marginLeft: 4,
|
|
157
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 9,
|
|
158
|
-
padding: '1px 5px', background: Col.RED, color: Col.WHITE,
|
|
159
|
-
borderRadius: 3, letterSpacing: 0.5, lineHeight: 1,
|
|
160
|
-
}}>+{clause.score}</div>
|
|
161
|
-
)}
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
<button
|
|
165
|
-
onClick={(e) => { e.stopPropagation(); onRemove(); }}
|
|
166
|
-
style={{
|
|
167
|
-
width: 22, height: 22, flexShrink: 0,
|
|
168
|
-
border: `2px solid ${Col.BLACK}`, borderRadius: 4,
|
|
169
|
-
background: Col.RED, color: Col.WHITE,
|
|
170
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 12, lineHeight: 1,
|
|
171
|
-
cursor: 'pointer', padding: 0,
|
|
172
|
-
boxShadow: `inset 0 1px 0 rgba(255,255,255,.2), 0 2px 0 ${Col.BLACK}`,
|
|
173
|
-
}}
|
|
174
|
-
>×</button>
|
|
175
|
-
</div>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ── MysteryAddTile — the "?" placeholder ──
|
|
180
|
-
function MysteryAddTile({ zoneKey, onTap }) {
|
|
181
|
-
const z = ZONES[zoneKey];
|
|
182
|
-
return (
|
|
183
|
-
<div
|
|
184
|
-
onClick={onTap}
|
|
185
|
-
style={{
|
|
186
|
-
cursor: 'pointer',
|
|
187
|
-
border: `2px dashed ${z.color}`,
|
|
188
|
-
borderRadius: 6,
|
|
189
|
-
padding: '12px 8px',
|
|
190
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
|
|
191
|
-
background: `${z.color}0d`,
|
|
192
|
-
minHeight: 60,
|
|
193
|
-
}}
|
|
194
|
-
>
|
|
195
|
-
<div style={{
|
|
196
|
-
width: 40, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
197
|
-
background: Col.DARKEST, border: `2px solid ${z.color}`, borderRadius: 6,
|
|
198
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 24, color: z.color,
|
|
199
|
-
}}>?</div>
|
|
200
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 12, color: z.accent, letterSpacing: 2 }}>
|
|
201
|
-
ADD TO {z.label}
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ── ZoneRail ──
|
|
208
|
-
function ZoneRail({ zoneKey, clauses, onAdd, onRemove, onEdit }) {
|
|
209
|
-
const z = ZONES[zoneKey];
|
|
210
|
-
return (
|
|
211
|
-
<div>
|
|
212
|
-
{/* Zone header: colored pill + hint */}
|
|
213
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
|
|
214
|
-
<div style={{
|
|
215
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 11,
|
|
216
|
-
padding: '2px 8px',
|
|
217
|
-
background: z.color, color: Col.WHITE,
|
|
218
|
-
borderRadius: 3, letterSpacing: 2,
|
|
219
|
-
boxShadow: `0 2px 0 ${Col.BLACK}`,
|
|
220
|
-
}}>{z.label}</div>
|
|
221
|
-
<div style={{ flex: 1, height: 2, background: `${z.color}55`, borderRadius: 1 }} />
|
|
222
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 8, color: Col.GREY }}>
|
|
223
|
-
{clauses.length}
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: Col.GREY, letterSpacing: 0.5, marginBottom: 8 }}>
|
|
227
|
-
{z.hint}
|
|
228
|
-
</div>
|
|
229
|
-
|
|
230
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
231
|
-
{clauses.map(c => (
|
|
232
|
-
<ClauseCard
|
|
233
|
-
key={c.id}
|
|
234
|
-
clause={c}
|
|
235
|
-
zoneKey={zoneKey}
|
|
236
|
-
onRemove={() => onRemove(c.id)}
|
|
237
|
-
onEdit={() => onEdit(c)}
|
|
238
|
-
/>
|
|
239
|
-
))}
|
|
240
|
-
<MysteryAddTile zoneKey={zoneKey} onTap={onAdd} />
|
|
241
|
-
</div>
|
|
242
|
-
</div>
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// ── PICKER: cascade bottom sheet ──
|
|
247
|
-
function PickerSheet({ open, zoneKey, editing, onClose, onCommit }) {
|
|
248
|
-
// Steps: 'cat' (category) → 'item' (sprite grid) → 'settings' (antes/score/commit)
|
|
249
|
-
const [step, setStep] = bUS('cat');
|
|
250
|
-
const [cat, setCat] = bUS(null); // selected CATEGORIES entry
|
|
251
|
-
const [item, setItem] = bUS(null); // selected item name
|
|
252
|
-
const [antes, setAntes] = bUS([1]);
|
|
253
|
-
const [score, setScore] = bUS(1);
|
|
254
|
-
|
|
255
|
-
bUE(() => {
|
|
256
|
-
if (open) {
|
|
257
|
-
if (editing) {
|
|
258
|
-
setCat(CATEGORIES.find(c => c.id === editing.type));
|
|
259
|
-
setItem(editing.value);
|
|
260
|
-
setAntes(editing.antes || [1]);
|
|
261
|
-
setScore(editing.score ?? 1);
|
|
262
|
-
setStep('settings');
|
|
263
|
-
} else {
|
|
264
|
-
setStep('cat');
|
|
265
|
-
setCat(null);
|
|
266
|
-
setItem(null);
|
|
267
|
-
setAntes([1]);
|
|
268
|
-
setScore(1);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}, [open, editing]);
|
|
272
|
-
|
|
273
|
-
if (!open) return null;
|
|
274
|
-
|
|
275
|
-
const z = ZONES[zoneKey];
|
|
276
|
-
const toggleAnte = (a) => setAntes(prev => prev.includes(a) ? prev.filter(x => x !== a) : [...prev, a].sort((x,y)=>x-y));
|
|
277
|
-
|
|
278
|
-
const commit = () => {
|
|
279
|
-
onCommit({
|
|
280
|
-
id: editing?.id || `c${Math.random().toString(36).slice(2,7)}`,
|
|
281
|
-
type: cat.id,
|
|
282
|
-
value: item,
|
|
283
|
-
antes: antes.length ? antes : [1],
|
|
284
|
-
score: zoneKey === 'should' ? score : undefined,
|
|
285
|
-
label: item,
|
|
286
|
-
});
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
// Build item list for the current category
|
|
290
|
-
const items = bUM(() => {
|
|
291
|
-
if (!cat) return [];
|
|
292
|
-
if (cat.filter) return cat.filter;
|
|
293
|
-
const map = window.SPRITE_MAPS?.[cat.sheet];
|
|
294
|
-
if (map) return Object.keys(map);
|
|
295
|
-
return [];
|
|
296
|
-
}, [cat]);
|
|
297
|
-
|
|
298
|
-
return (
|
|
299
|
-
<>
|
|
300
|
-
{/* Scrim */}
|
|
301
|
-
<div
|
|
302
|
-
onClick={onClose}
|
|
303
|
-
style={{
|
|
304
|
-
position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.7)', zIndex: 80,
|
|
305
|
-
animation: 'fadeIn 180ms ease-out',
|
|
306
|
-
}}
|
|
307
|
-
/>
|
|
308
|
-
{/* Sheet */}
|
|
309
|
-
<div style={{
|
|
310
|
-
position: 'absolute', left: 0, right: 0, bottom: 0, zIndex: 81,
|
|
311
|
-
background: Col.DARKEST,
|
|
312
|
-
borderTop: `3px solid ${z.color}`,
|
|
313
|
-
borderRadius: '14px 14px 0 0',
|
|
314
|
-
boxShadow: `0 -8px 24px rgba(0,0,0,.5)`,
|
|
315
|
-
animation: 'slideUp 220ms cubic-bezier(.2,.8,.2,1)',
|
|
316
|
-
maxHeight: '80%', display: 'flex', flexDirection: 'column',
|
|
317
|
-
}}>
|
|
318
|
-
{/* Handle */}
|
|
319
|
-
<div style={{ display: 'flex', justifyContent: 'center', padding: '6px 0 2px' }}>
|
|
320
|
-
<div style={{ width: 36, height: 4, background: Col.GREY, borderRadius: 2 }} />
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
{/* Header */}
|
|
324
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 12px 8px', borderBottom: `2px solid ${Col.BLACK}` }}>
|
|
325
|
-
{step !== 'cat' && (
|
|
326
|
-
<button onClick={() => setStep(step === 'settings' ? 'item' : 'cat')} style={{
|
|
327
|
-
background: Col.DARK_GREY, border: `2px solid ${Col.BLACK}`, borderRadius: 4,
|
|
328
|
-
width: 26, height: 26, color: Col.WHITE, cursor: 'pointer', padding: 0,
|
|
329
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 14,
|
|
330
|
-
}}>◂</button>
|
|
331
|
-
)}
|
|
332
|
-
<div style={{
|
|
333
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 11,
|
|
334
|
-
padding: '2px 8px', background: z.color, color: Col.WHITE,
|
|
335
|
-
borderRadius: 3, letterSpacing: 2,
|
|
336
|
-
}}>{z.label}</div>
|
|
337
|
-
<div style={{ flex: 1, fontFamily: 'm6x11plus, monospace', fontSize: 13, color: Col.WHITE, letterSpacing: 1 }}>
|
|
338
|
-
{step === 'cat' && 'Pick type'}
|
|
339
|
-
{step === 'item' && cat?.label}
|
|
340
|
-
{step === 'settings' && (item || '—')}
|
|
341
|
-
</div>
|
|
342
|
-
<button onClick={onClose} style={{
|
|
343
|
-
background: Col.DARK_GREY, border: `2px solid ${Col.BLACK}`, borderRadius: 4,
|
|
344
|
-
width: 26, height: 26, color: Col.WHITE, cursor: 'pointer', padding: 0,
|
|
345
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 12,
|
|
346
|
-
}}>×</button>
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
{/* Body */}
|
|
350
|
-
<div style={{ flex: 1, overflowY: 'auto', padding: 12 }}>
|
|
351
|
-
{step === 'cat' && (
|
|
352
|
-
<div style={{
|
|
353
|
-
display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8,
|
|
354
|
-
}}>
|
|
355
|
-
{CATEGORIES.map(c => (
|
|
356
|
-
<div
|
|
357
|
-
key={c.id}
|
|
358
|
-
onClick={() => { setCat(c); setStep('item'); }}
|
|
359
|
-
style={{
|
|
360
|
-
padding: 12, background: Col.DARK_GREY, borderRadius: 6,
|
|
361
|
-
border: `2px solid ${Col.PANEL_EDGE}`,
|
|
362
|
-
display: 'flex', alignItems: 'center', gap: 10,
|
|
363
|
-
cursor: 'pointer',
|
|
364
|
-
boxShadow: `0 2px 0 ${Col.BLACK}`,
|
|
365
|
-
}}
|
|
366
|
-
>
|
|
367
|
-
<div style={{ width: 32, height: 38, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
368
|
-
{c.render(
|
|
369
|
-
c.id === 'joker' ? 'joker' :
|
|
370
|
-
c.id === 'souljoker' ? 'perkeo' :
|
|
371
|
-
c.id === 'voucher' ? 'overstock' :
|
|
372
|
-
c.id === 'smallblindtag' || c.id === 'bigblindtag' ? 'uncommontag' :
|
|
373
|
-
c.id === 'boss' ? 'thehook' :
|
|
374
|
-
c.id === 'tarot' ? 'thefool' : '',
|
|
375
|
-
28,
|
|
376
|
-
)}
|
|
377
|
-
</div>
|
|
378
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 12, color: Col.WHITE, letterSpacing: 1.5 }}>
|
|
379
|
-
{c.label}
|
|
380
|
-
</div>
|
|
381
|
-
</div>
|
|
382
|
-
))}
|
|
383
|
-
</div>
|
|
384
|
-
)}
|
|
385
|
-
|
|
386
|
-
{step === 'item' && cat && (
|
|
387
|
-
<div style={{
|
|
388
|
-
display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 6,
|
|
389
|
-
}}>
|
|
390
|
-
{items.map(name => (
|
|
391
|
-
<div
|
|
392
|
-
key={name}
|
|
393
|
-
onClick={() => { setItem(name); setStep('settings'); }}
|
|
394
|
-
style={{
|
|
395
|
-
padding: 4,
|
|
396
|
-
background: item === name ? `${z.color}33` : Col.DARK_GREY,
|
|
397
|
-
border: `2px solid ${item === name ? z.color : Col.PANEL_EDGE}`,
|
|
398
|
-
borderRadius: 5,
|
|
399
|
-
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
|
|
400
|
-
cursor: 'pointer', minHeight: 60,
|
|
401
|
-
boxShadow: `0 2px 0 ${Col.BLACK}`,
|
|
402
|
-
}}
|
|
403
|
-
>
|
|
404
|
-
{cat.render(name, 36)}
|
|
405
|
-
</div>
|
|
406
|
-
))}
|
|
407
|
-
{!items.length && (
|
|
408
|
-
<div style={{ gridColumn: '1/-1', color: Col.GREY, fontSize: 11, fontFamily: 'm6x11plus, monospace', textAlign: 'center', padding: 20 }}>
|
|
409
|
-
Loading sprites…
|
|
410
|
-
</div>
|
|
411
|
-
)}
|
|
412
|
-
</div>
|
|
413
|
-
)}
|
|
414
|
-
|
|
415
|
-
{step === 'settings' && cat && item && (
|
|
416
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
|
417
|
-
{/* Preview */}
|
|
418
|
-
<div style={{
|
|
419
|
-
display: 'flex', alignItems: 'center', gap: 12,
|
|
420
|
-
padding: 10, background: Col.DARK_GREY, borderRadius: 6,
|
|
421
|
-
border: `2px solid ${z.color}`,
|
|
422
|
-
}}>
|
|
423
|
-
{cat.render(item, 52)}
|
|
424
|
-
<div style={{ flex: 1 }}>
|
|
425
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 15, color: Col.WHITE, letterSpacing: 0.5 }}>{item}</div>
|
|
426
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 10, color: Col.GREY, letterSpacing: 1 }}>{cat.label}</div>
|
|
427
|
-
</div>
|
|
428
|
-
</div>
|
|
429
|
-
|
|
430
|
-
{/* Antes */}
|
|
431
|
-
<div>
|
|
432
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 11, color: Col.GREY, letterSpacing: 2, marginBottom: 6 }}>
|
|
433
|
-
SEARCH ANTES
|
|
434
|
-
</div>
|
|
435
|
-
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
436
|
-
{[1,2,3,4,5,6,7,8].map(n => (
|
|
437
|
-
<AnteChip key={n} n={n} active={antes.includes(n)} onToggle={() => toggleAnte(n)} />
|
|
438
|
-
))}
|
|
439
|
-
</div>
|
|
440
|
-
</div>
|
|
441
|
-
|
|
442
|
-
{/* Score (should only) */}
|
|
443
|
-
{zoneKey === 'should' && (
|
|
444
|
-
<div>
|
|
445
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 11, color: Col.GREY, letterSpacing: 2, marginBottom: 6 }}>
|
|
446
|
-
SCORE ON MATCH
|
|
447
|
-
</div>
|
|
448
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
449
|
-
<button onClick={() => setScore(Math.max(0, score - 1))} style={stepperBtn}>–</button>
|
|
450
|
-
<div style={{
|
|
451
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 22, color: Col.RED,
|
|
452
|
-
minWidth: 52, textAlign: 'center',
|
|
453
|
-
border: `2px solid ${Col.RED}`, borderRadius: 6,
|
|
454
|
-
padding: '4px 10px', background: Col.DARKEST,
|
|
455
|
-
}}>+{score}</div>
|
|
456
|
-
<button onClick={() => setScore(score + 1)} style={stepperBtn}>+</button>
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
)}
|
|
460
|
-
|
|
461
|
-
{/* Commit button — color matches zone */}
|
|
462
|
-
<window.BalButton
|
|
463
|
-
fullWidth
|
|
464
|
-
color={z.color}
|
|
465
|
-
shadow={zoneKey === 'must' ? Col.DARK_BLUE : zoneKey === 'should' ? Col.DARK_RED : Col.DARK_ORANGE}
|
|
466
|
-
onClick={commit}
|
|
467
|
-
>{editing ? 'Update' : 'Add'}</window.BalButton>
|
|
468
|
-
</div>
|
|
469
|
-
)}
|
|
470
|
-
</div>
|
|
471
|
-
</div>
|
|
472
|
-
</>
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const stepperBtn = {
|
|
477
|
-
width: 38, height: 38,
|
|
478
|
-
background: Col.DARK_GREY, border: `2px solid ${Col.BLACK}`, borderRadius: 6,
|
|
479
|
-
color: Col.WHITE, fontFamily: 'm6x11plus, monospace', fontSize: 20, cursor: 'pointer',
|
|
480
|
-
boxShadow: `inset 0 1px 0 rgba(255,255,255,.15), 0 2px 0 ${Col.BLACK}`,
|
|
481
|
-
padding: 0,
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
// ── Top matter: filter name / author / description ──
|
|
485
|
-
function TopMatter({ name, author, desc }) {
|
|
486
|
-
return (
|
|
487
|
-
<div style={{
|
|
488
|
-
background: Col.DARK_GREY, borderRadius: 6, padding: 10,
|
|
489
|
-
border: `2px solid ${Col.PANEL_EDGE}`,
|
|
490
|
-
boxShadow: `0 2px 0 ${Col.BLACK}`,
|
|
491
|
-
}}>
|
|
492
|
-
<input
|
|
493
|
-
defaultValue={name}
|
|
494
|
-
style={{
|
|
495
|
-
display: 'block', width: '100%', background: 'transparent', border: 'none', outline: 'none',
|
|
496
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 18, color: Col.WHITE, letterSpacing: 1,
|
|
497
|
-
padding: 0, marginBottom: 4,
|
|
498
|
-
}}
|
|
499
|
-
/>
|
|
500
|
-
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
501
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: Col.GREY, letterSpacing: 2 }}>BY</div>
|
|
502
|
-
<input
|
|
503
|
-
defaultValue={author}
|
|
504
|
-
style={{
|
|
505
|
-
flex: 1, background: 'transparent', border: 'none', outline: 'none',
|
|
506
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 12, color: Col.GOLD_TEXT, letterSpacing: 1,
|
|
507
|
-
padding: 0,
|
|
508
|
-
}}
|
|
509
|
-
/>
|
|
510
|
-
</div>
|
|
511
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 10, color: Col.GREY, marginTop: 6, lineHeight: 1.35 }}>
|
|
512
|
-
{desc}
|
|
513
|
-
</div>
|
|
514
|
-
</div>
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// ── Main ──
|
|
519
|
-
function JamlBuilderV2({ initial }) {
|
|
520
|
-
const base = initial || window.FILTER_V2;
|
|
521
|
-
const [must, setMust] = bUS(() => (base.must || []).map(c => ({ ...c })));
|
|
522
|
-
const [should, setShould] = bUS(() => (base.should || []).map(c => ({ ...c })));
|
|
523
|
-
const [mustnot, setMustNot] = bUS([]);
|
|
524
|
-
|
|
525
|
-
const [picker, setPicker] = bUS({ open: false, zone: null, editing: null });
|
|
526
|
-
|
|
527
|
-
const openAdd = (zone) => setPicker({ open: true, zone, editing: null });
|
|
528
|
-
const openEdit = (zone, c) => setPicker({ open: true, zone, editing: c });
|
|
529
|
-
const close = () => setPicker({ open: false, zone: null, editing: null });
|
|
530
|
-
|
|
531
|
-
const commit = (clause) => {
|
|
532
|
-
const setters = { must: setMust, should: setShould, mustnot: setMustNot };
|
|
533
|
-
const current = { must, should, mustnot }[picker.zone];
|
|
534
|
-
const next = picker.editing
|
|
535
|
-
? current.map(c => c.id === clause.id ? clause : c)
|
|
536
|
-
: [...current, clause];
|
|
537
|
-
setters[picker.zone](next);
|
|
538
|
-
close();
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
const remove = (zone, id) => {
|
|
542
|
-
const setters = { must: setMust, should: setShould, mustnot: setMustNot };
|
|
543
|
-
const current = { must, should, mustnot }[zone];
|
|
544
|
-
setters[zone](current.filter(c => c.id !== id));
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
return (
|
|
548
|
-
<div style={{
|
|
549
|
-
width: '100%', height: '100%', background: Col.DARKEST,
|
|
550
|
-
position: 'relative', overflow: 'hidden',
|
|
551
|
-
display: 'flex', flexDirection: 'column',
|
|
552
|
-
fontFamily: 'm6x11plus, monospace', color: Col.WHITE,
|
|
553
|
-
}}>
|
|
554
|
-
{/* Scroll body — NO top bar; Back docks to bottom */}
|
|
555
|
-
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: 10, display: 'flex', flexDirection: 'column', gap: 14 }}>
|
|
556
|
-
<TopMatter name={base.name} author={base.author} desc={base.description} />
|
|
557
|
-
|
|
558
|
-
<ZoneRail zoneKey="must" clauses={must} onAdd={() => openAdd('must')} onRemove={(id) => remove('must', id)} onEdit={(c) => openEdit('must', c)} />
|
|
559
|
-
<ZoneRail zoneKey="should" clauses={should} onAdd={() => openAdd('should')} onRemove={(id) => remove('should', id)} onEdit={(c) => openEdit('should', c)} />
|
|
560
|
-
<ZoneRail zoneKey="mustnot" clauses={mustnot} onAdd={() => openAdd('mustnot')} onRemove={(id) => remove('mustnot', id)} onEdit={(c) => openEdit('mustnot', c)} />
|
|
561
|
-
|
|
562
|
-
<div style={{ height: 20 }} />
|
|
563
|
-
</div>
|
|
564
|
-
|
|
565
|
-
{/* Bottom: SEARCH (green, full-width) then BACK (orange, full-width, BOTTOM-MOST, thumb zone) */}
|
|
566
|
-
<div style={{ padding: '8px 10px 10px', borderTop: `2px solid ${Col.BLACK}`, background: Col.DARK_GREY, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
567
|
-
<window.BalButton tone="green" fullWidth size="md">Search</window.BalButton>
|
|
568
|
-
<window.BalButton tone="orange" fullWidth size="md">Back</window.BalButton>
|
|
569
|
-
</div>
|
|
570
|
-
|
|
571
|
-
<PickerSheet
|
|
572
|
-
open={picker.open}
|
|
573
|
-
zoneKey={picker.zone || 'must'}
|
|
574
|
-
editing={picker.editing}
|
|
575
|
-
onClose={close}
|
|
576
|
-
onCommit={commit}
|
|
577
|
-
/>
|
|
578
|
-
</div>
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Keyframes
|
|
583
|
-
(function injectBuilderKf(){
|
|
584
|
-
if (document.getElementById('v2-builder-kf')) return;
|
|
585
|
-
const s = document.createElement('style');
|
|
586
|
-
s.id = 'v2-builder-kf';
|
|
587
|
-
s.textContent = `
|
|
588
|
-
@keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
|
|
589
|
-
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
590
|
-
`;
|
|
591
|
-
document.head.appendChild(s);
|
|
592
|
-
})();
|
|
593
|
-
|
|
594
|
-
window.JamlBuilderV2 = JamlBuilderV2;
|