jaml-ui 0.14.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +197 -0
- package/dist/assets.d.ts +1 -0
- package/dist/assets.js +2 -0
- package/dist/decode/motelyItemDecoder.js +27 -3
- package/dist/decode/motelySprite.d.ts +15 -0
- package/dist/decode/motelySprite.js +27 -0
- package/dist/motely.d.ts +1 -0
- package/dist/motely.js +1 -0
- package/dist/r3f/Card3D.js +2 -0
- package/dist/r3f/JimboBillboard.d.ts +10 -0
- package/dist/r3f/JimboBillboard.js +29 -0
- package/dist/r3f/JimboText3D.d.ts +9 -0
- package/dist/r3f/JimboText3D.js +7 -0
- package/dist/r3f.d.ts +2 -0
- package/dist/r3f.js +2 -0
- package/dist/sprites/spriteData.d.ts +1 -0
- package/dist/sprites/spriteData.js +1 -0
- package/dist/sprites/spriteMapper.d.ts +7 -1
- package/dist/sprites/spriteMapper.js +12 -0
- package/dist/ui/JimboBadge.d.ts +7 -0
- package/dist/ui/JimboBadge.js +24 -0
- package/dist/ui/JimboFloating.d.ts +8 -0
- package/dist/ui/JimboFloating.js +17 -0
- package/dist/ui/JimboToggleList.d.ts +11 -0
- package/dist/ui/JimboToggleList.js +10 -0
- package/dist/ui/footer.js +1 -1
- package/dist/ui/jimboFlankNav.js +5 -5
- package/dist/ui/panel.d.ts +2 -1
- package/dist/ui/panel.js +4 -4
- package/dist/ui/sprites.d.ts +14 -0
- package/dist/ui/sprites.js +52 -12
- package/dist/ui.d.ts +3 -0
- package/dist/ui.js +3 -0
- package/package.json +129 -122
- 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,336 +0,0 @@
|
|
|
1
|
-
// SeedDetailV2 — phone-size shell around AntePageV2 list.
|
|
2
|
-
// ┌──────────────────────┐
|
|
3
|
-
// │ TOP BAR │ seed · deck/stake · per-clause score cols
|
|
4
|
-
// ├──────────────────────┤
|
|
5
|
-
// │ EDGE HIT PILLS │ COD-style edge indicators for hits off-screen
|
|
6
|
-
// │ │
|
|
7
|
-
// │ ante 1 │ scroll-snapped vertical list of AntePageV2
|
|
8
|
-
// │ ante 2 │
|
|
9
|
-
// │ … │
|
|
10
|
-
// │ ante 8 │
|
|
11
|
-
// └──────────────────────┘
|
|
12
|
-
|
|
13
|
-
const { useState: sUS, useRef: sUR, useEffect: sUE, useLayoutEffect: sULE, useMemo: sUM, useCallback: sUCB } = React;
|
|
14
|
-
const Cv = window.JimboColor;
|
|
15
|
-
|
|
16
|
-
// ── ScoreChip — shoulds: bare sprite + count badge (count IS the data)
|
|
17
|
-
// · musts: framed box with ✓/✗ (presence IS the data)
|
|
18
|
-
function ScoreCol({ clause, hits }) {
|
|
19
|
-
const lit = hits > 0;
|
|
20
|
-
const must = clause._kind === 'must';
|
|
21
|
-
// Balatro metaphor: must = BLUE (chips, the base), should = RED (mult, the bonus)
|
|
22
|
-
const color = must ? Cv.BLUE : lit ? Cv.RED : Cv.GREY;
|
|
23
|
-
|
|
24
|
-
const spriteKind =
|
|
25
|
-
clause.type === 'joker' || clause.type === 'souljoker' ? 'jokers' :
|
|
26
|
-
clause.type === 'voucher' ? 'vouchers' :
|
|
27
|
-
clause.type === 'smallblindtag' || clause.type === 'bigblindtag' ? 'tags' :
|
|
28
|
-
clause.type === 'boss' ? 'blinds' : null;
|
|
29
|
-
|
|
30
|
-
const w = 30, h = spriteKind === 'tags' || spriteKind === 'blinds' ? 30 : 40;
|
|
31
|
-
const sprite = (
|
|
32
|
-
spriteKind === 'jokers' ? <Sprite sheet="jokers" name={clause.value} width={w} height={h} /> :
|
|
33
|
-
spriteKind === 'vouchers' ? <Sprite sheet="vouchers" name={clause.value} width={w} height={h} /> :
|
|
34
|
-
spriteKind === 'tags' ? <Sprite sheet="tags" name={clause.value} width={h} height={h} /> :
|
|
35
|
-
spriteKind === 'blinds' ? <BossChip name={clause.value} size={h} /> : null
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
if (must) {
|
|
39
|
-
// Framed: the box + ✓/✗ IS the signal
|
|
40
|
-
return (
|
|
41
|
-
<div style={{
|
|
42
|
-
position: 'relative',
|
|
43
|
-
padding: '4px 4px 2px',
|
|
44
|
-
border: `2px solid ${lit ? color : Cv.DARK_GREY}`,
|
|
45
|
-
borderRadius: 5,
|
|
46
|
-
background: lit ? `${color}18` : Cv.DARKEST,
|
|
47
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
48
|
-
opacity: lit ? 1 : 0.6,
|
|
49
|
-
}}>
|
|
50
|
-
{sprite}
|
|
51
|
-
<div style={{
|
|
52
|
-
position: 'absolute', bottom: -8, left: '50%', transform: 'translateX(-50%)',
|
|
53
|
-
width: 16, height: 16,
|
|
54
|
-
background: Cv.DARKEST, border: `2px solid ${color}`, borderRadius: 8,
|
|
55
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 11, lineHeight: '12px',
|
|
56
|
-
color, textAlign: 'center',
|
|
57
|
-
}}>
|
|
58
|
-
{lit ? '✓' : '✗'}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
// Should: bare sprite + corner count
|
|
64
|
-
return (
|
|
65
|
-
<div style={{
|
|
66
|
-
position: 'relative', display: 'flex', alignItems: 'flex-end', justifyContent: 'center',
|
|
67
|
-
opacity: lit ? 1 : 0.4, filter: lit ? 'none' : 'grayscale(0.6)',
|
|
68
|
-
}}>
|
|
69
|
-
{sprite}
|
|
70
|
-
<div style={{
|
|
71
|
-
position: 'absolute', bottom: -4, right: -4,
|
|
72
|
-
minWidth: 16, height: 16, padding: '0 3px',
|
|
73
|
-
background: Cv.DARKEST,
|
|
74
|
-
fontFamily: 'm6x11plus, monospace', fontSize: 11, lineHeight: '16px',
|
|
75
|
-
color, textAlign: 'center',
|
|
76
|
-
borderRadius: 8,
|
|
77
|
-
}}>
|
|
78
|
-
×{hits}
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function TopBar({ seed, filter }) {
|
|
85
|
-
const allClauses = [...filter.must.map(c => ({ ...c, _kind: 'must' })), ...filter.should.map(c => ({ ...c, _kind: 'should' }))];
|
|
86
|
-
return (
|
|
87
|
-
<div style={{
|
|
88
|
-
background: Cv.DARKEST,
|
|
89
|
-
borderBottom: `2px solid ${Cv.BLACK}`,
|
|
90
|
-
padding: '8px 10px 8px',
|
|
91
|
-
position: 'sticky', top: 0, zIndex: 30,
|
|
92
|
-
boxShadow: '0 2px 0 #000',
|
|
93
|
-
}}>
|
|
94
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
|
|
95
|
-
<button style={btn}>
|
|
96
|
-
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round"><path d="M9 2L4 7l5 5"/></svg>
|
|
97
|
-
</button>
|
|
98
|
-
<div style={{ textAlign: 'center', flex: 1 }}>
|
|
99
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 18, color: Cv.WHITE, letterSpacing: 3, lineHeight: 1 }}>{seed.seed}</div>
|
|
100
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: Cv.GREY, letterSpacing: 1.5, marginTop: 2 }}>{seed.deck} · {seed.stake} · score {seed.score.totalScore}</div>
|
|
101
|
-
</div>
|
|
102
|
-
<button style={btn}>
|
|
103
|
-
<svg width="12" height="12" viewBox="0 0 14 14" fill="currentColor"><circle cx="3" cy="7" r="1.3"/><circle cx="7" cy="7" r="1.3"/><circle cx="11" cy="7" r="1.3"/></svg>
|
|
104
|
-
</button>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<div style={{ display: 'flex', gap: 10, justifyContent: 'center', alignItems: 'flex-end', padding: '0 4px' }}>
|
|
108
|
-
{allClauses.map(c => (
|
|
109
|
-
<ScoreCol key={c.id} clause={c} hits={seed.score.totals[c.id] || 0} />
|
|
110
|
-
))}
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const btn = {
|
|
117
|
-
width: 26, height: 26, border: `2px solid ${Cv.BLACK}`, borderRadius: 4, background: Cv.DARK_GREY,
|
|
118
|
-
color: Cv.WHITE, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
|
|
119
|
-
boxShadow: `inset 0 1px 0 rgba(255,255,255,.08), 0 2px 0 ${Cv.BLACK}`, padding: 0,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// ── EdgeHitIndicator — COD-style: when a hit is scrolled off the top/bottom,
|
|
123
|
-
// render a pinned arrow at the edge with a small joker sprite.
|
|
124
|
-
function EdgeHitIndicator({ direction, item, onTap }) {
|
|
125
|
-
const name = item.type === 'pack' ? 'arcana' : item.value;
|
|
126
|
-
const sheet = item.type === 'pack' ? 'boosters' : 'jokers';
|
|
127
|
-
const hit = item._bestHit;
|
|
128
|
-
const color = hitColor(hit);
|
|
129
|
-
const pointUp = direction === 'up';
|
|
130
|
-
return (
|
|
131
|
-
<div
|
|
132
|
-
onClick={onTap}
|
|
133
|
-
style={{
|
|
134
|
-
pointerEvents: 'auto',
|
|
135
|
-
display: 'flex', alignItems: 'center', gap: 6,
|
|
136
|
-
padding: '3px 8px 3px 4px',
|
|
137
|
-
background: `${Cv.DARKEST}ee`,
|
|
138
|
-
border: `2px solid ${color}`,
|
|
139
|
-
borderRadius: pointUp ? '0 0 14px 14px' : '14px 14px 0 0',
|
|
140
|
-
cursor: 'pointer',
|
|
141
|
-
animation: 'edgeBob 1.4s ease-in-out infinite',
|
|
142
|
-
boxShadow: `0 0 10px ${color}aa`,
|
|
143
|
-
}}
|
|
144
|
-
title={`${item.value} · Ante ${item.ante} · tap to scroll`}
|
|
145
|
-
>
|
|
146
|
-
<div style={{
|
|
147
|
-
width: 20, height: 20, overflow: 'hidden', borderRadius: 3,
|
|
148
|
-
border: `1px solid ${color}`,
|
|
149
|
-
}}>
|
|
150
|
-
<div style={{ transform: 'scale(1.6)', transformOrigin: '50% 30%' }}>
|
|
151
|
-
<Sprite sheet={sheet} name={name} width={20} height={27} />
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
<div style={{ display: 'flex', flexDirection: 'column', lineHeight: 1 }}>
|
|
155
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: Cv.WHITE, letterSpacing: 0.5, whiteSpace: 'nowrap', maxWidth: 74, overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
156
|
-
{item.value}
|
|
157
|
-
</div>
|
|
158
|
-
<div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 8, color, letterSpacing: 1 }}>
|
|
159
|
-
A{item.ante}·{item.where}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
<svg width="10" height="10" viewBox="0 0 10 10" style={{ color }}>
|
|
163
|
-
{pointUp
|
|
164
|
-
? <path d="M5 2 L9 7 L1 7 Z" fill="currentColor" />
|
|
165
|
-
: <path d="M5 8 L1 3 L9 3 Z" fill="currentColor" />}
|
|
166
|
-
</svg>
|
|
167
|
-
</div>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ── SeedBody — scroll container + vertical ante list + edge indicators
|
|
172
|
-
function SeedBody({ seed, filter }) {
|
|
173
|
-
const scrollRef = sUR(null);
|
|
174
|
-
const [edgeHits, setEdgeHits] = sUS({ up: [], down: [] });
|
|
175
|
-
|
|
176
|
-
// Collect all hits in this seed, with where-info for labels.
|
|
177
|
-
const allHits = sUM(() => {
|
|
178
|
-
const out = [];
|
|
179
|
-
seed.antes.forEach(a => {
|
|
180
|
-
a.shopQueue.forEach((it, i) => {
|
|
181
|
-
if (it.hits?.length) out.push({ value: it.value, type: 'joker', ante: a.ante, where: `Shop#${i+1}`, _bestHit: bestHit(it.hits), _findEl: () => it._el });
|
|
182
|
-
});
|
|
183
|
-
a.boosterPacks.forEach((p, pi) => {
|
|
184
|
-
p.itemHits?.forEach((hArr, ii) => {
|
|
185
|
-
if (hArr.length) out.push({ value: p.items[ii], type: 'joker', ante: a.ante, where: `P${pi+1}·${ii+1}`, _bestHit: bestHit(hArr), _findEl: () => p._el });
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
if (a._soulHits?.length) out.push({ value: a.soulJoker.value, type: 'joker', ante: a.ante, where: 'Soul', _bestHit: bestHit(a._soulHits), _findEl: () => a._soulEl });
|
|
189
|
-
});
|
|
190
|
-
return out;
|
|
191
|
-
}, [seed]);
|
|
192
|
-
|
|
193
|
-
sUE(() => {
|
|
194
|
-
const el = scrollRef.current;
|
|
195
|
-
if (!el) return;
|
|
196
|
-
const onScroll = () => {
|
|
197
|
-
const top = el.scrollTop;
|
|
198
|
-
const bottom = top + el.clientHeight;
|
|
199
|
-
const up = [], down = [];
|
|
200
|
-
for (const h of allHits) {
|
|
201
|
-
const node = h._findEl?.();
|
|
202
|
-
if (!node) continue;
|
|
203
|
-
const rect = node.getBoundingClientRect();
|
|
204
|
-
const parentRect = el.getBoundingClientRect();
|
|
205
|
-
const yTop = rect.top - parentRect.top + el.scrollTop;
|
|
206
|
-
const yBot = yTop + rect.height;
|
|
207
|
-
if (yBot < top + 40) up.push(h);
|
|
208
|
-
else if (yTop > bottom - 40) down.push(h);
|
|
209
|
-
}
|
|
210
|
-
setEdgeHits({ up: up.slice(-3).reverse(), down: down.slice(0, 3) });
|
|
211
|
-
};
|
|
212
|
-
onScroll();
|
|
213
|
-
el.addEventListener('scroll', onScroll, { passive: true });
|
|
214
|
-
const id = setInterval(onScroll, 400); // catch layout settling
|
|
215
|
-
return () => { el.removeEventListener('scroll', onScroll); clearInterval(id); };
|
|
216
|
-
}, [allHits]);
|
|
217
|
-
|
|
218
|
-
const jumpTo = (hit) => {
|
|
219
|
-
const el = scrollRef.current;
|
|
220
|
-
const node = hit._findEl?.();
|
|
221
|
-
if (!el || !node) return;
|
|
222
|
-
const parentRect = el.getBoundingClientRect();
|
|
223
|
-
const rect = node.getBoundingClientRect();
|
|
224
|
-
const target = el.scrollTop + (rect.top - parentRect.top) - 120;
|
|
225
|
-
el.scrollTo({ top: target, behavior: 'smooth' });
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<div style={{ position: 'relative', flex: 1, minHeight: 0, overflow: 'hidden' }}>
|
|
230
|
-
{/* Scroll container */}
|
|
231
|
-
<div
|
|
232
|
-
ref={scrollRef}
|
|
233
|
-
className="v2-shop-scroll"
|
|
234
|
-
style={{
|
|
235
|
-
height: '100%',
|
|
236
|
-
overflowY: 'auto',
|
|
237
|
-
overflowX: 'hidden',
|
|
238
|
-
scrollSnapType: 'y proximity',
|
|
239
|
-
scrollbarWidth: 'none',
|
|
240
|
-
}}
|
|
241
|
-
>
|
|
242
|
-
{seed.antes.map((a, i) => (
|
|
243
|
-
<div key={i} style={{ scrollSnapAlign: 'start' }}>
|
|
244
|
-
<AntePageV2 ante={a} />
|
|
245
|
-
{i < seed.antes.length - 1 && (
|
|
246
|
-
<div style={{
|
|
247
|
-
height: 2, margin: '0 14px',
|
|
248
|
-
background: `repeating-linear-gradient(90deg, ${Cv.PANEL_EDGE} 0 6px, transparent 6px 12px)`,
|
|
249
|
-
}} />
|
|
250
|
-
)}
|
|
251
|
-
</div>
|
|
252
|
-
))}
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
{/* Edge indicators */}
|
|
256
|
-
<div style={{
|
|
257
|
-
position: 'absolute', top: 0, left: 0, right: 0,
|
|
258
|
-
display: 'flex', justifyContent: 'center', gap: 6, padding: '0 8px',
|
|
259
|
-
pointerEvents: 'none', zIndex: 20,
|
|
260
|
-
flexWrap: 'wrap',
|
|
261
|
-
}}>
|
|
262
|
-
{edgeHits.up.map((h, i) => <EdgeHitIndicator key={`u${i}`} direction="up" item={h} onTap={() => jumpTo(h)} />)}
|
|
263
|
-
</div>
|
|
264
|
-
<div style={{
|
|
265
|
-
position: 'absolute', bottom: 0, left: 0, right: 0,
|
|
266
|
-
display: 'flex', justifyContent: 'center', gap: 6, padding: '0 8px',
|
|
267
|
-
pointerEvents: 'none', zIndex: 20,
|
|
268
|
-
flexWrap: 'wrap',
|
|
269
|
-
}}>
|
|
270
|
-
{edgeHits.down.map((h, i) => <EdgeHitIndicator key={`d${i}`} direction="down" item={h} onTap={() => jumpTo(h)} />)}
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// ── SeedDetailV2 — horizontal seed swiper + body
|
|
277
|
-
function SeedDetailV2({ seeds, filter }) {
|
|
278
|
-
const [idx, setIdx] = sUS(0);
|
|
279
|
-
const [drag, setDrag] = sUS({ dx: 0, active: false });
|
|
280
|
-
const touch = sUR({ x0: 0, y0: 0, locked: null });
|
|
281
|
-
|
|
282
|
-
const onDown = (e) => {
|
|
283
|
-
const t = e.touches ? e.touches[0] : e;
|
|
284
|
-
touch.current = { x0: t.clientX, y0: t.clientY, locked: null };
|
|
285
|
-
};
|
|
286
|
-
const onMove = (e) => {
|
|
287
|
-
if (!touch.current.x0) return;
|
|
288
|
-
const t = e.touches ? e.touches[0] : e;
|
|
289
|
-
const dx = t.clientX - touch.current.x0;
|
|
290
|
-
const dy = t.clientY - touch.current.y0;
|
|
291
|
-
if (touch.current.locked == null) {
|
|
292
|
-
if (Math.abs(dx) > 8 && Math.abs(dx) > Math.abs(dy) * 1.5) touch.current.locked = 'x';
|
|
293
|
-
else if (Math.abs(dy) > 8) touch.current.locked = 'y';
|
|
294
|
-
}
|
|
295
|
-
if (touch.current.locked === 'x') setDrag({ dx, active: true });
|
|
296
|
-
};
|
|
297
|
-
const onUp = () => {
|
|
298
|
-
if (touch.current.locked === 'x') {
|
|
299
|
-
const dx = drag.dx;
|
|
300
|
-
if (dx < -70 && idx < seeds.length - 1) setIdx(idx + 1);
|
|
301
|
-
else if (dx > 70 && idx > 0) setIdx(idx - 1);
|
|
302
|
-
}
|
|
303
|
-
touch.current = { x0: 0, y0: 0, locked: null };
|
|
304
|
-
setDrag({ dx: 0, active: false });
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const seed = seeds[idx];
|
|
308
|
-
|
|
309
|
-
return (
|
|
310
|
-
<div style={{
|
|
311
|
-
width: '100%', height: '100%', background: Cv.DARKEST,
|
|
312
|
-
display: 'flex', flexDirection: 'column', overflow: 'hidden',
|
|
313
|
-
fontFamily: 'm6x11plus, monospace', color: Cv.WHITE,
|
|
314
|
-
}}>
|
|
315
|
-
<div onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp}
|
|
316
|
-
style={{ touchAction: 'pan-y' }}>
|
|
317
|
-
<TopBar seed={seed} filter={filter} />
|
|
318
|
-
</div>
|
|
319
|
-
<div style={{
|
|
320
|
-
flex: 1, minHeight: 0, position: 'relative',
|
|
321
|
-
transform: drag.active ? `translateX(${drag.dx * 0.25}px) rotate(${drag.dx * 0.015}deg)` : 'none',
|
|
322
|
-
transition: drag.active ? 'none' : 'transform 260ms cubic-bezier(.33,1,.4,1)',
|
|
323
|
-
}}>
|
|
324
|
-
<SeedBody key={seed.seed} seed={seed} filter={filter} />
|
|
325
|
-
</div>
|
|
326
|
-
{/* Seed dots */}
|
|
327
|
-
<div style={{ position: 'absolute', bottom: 4, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 5, zIndex: 40 }}>
|
|
328
|
-
{seeds.map((_, i) => (
|
|
329
|
-
<div key={i} style={{ width: 6, height: 6, borderRadius: 3, background: i === idx ? Cv.GOLD : Cv.DARK_GREY }} />
|
|
330
|
-
))}
|
|
331
|
-
</div>
|
|
332
|
-
</div>
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
window.SeedDetailV2 = SeedDetailV2;
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
// SeedOGCard — 1200×630 social-preview rollup.
|
|
2
|
-
// /seed/[seed]/og.png renders this server-side.
|
|
3
|
-
//
|
|
4
|
-
// Design rule (from pifreak): DON'T SHOW OFF CARDS THAT AREN'T THERE.
|
|
5
|
-
// The point of posting a seed is showing off the JOKERS YOU FOUND, splayed
|
|
6
|
-
// out like a Balatro poker hand. Unmatched clauses get a tiny stat line,
|
|
7
|
-
// not a grayed-out sprite.
|
|
8
|
-
//
|
|
9
|
-
// Splay math: mirrors Balatro `cardarea.lua` hand positioning —
|
|
10
|
-
// rotate = (i - center) * spread° (≈6° per card, clamped)
|
|
11
|
-
// y = abs(i - center) * arcDip (arc dip, not a straight line)
|
|
12
|
-
// ambient_tilt: ~0.2 per Balatro/card.lua — each card gets a unique
|
|
13
|
-
// phase (i/1.14212) and a gentle sin/cos wobble. Pure CSS anim.
|
|
14
|
-
//
|
|
15
|
-
// Score columns: red SHOULD line has room for per-clause `+N` pills so
|
|
16
|
-
// the share card doubles as a scoring preview.
|
|
17
|
-
|
|
18
|
-
const Co = window.JimboColor;
|
|
19
|
-
|
|
20
|
-
// Balatro rarity palette
|
|
21
|
-
const RARITY_COLOR = {
|
|
22
|
-
Common: '#009dff',
|
|
23
|
-
Uncommon: '#3bc47e',
|
|
24
|
-
Rare: '#fe5f55',
|
|
25
|
-
Legendary: '#b26cbd',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Inject ambient_tilt keyframes once (Balatro card.lua: G.TIMERS.REAL*1.56 + id/1.35 drift)
|
|
29
|
-
(function injectOgKf(){
|
|
30
|
-
if (document.getElementById('og-card-kf')) return;
|
|
31
|
-
const s = document.createElement('style');
|
|
32
|
-
s.id = 'og-card-kf';
|
|
33
|
-
s.textContent = `
|
|
34
|
-
@keyframes ogTilt0 { 0%,100% { transform: var(--base) rotate(var(--r, 0deg)) translateY(var(--ty, 0px)); }
|
|
35
|
-
50% { transform: var(--base) rotate(calc(var(--r, 0deg) + 1.2deg)) translateY(calc(var(--ty, 0px) - 2px)); } }
|
|
36
|
-
@keyframes ogTilt1 { 0%,100% { transform: var(--base) rotate(var(--r, 0deg)) translateY(var(--ty, 0px)); }
|
|
37
|
-
50% { transform: var(--base) rotate(calc(var(--r, 0deg) - 1.4deg)) translateY(calc(var(--ty, 0px) + 2px)); } }
|
|
38
|
-
@keyframes ogTilt2 { 0%,100% { transform: var(--base) rotate(var(--r, 0deg)) translateY(var(--ty, 0px)); }
|
|
39
|
-
50% { transform: var(--base) rotate(calc(var(--r, 0deg) + 0.9deg)) translateY(calc(var(--ty, 0px) - 1px)); } }
|
|
40
|
-
@keyframes ogTilt3 { 0%,100% { transform: var(--base) rotate(var(--r, 0deg)) translateY(var(--ty, 0px)); }
|
|
41
|
-
50% { transform: var(--base) rotate(calc(var(--r, 0deg) - 0.7deg)) translateY(calc(var(--ty, 0px) + 1.5px)); } }
|
|
42
|
-
`;
|
|
43
|
-
document.head.appendChild(s);
|
|
44
|
-
})();
|
|
45
|
-
|
|
46
|
-
// One splayed card in the hand. Includes ambient tilt + per-card phase.
|
|
47
|
-
function FannedCard({ sprite, label, score, frame, rotate, yOff, zIndex, phase }) {
|
|
48
|
-
const tiltKf = ['ogTilt0','ogTilt1','ogTilt2','ogTilt3'][phase % 4];
|
|
49
|
-
return (
|
|
50
|
-
<div style={{
|
|
51
|
-
position: 'relative',
|
|
52
|
-
marginLeft: -14, // cards overlap like a hand
|
|
53
|
-
zIndex,
|
|
54
|
-
animation: `${tiltKf} ${4 + (phase % 3)}s ease-in-out infinite`,
|
|
55
|
-
animationDelay: `${phase * 0.37}s`,
|
|
56
|
-
['--base']: 'translate(0,0)',
|
|
57
|
-
['--r']: `${rotate}deg`,
|
|
58
|
-
['--ty']: `${yOff}px`,
|
|
59
|
-
transformOrigin: '50% 110%', // pivot below card, like holding from bottom
|
|
60
|
-
transform: `translate(0,0) rotate(${rotate}deg) translateY(${yOff}px)`,
|
|
61
|
-
}}>
|
|
62
|
-
<div style={{
|
|
63
|
-
padding: 6, borderRadius: 8,
|
|
64
|
-
border: `4px solid ${frame || Co.GOLD}`,
|
|
65
|
-
background: `${Co.DARKEST}`,
|
|
66
|
-
boxShadow: `0 6px 0 rgba(0,0,0,.55), 0 0 18px ${frame || Co.GOLD}55`,
|
|
67
|
-
position: 'relative',
|
|
68
|
-
}}>
|
|
69
|
-
{sprite}
|
|
70
|
-
{score != null && (
|
|
71
|
-
<div style={{
|
|
72
|
-
position: 'absolute', top: -14, right: -14,
|
|
73
|
-
minWidth: 34, padding: '2px 8px', height: 30,
|
|
74
|
-
background: Co.RED, border: `3px solid ${Co.DARKEST}`, borderRadius: 15,
|
|
75
|
-
color: Co.WHITE, fontSize: 18, lineHeight: '22px', textAlign: 'center',
|
|
76
|
-
textShadow: '1px 1px 0 rgba(0,0,0,.8)', letterSpacing: 1,
|
|
77
|
-
}}>+{score}</div>
|
|
78
|
-
)}
|
|
79
|
-
{label && (
|
|
80
|
-
<div style={{
|
|
81
|
-
position: 'absolute', left: 0, right: 0, bottom: -26, textAlign: 'center',
|
|
82
|
-
fontSize: 13, color: frame || Co.GOLD_TEXT, letterSpacing: 2,
|
|
83
|
-
textShadow: '1px 1px 0 rgba(0,0,0,.8)',
|
|
84
|
-
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
85
|
-
}}>{label}</div>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function SeedOGCard({ seed, filter }) {
|
|
93
|
-
const must = filter.must || [];
|
|
94
|
-
const should = filter.should || [];
|
|
95
|
-
const allMustHit = must.every(c => (seed.score?.totals[c.id] || 0) > 0);
|
|
96
|
-
|
|
97
|
-
// Collect actual HITS across MUST + SHOULD to render as the hand.
|
|
98
|
-
// Each entry: { value, edition, rarity, score, kind, clauseId }
|
|
99
|
-
const hand = [];
|
|
100
|
-
for (const c of must) {
|
|
101
|
-
const matches = seed.score?.matches?.[c.id] || [];
|
|
102
|
-
if (!matches.length) continue;
|
|
103
|
-
const m = matches[0];
|
|
104
|
-
hand.push({
|
|
105
|
-
kind: 'must', value: m.value, edition: m.edition, clauseId: c.id,
|
|
106
|
-
frame: Co.BLUE, label: c.label || m.value, score: null,
|
|
107
|
-
sheet: c.type === 'joker' || c.type === 'souljoker' ? 'jokers' :
|
|
108
|
-
c.type === 'voucher' ? 'vouchers' :
|
|
109
|
-
c.type.includes('tag') ? 'tags' :
|
|
110
|
-
c.type === 'boss' ? 'blinds' : 'jokers',
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
for (const c of should) {
|
|
114
|
-
const matches = seed.score?.matches?.[c.id] || [];
|
|
115
|
-
if (!matches.length) continue;
|
|
116
|
-
const m = matches[0];
|
|
117
|
-
const rarity = c.rarity || window.jokerRarity?.(m.value) || 'Common';
|
|
118
|
-
hand.push({
|
|
119
|
-
kind: 'should', value: m.value, edition: m.edition, clauseId: c.id,
|
|
120
|
-
frame: RARITY_COLOR[rarity], label: c.label && c.label !== 'Any Rare' ? c.label : m.value,
|
|
121
|
-
score: (c.score || 1) * (seed.score.totals[c.id] || 1),
|
|
122
|
-
sheet: 'jokers',
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Un-matched SHOULD clauses go in a tiny "didn't hit" score line (no big art).
|
|
127
|
-
const unmatched = should.filter(c => !(seed.score?.totals[c.id] > 0));
|
|
128
|
-
|
|
129
|
-
// Splay math — Balatro hand fan.
|
|
130
|
-
const n = hand.length;
|
|
131
|
-
const center = (n - 1) / 2;
|
|
132
|
-
const spread = Math.min(8, 44 / Math.max(1, n)); // degrees per card, clamp so 8 cards look right
|
|
133
|
-
const arcDip = 3.5; // px per step of arc
|
|
134
|
-
const mkFan = (i) => {
|
|
135
|
-
const off = i - center;
|
|
136
|
-
return { rotate: off * spread, yOff: Math.abs(off) * arcDip, zIndex: 100 - Math.round(Math.abs(off) * 10) };
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Sprite renderer for a hand entry
|
|
140
|
-
const renderSprite = (h, size = 96) => {
|
|
141
|
-
const w = size, hh = h.sheet === 'tags' || h.sheet === 'blinds' ? size : Math.round(size * 95 / 71);
|
|
142
|
-
if (h.sheet === 'blinds') return <BossChip name={h.value} size={hh} />;
|
|
143
|
-
return <Sprite sheet={h.sheet} name={h.value} width={w} height={hh} />;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<div style={{
|
|
148
|
-
width: 1200, height: 630, position: 'relative', overflow: 'hidden',
|
|
149
|
-
background: `radial-gradient(ellipse at top left, #2d4a38 0%, #0f1a13 70%)`,
|
|
150
|
-
fontFamily: 'm6x11plus, monospace', color: Co.WHITE,
|
|
151
|
-
}}>
|
|
152
|
-
{/* Felt texture overlay */}
|
|
153
|
-
<div style={{
|
|
154
|
-
position: 'absolute', inset: 0, opacity: 0.08,
|
|
155
|
-
backgroundImage: 'repeating-linear-gradient(45deg, #fff 0 1px, transparent 1px 3px)',
|
|
156
|
-
}} />
|
|
157
|
-
|
|
158
|
-
{/* Top strip — filter name + branding */}
|
|
159
|
-
<div style={{ position: 'absolute', top: 28, left: 40, right: 40, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
|
160
|
-
<div style={{ minWidth: 0, flex: 1 }}>
|
|
161
|
-
<div style={{ fontSize: 16, color: Co.GOLD, letterSpacing: 3, textShadow: '2px 2px 0 rgba(0,0,0,.8)' }}>FILTER</div>
|
|
162
|
-
<div style={{ fontSize: 28, color: Co.WHITE, letterSpacing: 1, textShadow: '2px 2px 0 rgba(0,0,0,.8)',
|
|
163
|
-
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 760 }}>
|
|
164
|
-
{filter.name}
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
<div style={{ fontSize: 16, color: Co.GREY, letterSpacing: 3 }}>Balatro Seed Curator</div>
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
{/* Seed code — big, LEFT */}
|
|
171
|
-
<div style={{ position: 'absolute', top: 96, left: 40 }}>
|
|
172
|
-
<div style={{ fontSize: 18, color: Co.GREY, letterSpacing: 4 }}>SEED</div>
|
|
173
|
-
<div style={{ fontSize: 108, color: Co.WHITE, letterSpacing: 10, lineHeight: 1, marginTop: 4, textShadow: `3px 4px 0 ${Co.BLACK}` }}>{seed.seed}</div>
|
|
174
|
-
<div style={{ display: 'flex', gap: 16, marginTop: 8, fontSize: 20, color: Co.GREY, letterSpacing: 2 }}>
|
|
175
|
-
<span>{seed.deck} Deck</span>
|
|
176
|
-
<span style={{ color: Co.PANEL_EDGE }}>·</span>
|
|
177
|
-
<span>{seed.stake} Stake</span>
|
|
178
|
-
<span style={{ color: Co.PANEL_EDGE }}>·</span>
|
|
179
|
-
<span style={{ color: allMustHit ? Co.BLUE : Co.RED }}>
|
|
180
|
-
{allMustHit ? '✓ must complete' : '✗ must incomplete'}
|
|
181
|
-
</span>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
{/* Score — big number, RIGHT */}
|
|
186
|
-
<div style={{ position: 'absolute', top: 96, right: 40, textAlign: 'right' }}>
|
|
187
|
-
<div style={{ fontSize: 18, color: Co.GREY, letterSpacing: 4 }}>SCORE</div>
|
|
188
|
-
<div style={{ fontSize: 140, color: Co.RED, letterSpacing: 4, lineHeight: 1, marginTop: 4, textShadow: `4px 6px 0 ${Co.BLACK}` }}>
|
|
189
|
-
{seed.score.totalScore}
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
{/* THE HAND — splayed rainbow poker-hand of everything that hit. */}
|
|
194
|
-
<div style={{
|
|
195
|
-
position: 'absolute', left: 40, right: 40, bottom: 78,
|
|
196
|
-
display: 'flex', justifyContent: 'center', alignItems: 'flex-end',
|
|
197
|
-
paddingLeft: 14, // compensate first card's -14 marginLeft
|
|
198
|
-
}}>
|
|
199
|
-
{hand.map((h, i) => {
|
|
200
|
-
const fan = mkFan(i);
|
|
201
|
-
return (
|
|
202
|
-
<FannedCard
|
|
203
|
-
key={h.clauseId + ':' + i}
|
|
204
|
-
sprite={renderSprite(h, 96)}
|
|
205
|
-
label={h.label}
|
|
206
|
-
score={h.kind === 'should' ? h.score : null}
|
|
207
|
-
frame={h.frame}
|
|
208
|
-
rotate={fan.rotate}
|
|
209
|
-
yOff={fan.yOff}
|
|
210
|
-
zIndex={fan.zIndex}
|
|
211
|
-
phase={i}
|
|
212
|
-
/>
|
|
213
|
-
);
|
|
214
|
-
})}
|
|
215
|
-
{hand.length === 0 && (
|
|
216
|
-
<div style={{ fontSize: 22, color: Co.GREY, letterSpacing: 3, padding: 40 }}>
|
|
217
|
-
no hits — try a different seed
|
|
218
|
-
</div>
|
|
219
|
-
)}
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
{/* SHOULD score line — unmatched clauses get tiny sad-face stat pills here.
|
|
223
|
-
Room preserved so this reads as a proper scoring readout, not just art. */}
|
|
224
|
-
{unmatched.length > 0 && (
|
|
225
|
-
<div style={{
|
|
226
|
-
position: 'absolute', left: 40, right: 40, bottom: 30,
|
|
227
|
-
display: 'flex', alignItems: 'center', gap: 10,
|
|
228
|
-
}}>
|
|
229
|
-
<div style={{
|
|
230
|
-
fontSize: 12, letterSpacing: 3, padding: '3px 10px',
|
|
231
|
-
background: Co.DARK_RED, color: Co.WHITE, borderRadius: 3,
|
|
232
|
-
textShadow: '1px 1px 0 rgba(0,0,0,.8)',
|
|
233
|
-
}}>MISSED</div>
|
|
234
|
-
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
235
|
-
{unmatched.map(c => (
|
|
236
|
-
<div key={c.id} style={{
|
|
237
|
-
fontSize: 12, color: Co.GREY, letterSpacing: 1.5,
|
|
238
|
-
padding: '2px 8px', border: `1px dashed ${Co.GREY}`, borderRadius: 3,
|
|
239
|
-
}}>
|
|
240
|
-
{c.label || c.value} <span style={{ color: Co.DARK_RED }}>+{c.score || 1}</span>
|
|
241
|
-
</div>
|
|
242
|
-
))}
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
)}
|
|
246
|
-
</div>
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
window.SeedOGCard = SeedOGCard;
|
|
251
|
-
window.RARITY_COLOR = RARITY_COLOR;
|