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.
Files changed (84) hide show
  1. package/DESIGN.md +197 -0
  2. package/package.json +5 -5
  3. package/assets/Balatro Seed Curator (DesignsV2)/.design-canvas.state.json +0 -1
  4. package/assets/Balatro Seed Curator (DesignsV2)/Assets/BlindChips.png +0 -0
  5. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/Boosters.json +0 -303
  6. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/boosters.png +0 -0
  7. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters.png +0 -0
  8. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/BlindChips.png +0 -0
  9. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/blinds_metadata.json +0 -51
  10. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/8BitDeck.png +0 -0
  11. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/Enhancers.png +0 -0
  12. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/balatro-stake-chips.png +0 -0
  13. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/enhancers_metadata.json +0 -52
  14. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/playing_cards_metadata.json +0 -249
  15. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/stakes.json +0 -19
  16. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Editions.png +0 -0
  17. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Enhancers.png +0 -0
  18. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Editions.png +0 -0
  19. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Jokers.png +0 -0
  20. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/jokers.json +0 -1087
  21. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers.png +0 -0
  22. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers_metadata.json +0 -25
  23. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers.png +0 -0
  24. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.json +0 -191
  25. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.png +0 -0
  26. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/Tarots.png +0 -0
  27. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/planets.json +0 -15
  28. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/spectrals.json +0 -21
  29. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/tarots.json +0 -163
  30. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots.png +0 -0
  31. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/Vouchers.png +0 -0
  32. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/vouchers.json +0 -130
  33. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers.png +0 -0
  34. package/assets/Balatro Seed Curator (DesignsV2)/Assets/blinds.json +0 -51
  35. package/assets/Balatro Seed Curator (DesignsV2)/Assets/boosters.json +0 -303
  36. package/assets/Balatro Seed Curator (DesignsV2)/Assets/fonts/m6x11plusplus.otf +0 -0
  37. package/assets/Balatro Seed Curator (DesignsV2)/Assets/jokers.json +0 -1087
  38. package/assets/Balatro Seed Curator (DesignsV2)/Assets/planets.json +0 -15
  39. package/assets/Balatro Seed Curator (DesignsV2)/Assets/spectrals.json +0 -21
  40. package/assets/Balatro Seed Curator (DesignsV2)/Assets/stakes.png +0 -0
  41. package/assets/Balatro Seed Curator (DesignsV2)/Assets/stickers.png +0 -0
  42. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.json +0 -191
  43. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.png +0 -0
  44. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tarots.json +0 -163
  45. package/assets/Balatro Seed Curator (DesignsV2)/Assets/vouchers.json +0 -130
  46. package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail v2.html +0 -40
  47. package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail.html +0 -34
  48. package/assets/Balatro Seed Curator (DesignsV2)/public/fonts/m6x11plusplus.otf +0 -0
  49. package/assets/Balatro Seed Curator (DesignsV2)/src/AntePage.jsx +0 -228
  50. package/assets/Balatro Seed Curator (DesignsV2)/src/SeedDetail.jsx +0 -222
  51. package/assets/Balatro Seed Curator (DesignsV2)/src/app.jsx +0 -35
  52. package/assets/Balatro Seed Curator (DesignsV2)/src/mockData.js +0 -185
  53. package/assets/Balatro Seed Curator (DesignsV2)/src/sprites.jsx +0 -259
  54. package/assets/Balatro Seed Curator (DesignsV2)/src/tokens.js +0 -49
  55. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/AntePageV2.jsx +0 -290
  56. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/BalButton.jsx +0 -107
  57. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlBuilderV2.jsx +0 -594
  58. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlIde.jsx +0 -302
  59. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SearchResultsV2.jsx +0 -286
  60. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedDetailV2.jsx +0 -336
  61. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedOGCard.jsx +0 -251
  62. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/Showcase.jsx +0 -131
  63. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/app.jsx +0 -55
  64. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/data.js +0 -296
  65. package/assets/Balatro Seed Curator (DesignsV2)/starters/design-canvas.jsx +0 -622
  66. package/assets/Balatro Seed Curator (DesignsV2)/uploads/8BitDeck.png +0 -0
  67. package/assets/Balatro Seed Curator (DesignsV2)/uploads/BlindChips.png +0 -0
  68. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Boosters.png +0 -0
  69. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Editions.png +0 -0
  70. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Enhancers.png +0 -0
  71. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Jokers.png +0 -0
  72. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Tarots.png +0 -0
  73. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749540653-0.png +0 -0
  74. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749644934-0.png +0 -0
  75. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749661871-0.png +0 -0
  76. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749674748-0.png +0 -0
  77. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749703076-0.png +0 -0
  78. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749882759-0.png +0 -0
  79. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750354200-0.png +0 -0
  80. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750733265-0.png +0 -0
  81. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776751928925-0.png +0 -0
  82. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776800975060-0.png +0 -0
  83. package/assets/Balatro Seed Curator (DesignsV2)/uploads/stickers.png +0 -0
  84. 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;