jaml-ui 0.14.2 → 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 (94) hide show
  1. package/DESIGN.md +197 -0
  2. package/dist/components/AnalyzerExplorer.js +0 -11
  3. package/dist/components/JamlAestheticSelector.js +1 -3
  4. package/dist/components/JamlAnalyzerFullscreen.js +3 -3
  5. package/dist/components/JamlIde.js +0 -2
  6. package/dist/components/JamlSeedInput.js +1 -2
  7. package/dist/components/JamlSpeedometer.js +1 -1
  8. package/dist/ui/codeBlock.js +1 -1
  9. package/dist/ui/jimboCopyRow.js +1 -2
  10. package/dist/ui/jimboFilterBar.js +2 -4
  11. package/dist/ui/showcase.js +4 -4
  12. package/package.json +8 -5
  13. package/assets/Balatro Seed Curator (DesignsV2)/.design-canvas.state.json +0 -1
  14. package/assets/Balatro Seed Curator (DesignsV2)/Assets/BlindChips.png +0 -0
  15. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/Boosters.json +0 -303
  16. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters/boosters.png +0 -0
  17. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Boosters.png +0 -0
  18. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/BlindChips.png +0 -0
  19. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Bosses/blinds_metadata.json +0 -51
  20. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/8BitDeck.png +0 -0
  21. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/Enhancers.png +0 -0
  22. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/balatro-stake-chips.png +0 -0
  23. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/enhancers_metadata.json +0 -52
  24. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/playing_cards_metadata.json +0 -249
  25. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Decks/stakes.json +0 -19
  26. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Editions.png +0 -0
  27. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Enhancers.png +0 -0
  28. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Editions.png +0 -0
  29. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/Jokers.png +0 -0
  30. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/jokers.json +0 -1087
  31. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers.png +0 -0
  32. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers/stickers_metadata.json +0 -25
  33. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Jokers.png +0 -0
  34. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.json +0 -191
  35. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tags/tags.png +0 -0
  36. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/Tarots.png +0 -0
  37. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/planets.json +0 -15
  38. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/spectrals.json +0 -21
  39. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots/tarots.json +0 -163
  40. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Tarots.png +0 -0
  41. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/Vouchers.png +0 -0
  42. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers/vouchers.json +0 -130
  43. package/assets/Balatro Seed Curator (DesignsV2)/Assets/Vouchers.png +0 -0
  44. package/assets/Balatro Seed Curator (DesignsV2)/Assets/blinds.json +0 -51
  45. package/assets/Balatro Seed Curator (DesignsV2)/Assets/boosters.json +0 -303
  46. package/assets/Balatro Seed Curator (DesignsV2)/Assets/fonts/m6x11plusplus.otf +0 -0
  47. package/assets/Balatro Seed Curator (DesignsV2)/Assets/jokers.json +0 -1087
  48. package/assets/Balatro Seed Curator (DesignsV2)/Assets/planets.json +0 -15
  49. package/assets/Balatro Seed Curator (DesignsV2)/Assets/spectrals.json +0 -21
  50. package/assets/Balatro Seed Curator (DesignsV2)/Assets/stakes.png +0 -0
  51. package/assets/Balatro Seed Curator (DesignsV2)/Assets/stickers.png +0 -0
  52. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.json +0 -191
  53. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tags.png +0 -0
  54. package/assets/Balatro Seed Curator (DesignsV2)/Assets/tarots.json +0 -163
  55. package/assets/Balatro Seed Curator (DesignsV2)/Assets/vouchers.json +0 -130
  56. package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail v2.html +0 -40
  57. package/assets/Balatro Seed Curator (DesignsV2)/Seed Detail.html +0 -34
  58. package/assets/Balatro Seed Curator (DesignsV2)/public/fonts/m6x11plusplus.otf +0 -0
  59. package/assets/Balatro Seed Curator (DesignsV2)/src/AntePage.jsx +0 -228
  60. package/assets/Balatro Seed Curator (DesignsV2)/src/SeedDetail.jsx +0 -222
  61. package/assets/Balatro Seed Curator (DesignsV2)/src/app.jsx +0 -35
  62. package/assets/Balatro Seed Curator (DesignsV2)/src/mockData.js +0 -185
  63. package/assets/Balatro Seed Curator (DesignsV2)/src/sprites.jsx +0 -259
  64. package/assets/Balatro Seed Curator (DesignsV2)/src/tokens.js +0 -49
  65. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/AntePageV2.jsx +0 -290
  66. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/BalButton.jsx +0 -107
  67. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlBuilderV2.jsx +0 -594
  68. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/JamlIde.jsx +0 -302
  69. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SearchResultsV2.jsx +0 -286
  70. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedDetailV2.jsx +0 -336
  71. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/SeedOGCard.jsx +0 -251
  72. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/Showcase.jsx +0 -131
  73. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/app.jsx +0 -55
  74. package/assets/Balatro Seed Curator (DesignsV2)/src/v2/data.js +0 -296
  75. package/assets/Balatro Seed Curator (DesignsV2)/starters/design-canvas.jsx +0 -622
  76. package/assets/Balatro Seed Curator (DesignsV2)/uploads/8BitDeck.png +0 -0
  77. package/assets/Balatro Seed Curator (DesignsV2)/uploads/BlindChips.png +0 -0
  78. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Boosters.png +0 -0
  79. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Editions.png +0 -0
  80. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Enhancers.png +0 -0
  81. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Jokers.png +0 -0
  82. package/assets/Balatro Seed Curator (DesignsV2)/uploads/Tarots.png +0 -0
  83. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749540653-0.png +0 -0
  84. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749644934-0.png +0 -0
  85. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749661871-0.png +0 -0
  86. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749674748-0.png +0 -0
  87. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749703076-0.png +0 -0
  88. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776749882759-0.png +0 -0
  89. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750354200-0.png +0 -0
  90. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776750733265-0.png +0 -0
  91. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776751928925-0.png +0 -0
  92. package/assets/Balatro Seed Curator (DesignsV2)/uploads/pasted-1776800975060-0.png +0 -0
  93. package/assets/Balatro Seed Curator (DesignsV2)/uploads/stickers.png +0 -0
  94. package/assets/Balatro Seed Curator (DesignsV2)/uploads/tags.png +0 -0
@@ -1,228 +0,0 @@
1
- // AntePage — one ante's worth of content, scrolled vertically.
2
- // [voucher row + tags row]
3
- // [SMALL BLIND section: boss chip + pack(s) + 6 shop items in 2×3]
4
- // [BIG BLIND section: ditto]
5
- // [BOSS BLIND section: ditto]
6
- //
7
- // Pack tap → fan-out of contents below the pack.
8
- // Tag tap → reveal of reward preview (same motion as pack tap).
9
- // Hit items display a corner stamp; a footer strip shows the per-clause
10
- // running tally across the seed.
11
-
12
- const { useState: uS, useMemo: uM } = React;
13
-
14
- // ── Small chrome: a pixel-border card (Balatro panels) ───────
15
- function BalatroPanel({ children, style = {}, color = '#2a3336' }) {
16
- return (
17
- <div
18
- style={{
19
- background: color,
20
- border: '2px solid #1e2b2d',
21
- borderRadius: 6,
22
- boxShadow: 'inset 0 0 0 1px #3e4a4d, 0 2px 0 #0b1416',
23
- padding: 10,
24
- color: '#fff',
25
- ...style,
26
- }}
27
- >
28
- {children}
29
- </div>
30
- );
31
- }
32
-
33
- // ── Chunky Balatro section header pill ───────────────────────
34
- function BlindHeader({ kind, boss, onTap }) {
35
- const label = kind === 'small' ? 'SMALL BLIND' : kind === 'big' ? 'BIG BLIND' : 'BOSS BLIND';
36
- const color = kind === 'boss' ? '#6d2b2b' : '#2b3a5e';
37
- return (
38
- <div
39
- onClick={onTap}
40
- style={{
41
- display: 'flex',
42
- alignItems: 'center',
43
- gap: 10,
44
- background: color,
45
- padding: '6px 12px 6px 8px',
46
- border: '2px solid #1e2b2d',
47
- borderRadius: 18,
48
- boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.15), 0 2px 0 #0b1416',
49
- cursor: onTap ? 'pointer' : 'default',
50
- alignSelf: 'flex-start',
51
- }}
52
- >
53
- <BossChip name={boss || (kind === 'boss' ? 'TheOx' : kind === 'big' ? 'BigBlind' : 'SmallBlind')} size={32} />
54
- <span style={{ fontFamily: 'm6x11plus, monospace', fontSize: 16, letterSpacing: 1, color: '#fff' }}>{label}</span>
55
- </div>
56
- );
57
- }
58
-
59
- // ── FanOut — used by tap-to-reveal (packs and tags). Renders row of
60
- // children sliding + fading in from under the parent.
61
- function FanOut({ open, children }) {
62
- return (
63
- <div
64
- style={{
65
- overflow: 'hidden',
66
- transition: 'max-height 260ms cubic-bezier(.33,1,.4,1), opacity 180ms',
67
- maxHeight: open ? 180 : 0,
68
- opacity: open ? 1 : 0,
69
- }}
70
- >
71
- <div
72
- style={{
73
- display: 'flex',
74
- gap: 8,
75
- padding: '10px 0 4px',
76
- transform: open ? 'translateY(0)' : 'translateY(-8px)',
77
- transition: 'transform 260ms cubic-bezier(.33,1,.4,1)',
78
- }}
79
- >
80
- {children}
81
- </div>
82
- </div>
83
- );
84
- }
85
-
86
- // ── PackCluster — the pack sprite, plus an expanded fan-out of its cards
87
- function PackCluster({ pack }) {
88
- const [open, setOpen] = uS(false);
89
- const kind = pack.kind.toLowerCase();
90
- const hitKinds = pack.contents?.flatMap((c) => c.hits || []) || [];
91
- const firstHit = hitKinds.length ? 'should' : null;
92
- return (
93
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
94
- <PackSprite kind={kind} size={56} onClick={() => setOpen((o) => !o)} open={open} hit={firstHit} />
95
- {pack.contents?.length ? (
96
- <FanOut open={open}>
97
- {pack.contents.map((c, i) => (
98
- <CardItem key={i} item={c} size={46} />
99
- ))}
100
- </FanOut>
101
- ) : null}
102
- </div>
103
- );
104
- }
105
-
106
- // ── CardItem — renders a shop/pack item by kind.
107
- function CardItem({ item, size = 58 }) {
108
- const hit = item.hits && item.hits.length ? (item.hits.some((h) => h.startsWith('m')) ? 'must' : 'should') : null;
109
- if (item.kind === 'Joker') return <JokerMini name={item.name} size={size} hit={hit} edition={item.edition} />;
110
- if (item.kind === 'Tarot') return <TarotMini name={item.name} size={size} hit={hit} />;
111
- if (item.kind === 'Voucher') return <VoucherMini name={item.name} size={size} hit={hit} />;
112
- return null;
113
- }
114
-
115
- // ── TagReveal — tap a tag, see its preview (mock)
116
- function TagCluster({ tag }) {
117
- const [open, setOpen] = uS(false);
118
- const preview = TAG_PREVIEWS[tag.name.toLowerCase()] || { label: tag.name, body: 'Tag reward preview' };
119
- const hit = tag.hits && tag.hits.length ? 'should' : null;
120
- return (
121
- <div style={{ position: 'relative' }}>
122
- <div onClick={() => setOpen((o) => !o)} style={{ cursor: 'pointer' }}>
123
- <TagChip name={tag.name} size={30} hit={hit} />
124
- </div>
125
- {open && (
126
- <div
127
- style={{
128
- position: 'absolute',
129
- top: 'calc(100% + 6px)',
130
- left: '50%',
131
- transform: 'translateX(-50%)',
132
- background: '#1e2b2d',
133
- border: '2px solid #3e4a4d',
134
- borderRadius: 6,
135
- padding: '6px 10px',
136
- fontFamily: 'm6x11plus, monospace',
137
- fontSize: 12,
138
- color: '#fff',
139
- whiteSpace: 'nowrap',
140
- zIndex: 10,
141
- boxShadow: '0 6px 20px rgba(0,0,0,0.4)',
142
- }}
143
- onClick={() => setOpen(false)}
144
- >
145
- <div style={{ color: '#e4b643' }}>{preview.label}</div>
146
- <div style={{ opacity: 0.75, fontSize: 10, marginTop: 2 }}>{preview.body}</div>
147
- </div>
148
- )}
149
- </div>
150
- );
151
- }
152
-
153
- const TAG_PREVIEWS = {
154
- raretag: { label: 'RARE TAG', body: 'Next shop: Free Rare Joker' },
155
- uncommontag: { label: 'UNCOMMON TAG', body: 'Next shop: Free Uncommon Joker' },
156
- negativetag: { label: 'NEGATIVE TAG', body: 'Next base Joker becomes Negative' },
157
- foiltag: { label: 'FOIL TAG', body: 'Next base Joker becomes Foil' },
158
- holographictag: { label: 'HOLO TAG', body: 'Next base Joker becomes Holographic' },
159
- polychrometag: { label: 'POLY TAG', body: 'Next base Joker becomes Polychrome' },
160
- topuptag: { label: 'TOP-UP TAG', body: 'Create up to 2 Common Jokers' },
161
- standardtag: { label: 'STANDARD TAG', body: 'Free Standard Booster Pack' },
162
- charmtag: { label: 'CHARM TAG', body: 'Free Mega Arcana Pack' },
163
- meteortag: { label: 'METEOR TAG', body: 'Free Mega Celestial Pack' },
164
- };
165
-
166
- // ── BlindSection — boss header + packs + shop 6 items in 2 rows of 3
167
- function BlindSection({ blind }) {
168
- return (
169
- <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 18 }}>
170
- <BlindHeader kind={blind.kind} boss={blind.boss} />
171
- <div style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
172
- <div style={{ flex: 1 }}>
173
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
174
- {blind.shop.map((item, i) => (
175
- <div key={i} style={{ display: 'flex', justifyContent: 'center' }}>
176
- <CardItem item={item} size={58} />
177
- </div>
178
- ))}
179
- </div>
180
- </div>
181
- {blind.packs && blind.packs.length > 0 && (
182
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
183
- {blind.packs.map((p, i) => <PackCluster key={i} pack={p} />)}
184
- </div>
185
- )}
186
- </div>
187
- </div>
188
- );
189
- }
190
-
191
- // ── AntePage — voucher + tags row, then all three blinds
192
- function AntePage({ ante }) {
193
- if (ante.isPre) {
194
- return (
195
- <div style={{ padding: 20, color: '#b0c0c4', fontFamily: 'm6x11plus, monospace', fontSize: 14, textAlign: 'center' }}>
196
- <div style={{ fontSize: 18, color: '#fff', marginBottom: 8 }}>RUN START</div>
197
- <div style={{ opacity: 0.7 }}>Swipe up to Ante 1 →</div>
198
- </div>
199
- );
200
- }
201
- return (
202
- <div style={{ padding: '16px 14px 40px' }}>
203
- {/* Ante header */}
204
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
205
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 22, color: '#fff', letterSpacing: 1 }}>ANTE {ante.ante}</div>
206
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
207
- {ante.tags.map((t, i) => <TagCluster key={i} tag={t} />)}
208
- </div>
209
- </div>
210
-
211
- {/* Voucher strip */}
212
- {ante.voucher ? (
213
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14, padding: '8px 10px', background: '#1a2529', border: '2px solid #0b1416', borderRadius: 6 }}>
214
- <VoucherMini name={ante.voucher.name} size={50} hit={ante.voucher.hits?.length ? 'should' : null} />
215
- <div>
216
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 11, color: '#7e8e91', letterSpacing: 1 }}>VOUCHER</div>
217
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 15, color: '#fff' }}>{ante.voucher.name}</div>
218
- </div>
219
- </div>
220
- ) : null}
221
-
222
- {ante.blinds.map((b) => <BlindSection key={b.kind} blind={b} />)}
223
- </div>
224
- );
225
- }
226
-
227
- window.AntePage = AntePage;
228
- window.BalatroPanel = BalatroPanel;
@@ -1,222 +0,0 @@
1
- // SeedDetail — the full phone-sized screen.
2
- // ┌──────────────────────────┐
3
- // │ TOP BAR │ fixed
4
- // │ seed · deck · score row │
5
- // │ score column per clause │
6
- // ├──────────────────────────┤
7
- // │ ⟨ horizontal pager ⟩ │
8
- // │ ┃ vertical ante snap │ swipes L/R between seeds,
9
- // │ ┃ ┃ page 0 (pre) │ swipes up/down between antes
10
- // │ ┃ ┃ page 1..N │
11
- // │ ┃ │
12
- // └──────────────────────────┘
13
-
14
- const { useState: sdUS, useRef: sdUR, useEffect: sdUE } = React;
15
-
16
- // ── Score column: one per should-clause + must-clause.
17
- function ScoreCol({ clause, hits, kind }) {
18
- const hit = hits > 0;
19
- const must = kind === 'must';
20
- return (
21
- <div
22
- style={{
23
- flex: 1,
24
- background: hit ? '#1e2e2e' : '#161c1e',
25
- border: `2px solid ${hit ? clause.color || '#3e4a4d' : '#0b1416'}`,
26
- borderRadius: 4,
27
- padding: '4px 6px',
28
- display: 'flex',
29
- flexDirection: 'column',
30
- alignItems: 'center',
31
- gap: 2,
32
- minWidth: 0,
33
- }}
34
- >
35
- <div style={{ display: 'flex', alignItems: 'center', gap: 4, height: 22 }}>
36
- {clause.kind === 'Joker' && <Sprite sheet="jokers" name={clause.target} width={18} height={24} />}
37
- {clause.kind === 'Voucher' && <Sprite sheet="vouchers" name={clause.target} width={18} height={24} />}
38
- {clause.kind === 'Tag' && <Sprite sheet="tags" name={clause.target} width={20} height={20} />}
39
- </div>
40
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: hit ? '#fff' : '#556265', letterSpacing: 0.5, lineHeight: 1, textTransform: 'uppercase', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '100%' }}>
41
- {clause.target}
42
- </div>
43
- <div
44
- style={{
45
- fontFamily: 'm6x11plus, monospace',
46
- fontSize: 16,
47
- color: must ? '#e4b643' : hit ? '#35bd86' : '#3e4a4d',
48
- lineHeight: 1,
49
- }}
50
- >
51
- ×{hits}
52
- </div>
53
- </div>
54
- );
55
- }
56
-
57
- function TopBar({ seed, filter }) {
58
- return (
59
- <div style={{ background: '#0f1719', borderBottom: '2px solid #000', padding: '10px 12px 10px', position: 'sticky', top: 0, zIndex: 20 }}>
60
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
61
- <button style={chromeBtn}>
62
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M9 2L4 7l5 5"/></svg>
63
- </button>
64
- <div style={{ textAlign: 'center' }}>
65
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 18, color: '#fff', letterSpacing: 2 }}>{seed.seed}</div>
66
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 10, color: '#7e8e91', letterSpacing: 1 }}>{seed.deck} · {seed.stake}</div>
67
- </div>
68
- <button style={chromeBtn}>
69
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="7" cy="7" r="1.2"/><circle cx="2" cy="7" r="1.2"/><circle cx="12" cy="7" r="1.2"/></svg>
70
- </button>
71
- </div>
72
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: '#556265', letterSpacing: 1, marginBottom: 4 }}>
73
- FILTER · {filter.name.toUpperCase()}
74
- </div>
75
- <div style={{ display: 'flex', gap: 4 }}>
76
- {filter.shouldClauses.map((c) => (
77
- <ScoreCol key={c.id} clause={c} hits={seed.score.perClause[c.id] || 0} kind="should" />
78
- ))}
79
- {filter.mustClauses.map((c) => (
80
- <ScoreCol key={c.id} clause={{ ...c, color: '#e4b643' }} hits={seed.score.perClause[c.id] || 0} kind="must" />
81
- ))}
82
- </div>
83
- </div>
84
- );
85
- }
86
-
87
- const chromeBtn = {
88
- width: 30, height: 30, border: '2px solid #0b1416', borderRadius: 4, background: '#2a3336',
89
- color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
90
- boxShadow: 'inset 0 1px 0 rgba(255,255,255,.1), 0 2px 0 #0b1416',
91
- };
92
-
93
- // ── Vertical snap scroller of AntePage; shows ante index indicator
94
- function SeedPager({ seed, filter }) {
95
- const scrollRef = sdUR(null);
96
- const [anteIdx, setAnteIdx] = sdUS(1);
97
- const onScroll = (e) => {
98
- const el = e.currentTarget;
99
- const i = Math.round(el.scrollTop / el.clientHeight);
100
- if (i !== anteIdx) setAnteIdx(i);
101
- };
102
-
103
- return (
104
- <div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex', overflow: 'hidden' }}>
105
- <div
106
- ref={scrollRef}
107
- onScroll={onScroll}
108
- style={{
109
- flex: 1,
110
- overflowY: 'auto',
111
- overflowX: 'hidden',
112
- scrollSnapType: 'y mandatory',
113
- scrollbarWidth: 'none',
114
- }}
115
- >
116
- {seed.antes.map((ante, i) => (
117
- <div key={i} style={{ minHeight: '100%', scrollSnapAlign: 'start', scrollSnapStop: 'always' }}>
118
- <AntePage ante={ante} />
119
- </div>
120
- ))}
121
- </div>
122
-
123
- {/* Right-edge ante rail */}
124
- <div style={{ position: 'absolute', right: 4, top: 8, bottom: 8, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4, pointerEvents: 'none' }}>
125
- {seed.antes.map((a, i) => (
126
- <div
127
- key={i}
128
- style={{
129
- width: 3,
130
- height: i === anteIdx ? 18 : 8,
131
- borderRadius: 2,
132
- background: i === anteIdx ? '#e4b643' : '#3e4a4d',
133
- transition: 'all 200ms',
134
- }}
135
- />
136
- ))}
137
- </div>
138
- </div>
139
- );
140
- }
141
-
142
- // ── Root: horizontal seed swiper wrapping the vertical pager
143
- function SeedDetail({ seeds, filter }) {
144
- const [idx, setIdx] = sdUS(0);
145
- const [drag, setDrag] = sdUS({ dx: 0, active: false });
146
- const touch = sdUR({ x0: 0, y0: 0, locked: null });
147
-
148
- const onDown = (e) => {
149
- const t = e.touches ? e.touches[0] : e;
150
- // only start tracking from top bar region (safe)
151
- touch.current = { x0: t.clientX, y0: t.clientY, locked: null };
152
- };
153
- const onMove = (e) => {
154
- if (!touch.current.x0) return;
155
- const t = e.touches ? e.touches[0] : e;
156
- const dx = t.clientX - touch.current.x0;
157
- const dy = t.clientY - touch.current.y0;
158
- if (touch.current.locked == null) {
159
- if (Math.abs(dx) > 8 && Math.abs(dx) > Math.abs(dy) * 1.5) touch.current.locked = 'x';
160
- else if (Math.abs(dy) > 8) touch.current.locked = 'y';
161
- }
162
- if (touch.current.locked === 'x') {
163
- e.preventDefault?.();
164
- setDrag({ dx, active: true });
165
- }
166
- };
167
- const onUp = () => {
168
- if (touch.current.locked === 'x') {
169
- const dx = drag.dx;
170
- if (dx < -70 && idx < seeds.length - 1) setIdx(idx + 1);
171
- else if (dx > 70 && idx > 0) setIdx(idx - 1);
172
- }
173
- touch.current = { x0: 0, y0: 0, locked: null };
174
- setDrag({ dx: 0, active: false });
175
- };
176
-
177
- const seed = seeds[idx];
178
-
179
- return (
180
- <div
181
- style={{
182
- width: '100%',
183
- height: '100%',
184
- background: '#0b1416',
185
- display: 'flex',
186
- flexDirection: 'column',
187
- position: 'relative',
188
- overflow: 'hidden',
189
- fontFamily: 'm6x11plus, monospace',
190
- color: '#fff',
191
- }}
192
- >
193
- {/* Seed-swipe layer: only the top bar is draggable for horizontal
194
- swipes (keeps vertical scroll natural inside the ante pager). */}
195
- <div onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp}
196
- style={{ touchAction: 'pan-y' }}>
197
- <TopBar seed={seed} filter={filter} />
198
- </div>
199
-
200
- <div
201
- style={{
202
- flex: 1,
203
- minHeight: 0,
204
- position: 'relative',
205
- transform: drag.active ? `translateX(${drag.dx * 0.3}px) rotate(${drag.dx * 0.02}deg)` : 'none',
206
- transition: drag.active ? 'none' : 'transform 260ms cubic-bezier(.33,1,.4,1)',
207
- }}
208
- >
209
- <SeedPager key={seed.seed} seed={seed} filter={filter} />
210
- </div>
211
-
212
- {/* Seed dots */}
213
- <div style={{ position: 'absolute', bottom: 6, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 5 }}>
214
- {seeds.map((_, i) => (
215
- <div key={i} style={{ width: 6, height: 6, borderRadius: 3, background: i === idx ? '#e4b643' : '#3e4a4d' }} />
216
- ))}
217
- </div>
218
- </div>
219
- );
220
- }
221
-
222
- window.SeedDetail = SeedDetail;
@@ -1,35 +0,0 @@
1
- // App shell — wraps SeedDetail in a design_canvas artboard at phone size.
2
-
3
- function App() {
4
- const [mapsReady, setMapsReady] = React.useState(false);
5
-
6
- React.useEffect(() => {
7
- (async () => {
8
- await Promise.all([
9
- window.loadSpriteMap('jokers', 'assets/jokers.json'),
10
- window.loadSpriteMap('tarots', 'assets/tarots.json'),
11
- window.loadSpriteMap('vouchers', 'assets/vouchers.json'),
12
- window.loadSpriteMap('tags', 'assets/tags.json'),
13
- window.loadSpriteMap('boosters', 'assets/boosters.json'),
14
- window.loadSpriteMap('blinds', 'assets/blinds.json'),
15
- ]);
16
- setMapsReady(true);
17
- })();
18
- }, []);
19
-
20
- if (!mapsReady) {
21
- return <div style={{ padding: 40, color: '#fff', fontFamily: 'm6x11plus, monospace' }}>Loading sprites…</div>;
22
- }
23
-
24
- return (
25
- <DesignCanvas>
26
- <DCSection id="seed-detail" title="Seed Detail" subtitle="Mobile seed view — swipe ↕ between antes, ↔ between seeds">
27
- <DCArtboard id="hifi" label="Hi-fi · Balatro native" width={390} height={844}>
28
- <SeedDetail seeds={window.MOCK_SEEDS} filter={window.MOCK_FILTER} />
29
- </DCArtboard>
30
- </DCSection>
31
- </DesignCanvas>
32
- );
33
- }
34
-
35
- ReactDOM.createRoot(document.getElementById('root')).render(<App />);
@@ -1,185 +0,0 @@
1
- // Mock data: a "Blueprint + Brainstorm + Telescope" filter + one seed.
2
- // Schema matches JAML should/must clauses. Each hit on the seed carries a
3
- // clauseId so the card can attribute per-clause score.
4
-
5
- window.MOCK_FILTER = {
6
- name: 'Blueprint Parade',
7
- mustClauses: [
8
- { id: 'm1', kind: 'Joker', target: 'Blueprint', antes: [1, 2, 3, 4, 5, 6, 7, 8] },
9
- ],
10
- shouldClauses: [
11
- { id: 's1', kind: 'Joker', target: 'Blueprint', antes: [1, 2, 3, 4, 5, 6, 7, 8], color: '#ff4c40' },
12
- { id: 's2', kind: 'Joker', target: 'Brainstorm', antes: [1, 2, 3, 4, 5, 6, 7, 8], color: '#0093ff' },
13
- { id: 's3', kind: 'Voucher', target: 'Telescope', antes: [1, 2, 3, 4, 5, 6, 7, 8], color: '#e4b643' },
14
- ],
15
- };
16
-
17
- // ── Helper for seed generation ────────────────────────────────
18
- // Each ante has: voucher (or null), tags [2], smallBlindPack,
19
- // bigBlindPack, boss, and 4 shops (small, big, boss — each has 6 items
20
- // in 2 rows of 3, but Balatro shops cycle, so we just show a few reroll
21
- // batches). For simplicity: one shop per blind, 4 items per shop slot row.
22
-
23
- // Each item: { kind:'Joker'|'Tarot'|'Voucher'|'Pack', name, edition?, hits?:[clauseId] }
24
- // hits = array of clauseIds this item satisfies. Renderer stamps a badge
25
- // per hit and builds the per-clause score by counting.
26
-
27
- const seed1 = {
28
- seed: 'GR0XQVCF',
29
- deck: 'Red Deck',
30
- stake: 'Blue Stake',
31
- score: { total: 7, perClause: { s1: 3, s2: 3, s3: 1, m1: 3 } },
32
- antes: [
33
- // Ante 0 is pre-game (just deck/stake summary)
34
- { ante: 0, voucher: null, tags: [], blinds: [], isPre: true },
35
-
36
- // Ante 1
37
- {
38
- ante: 1,
39
- voucher: { name: 'Overstock', hits: [] },
40
- tags: [{ name: 'RareTag', hits: [] }, { name: 'UncommonTag', hits: [] }],
41
- blinds: [
42
- { kind: 'small', boss: 'SmallBlind',
43
- shop: [
44
- { kind: 'Joker', name: 'Joker', hits: [] },
45
- { kind: 'Joker', name: 'GreedyJoker', hits: [] },
46
- { kind: 'Joker', name: 'Blueprint', edition: 'Foil', hits: ['m1', 's1'] },
47
- { kind: 'Tarot', name: 'TheFool', hits: [] },
48
- { kind: 'Tarot', name: 'TheEmpress', hits: [] },
49
- { kind: 'Joker', name: 'JollyJoker', hits: [] },
50
- ],
51
- packs: [{ kind: 'ArcanaPack', contents: [
52
- { kind: 'Tarot', name: 'TheMagician', hits: [] },
53
- { kind: 'Tarot', name: 'TheHighPriestess', hits: [] },
54
- { kind: 'Tarot', name: 'TheEmperor', hits: [] },
55
- ] }],
56
- },
57
- { kind: 'big', boss: 'BigBlind',
58
- shop: [
59
- { kind: 'Joker', name: 'WrathfulJoker', hits: [] },
60
- { kind: 'Joker', name: 'ZanyJoker', hits: [] },
61
- { kind: 'Tarot', name: 'TheLovers', hits: [] },
62
- { kind: 'Joker', name: 'GrosMichel', hits: [] },
63
- { kind: 'Joker', name: 'EvenSteven', hits: [] },
64
- { kind: 'Joker', name: 'OddTodd', hits: [] },
65
- ],
66
- packs: [{ kind: 'BuffoonPack', contents: [
67
- { kind: 'Joker', name: 'Brainstorm', hits: ['s2'] },
68
- { kind: 'Joker', name: 'Fibonacci', hits: [] },
69
- ] }],
70
- },
71
- { kind: 'boss', boss: 'TheOx',
72
- shop: [
73
- { kind: 'Voucher', name: 'Telescope', hits: ['s3'] },
74
- { kind: 'Joker', name: 'Scholar', hits: [] },
75
- { kind: 'Joker', name: 'Supernova', hits: [] },
76
- { kind: 'Tarot', name: 'TheHierophant', hits: [] },
77
- { kind: 'Joker', name: 'Ceremonial', hits: [] },
78
- { kind: 'Joker', name: 'Banner', hits: [] },
79
- ],
80
- packs: [{ kind: 'StandardPack', contents: [] }],
81
- },
82
- ],
83
- },
84
-
85
- // Ante 2
86
- {
87
- ante: 2,
88
- voucher: { name: 'ClearanceSale', hits: [] },
89
- tags: [{ name: 'TopUpTag', hits: [] }, { name: 'FoilTag', hits: [] }],
90
- blinds: [
91
- { kind: 'small', boss: 'SmallBlind',
92
- shop: [
93
- { kind: 'Joker', name: 'Joker', hits: [] },
94
- { kind: 'Joker', name: 'MadJoker', hits: [] },
95
- { kind: 'Joker', name: 'CrazyJoker', hits: [] },
96
- { kind: 'Tarot', name: 'Strength', hits: [] },
97
- { kind: 'Joker', name: 'DrollJoker', hits: [] },
98
- { kind: 'Joker', name: 'HalfJoker', hits: [] },
99
- ],
100
- packs: [{ kind: 'CelestialPack', contents: [] }],
101
- },
102
- { kind: 'big', boss: 'BigBlind',
103
- shop: [
104
- { kind: 'Joker', name: 'Blueprint', edition: 'Holographic', hits: ['m1', 's1'] },
105
- { kind: 'Joker', name: 'SlyJoker', hits: [] },
106
- { kind: 'Tarot', name: 'TheHermit', hits: [] },
107
- { kind: 'Joker', name: 'WilyJoker', hits: [] },
108
- { kind: 'Joker', name: 'CleverJoker', hits: [] },
109
- { kind: 'Joker', name: 'DeviousJoker', hits: [] },
110
- ],
111
- packs: [{ kind: 'MegaArcanaPack', contents: [
112
- { kind: 'Tarot', name: 'Justice', hits: [] },
113
- { kind: 'Tarot', name: 'Death', hits: [] },
114
- { kind: 'Tarot', name: 'Temperance', hits: [] },
115
- { kind: 'Tarot', name: 'TheDevil', hits: [] },
116
- { kind: 'Tarot', name: 'TheTower', hits: [] },
117
- ] }],
118
- },
119
- { kind: 'boss', boss: 'TheHouse',
120
- shop: [
121
- { kind: 'Joker', name: 'Brainstorm', edition: 'Polychrome', hits: ['s2'] },
122
- { kind: 'Joker', name: 'Banner', hits: [] },
123
- { kind: 'Joker', name: 'MysticSummit', hits: [] },
124
- { kind: 'Joker', name: 'MarbleJoker', hits: [] },
125
- { kind: 'Joker', name: 'LoyaltyCard', hits: [] },
126
- { kind: 'Joker', name: 'EightBall', hits: [] },
127
- ],
128
- packs: [{ kind: 'SpectralPack', contents: [] }],
129
- },
130
- ],
131
- },
132
-
133
- // Ante 3
134
- {
135
- ante: 3,
136
- voucher: null,
137
- tags: [{ name: 'HolographicTag', hits: [] }, { name: 'NegativeTag', hits: [] }],
138
- blinds: [
139
- { kind: 'small', boss: 'SmallBlind',
140
- shop: [
141
- { kind: 'Joker', name: 'Brainstorm', edition: 'Foil', hits: ['s2'] },
142
- { kind: 'Joker', name: 'Misprint', hits: [] },
143
- { kind: 'Tarot', name: 'TheStar', hits: [] },
144
- { kind: 'Joker', name: 'RaisedFist', hits: [] },
145
- { kind: 'Joker', name: 'Chaos', hits: [] },
146
- { kind: 'Joker', name: 'Fibonacci', hits: [] },
147
- ],
148
- packs: [{ kind: 'ArcanaPack', contents: [] }],
149
- },
150
- { kind: 'big', boss: 'BigBlind',
151
- shop: [
152
- { kind: 'Joker', name: 'SteelJoker', hits: [] },
153
- { kind: 'Joker', name: 'ScaryFace', hits: [] },
154
- { kind: 'Joker', name: 'AbstractJoker', hits: [] },
155
- { kind: 'Joker', name: 'DelayedGratification', hits: [] },
156
- { kind: 'Joker', name: 'Pareidolia', hits: [] },
157
- { kind: 'Joker', name: 'Hack', hits: [] },
158
- ],
159
- packs: [{ kind: 'JumboBuffoonPack', contents: [
160
- { kind: 'Joker', name: 'Blueprint', hits: ['m1', 's1'] },
161
- { kind: 'Joker', name: 'Cartomancer', hits: [] },
162
- { kind: 'Joker', name: 'Astronomer', hits: [] },
163
- ] }],
164
- },
165
- { kind: 'boss', boss: 'TheClub',
166
- shop: [
167
- { kind: 'Joker', name: 'Gros Michel', hits: [] },
168
- { kind: 'Joker', name: 'EvenSteven', hits: [] },
169
- { kind: 'Joker', name: 'OddTodd', hits: [] },
170
- { kind: 'Joker', name: 'Scholar', hits: [] },
171
- { kind: 'Joker', name: 'BusinessCard', hits: [] },
172
- { kind: 'Joker', name: 'Supernova', hits: [] },
173
- ],
174
- packs: [{ kind: 'StandardPack', contents: [] }],
175
- },
176
- ],
177
- },
178
- ],
179
- };
180
-
181
- // Two more placeholder seeds so the horizontal swipe has somewhere to go.
182
- const seed2 = { ...seed1, seed: 'ALEPH999', score: { total: 4, perClause: { s1: 2, s2: 1, s3: 1, m1: 2 } } };
183
- const seed3 = { ...seed1, seed: 'BETAZERO', score: { total: 6, perClause: { s1: 3, s2: 2, s3: 1, m1: 3 } } };
184
-
185
- window.MOCK_SEEDS = [seed1, seed2, seed3];