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,259 +0,0 @@
1
- // <Sprite> — positions a slice of a sprite sheet via background-image.
2
- // Works with any sheet registered in window.SHEETS and any name map loaded
3
- // via loadSpriteMap(). Sizes are in display-px; pixel-art is preserved via
4
- // image-rendering: pixelated.
5
-
6
- const { useState, useEffect, useRef } = React;
7
-
8
- // ─── Name maps, loaded async from JSON. Keys are normalized (lowercase, no
9
- // spaces, no punctuation) to tolerate both "Blueprint" and "blueprint"
10
- // inputs.
11
- const SPRITE_MAPS = {};
12
-
13
- function normalizeName(s) {
14
- return String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
15
- }
16
-
17
- async function loadSpriteMap(sheet, url, { field = 'pos' } = {}) {
18
- const r = await fetch(url);
19
- const data = await r.json();
20
- const out = {};
21
- if (Array.isArray(data)) {
22
- for (const e of data) out[normalizeName(e.name)] = e[field];
23
- } else if (data.sprites) {
24
- // blinds_metadata shape
25
- const walk = (obj) => {
26
- for (const [k, v] of Object.entries(obj)) {
27
- if (v && typeof v === 'object' && 'x' in v && 'y' in v) {
28
- out[normalizeName(k)] = { x: v.x, y: v.y };
29
- } else if (v && typeof v === 'object') {
30
- walk(v);
31
- }
32
- }
33
- };
34
- walk(data.sprites);
35
- }
36
- SPRITE_MAPS[sheet] = { ...(SPRITE_MAPS[sheet] || {}), ...out };
37
- return out;
38
- }
39
-
40
- function getSpritePos(sheet, name) {
41
- const m = SPRITE_MAPS[sheet];
42
- if (!m) return null;
43
- return m[normalizeName(name)] || null;
44
- }
45
-
46
- // Hook: re-render when any sprite map loads. Used by Sprite to pick up
47
- // positions that arrive after first render.
48
- const mapListeners = new Set();
49
- function useSpriteMapTick() {
50
- const [, set] = useState(0);
51
- useEffect(() => {
52
- const fn = () => set((n) => n + 1);
53
- mapListeners.add(fn);
54
- return () => mapListeners.delete(fn);
55
- }, []);
56
- }
57
- const _origLoadSpriteMap = loadSpriteMap;
58
- window.loadSpriteMap = async (...args) => {
59
- const r = await _origLoadSpriteMap(...args);
60
- for (const fn of mapListeners) fn();
61
- return r;
62
- };
63
-
64
- // ─── <Sprite> — pos is {x,y} grid index. width/height in display px.
65
- function Sprite({ sheet, name, pos: posOverride, width, height, className = '', style = {}, children }) {
66
- useSpriteMapTick();
67
- const sheetInfo = window.SHEETS[sheet];
68
- if (!sheetInfo) return null;
69
- const pos = posOverride || getSpritePos(sheet, name);
70
-
71
- // Native sprite aspect (before display sizing). Pull from the sheet image
72
- // if already cached, else assume 71:95 joker ratio as a safe default.
73
- const aspect = SHEET_ASPECTS[sheet] || (95 / 71);
74
- const w = width || 71;
75
- const h = height || Math.round(w * aspect);
76
-
77
- if (!pos) {
78
- return (
79
- <div className={className} style={{ width: w, height: h, background: 'rgba(255,0,0,0.08)', border: '1px dashed rgba(255,0,0,0.3)', boxSizing: 'border-box', ...style }} title={`missing: ${sheet}/${name}`}>
80
- {children}
81
- </div>
82
- );
83
- }
84
-
85
- const bgW = w * sheetInfo.cols;
86
- const bgH = h * sheetInfo.rows;
87
-
88
- return (
89
- <div
90
- className={className}
91
- style={{
92
- width: w,
93
- height: h,
94
- backgroundImage: `url(${sheetInfo.src})`,
95
- backgroundSize: `${bgW}px ${bgH}px`,
96
- backgroundPosition: `-${pos.x * w}px -${pos.y * h}px`,
97
- backgroundRepeat: 'no-repeat',
98
- imageRendering: 'pixelated',
99
- flexShrink: 0,
100
- position: 'relative',
101
- ...style,
102
- }}
103
- >
104
- {children}
105
- </div>
106
- );
107
- }
108
-
109
- // Aspect ratios per sheet (h/w), so Sprite can size correctly from just a
110
- // width. Derived from image-metadata; fallback is joker-like.
111
- const SHEET_ASPECTS = {
112
- jokers: 95 / 71, // 710/10 = 71, 1520/16 = 95
113
- tarots: 95 / 71,
114
- vouchers: 95 / 71,
115
- tags: 34 / 34, // tags are square-ish
116
- boosters: 190 / 142, // 568/4 = 142, 1710/9 = 190
117
- blinds: 1, // 68×68 square
118
- editions: 95 / 71,
119
- enhancers: 95 / 71,
120
- stickers: 95 / 71,
121
- deck: 95 / 71,
122
- stakes: 1,
123
- };
124
-
125
- // ─── <HitStamp> — small corner badge: green check for should-hit, gold
126
- // star for must-hit. Positioned absolutely, so parent must be relative.
127
- function HitStamp({ kind }) {
128
- if (!kind) return null;
129
- const palette = kind === 'must'
130
- ? { bg: window.JimboColor.GOLD, border: '#8a6a1f', glyph: '★', color: '#1e2b2d' }
131
- : { bg: window.JimboColor.GREEN_TEXT, border: '#1f7a55', glyph: '✓', color: '#fff' };
132
-
133
- return (
134
- <div
135
- style={{
136
- position: 'absolute',
137
- top: -4,
138
- right: -4,
139
- width: 16,
140
- height: 16,
141
- borderRadius: 8,
142
- background: palette.bg,
143
- border: `1.5px solid ${palette.border}`,
144
- color: palette.color,
145
- fontSize: 11,
146
- lineHeight: '13px',
147
- textAlign: 'center',
148
- fontFamily: 'm6x11plus, monospace',
149
- boxShadow: '0 1px 2px rgba(0,0,0,0.35)',
150
- zIndex: 2,
151
- pointerEvents: 'none',
152
- }}
153
- >
154
- {palette.glyph}
155
- </div>
156
- );
157
- }
158
-
159
- // ─── <JokerMini>, <TarotMini>, etc. — pre-sized wrappers with optional hit.
160
- function JokerMini({ name, hit, size = 64, edition, onClick }) {
161
- const w = size;
162
- const h = Math.round(size * 95 / 71);
163
- return (
164
- <div
165
- onClick={onClick}
166
- style={{
167
- position: 'relative',
168
- width: w,
169
- height: h,
170
- cursor: onClick ? 'pointer' : 'default',
171
- filter: hit ? 'none' : 'brightness(0.78) saturate(0.85)',
172
- transition: 'filter 180ms',
173
- }}
174
- >
175
- <Sprite sheet="jokers" name={name} width={w} height={h} />
176
- {edition && <Sprite sheet="editions" pos={EDITION_POS[edition] || { x: 0, y: 0 }} width={w} height={h} style={{ position: 'absolute', inset: 0, mixBlendMode: 'screen', opacity: 0.85 }} />}
177
- <HitStamp kind={hit} />
178
- </div>
179
- );
180
- }
181
-
182
- const EDITION_POS = { Foil: { x: 0, y: 0 }, Holographic: { x: 1, y: 0 }, Polychrome: { x: 2, y: 0 }, Negative: { x: 3, y: 0 } };
183
-
184
- function TarotMini({ name, hit, size = 64, onClick }) {
185
- const w = size;
186
- const h = Math.round(size * 95 / 71);
187
- return (
188
- <div onClick={onClick} style={{ position: 'relative', width: w, height: h, cursor: onClick ? 'pointer' : 'default', filter: hit ? 'none' : 'brightness(0.78) saturate(0.85)', transition: 'filter 180ms' }}>
189
- <Sprite sheet="tarots" name={name} width={w} height={h} />
190
- <HitStamp kind={hit} />
191
- </div>
192
- );
193
- }
194
-
195
- function VoucherMini({ name, hit, size = 64, onClick }) {
196
- const w = size;
197
- const h = Math.round(size * 95 / 71);
198
- return (
199
- <div onClick={onClick} style={{ position: 'relative', width: w, height: h, cursor: onClick ? 'pointer' : 'default', filter: hit ? 'none' : 'brightness(0.85) saturate(0.9)', transition: 'filter 180ms' }}>
200
- <Sprite sheet="vouchers" name={name} width={w} height={h} />
201
- <HitStamp kind={hit} />
202
- </div>
203
- );
204
- }
205
-
206
- function TagChip({ name, hit, size = 28, onClick }) {
207
- return (
208
- <div onClick={onClick} style={{ position: 'relative', cursor: onClick ? 'pointer' : 'default', filter: hit ? 'none' : 'none' }}>
209
- <Sprite sheet="tags" name={name} width={size} height={size} />
210
- <HitStamp kind={hit} />
211
- </div>
212
- );
213
- }
214
-
215
- function BossChip({ name, hit, size = 48, onClick }) {
216
- // BlindChips.png is an animation strip (21 frames per row). Show frame 0
217
- // by overriding pos.y to the row for this boss and pos.x = 0.
218
- const basePos = getSpritePos('blinds', name);
219
- if (!basePos) return <Sprite sheet="blinds" name={name} width={size} height={size} />;
220
- return (
221
- <div onClick={onClick} style={{ position: 'relative', cursor: onClick ? 'pointer' : 'default' }}>
222
- <Sprite sheet="blinds" pos={{ x: 0, y: basePos.y }} width={size} height={size} />
223
- <HitStamp kind={hit} />
224
- </div>
225
- );
226
- }
227
-
228
- function PackSprite({ kind, size = 56, onClick, open, hit }) {
229
- // kind: 'arcana' | 'celestial' | 'spectral' | 'buffoon' | 'standard' + 'jumbo' / 'mega' variants
230
- const w = size;
231
- const h = Math.round(size * 190 / 142);
232
- return (
233
- <div
234
- onClick={onClick}
235
- style={{
236
- position: 'relative',
237
- width: w,
238
- height: h,
239
- cursor: onClick ? 'pointer' : 'default',
240
- transform: open ? 'translateY(-4px) rotate(-2deg)' : 'none',
241
- transition: 'transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1)',
242
- }}
243
- >
244
- <Sprite sheet="boosters" name={kind} width={w} height={h} />
245
- <HitStamp kind={hit} />
246
- </div>
247
- );
248
- }
249
-
250
- window.Sprite = Sprite;
251
- window.HitStamp = HitStamp;
252
- window.JokerMini = JokerMini;
253
- window.TarotMini = TarotMini;
254
- window.VoucherMini = VoucherMini;
255
- window.TagChip = TagChip;
256
- window.BossChip = BossChip;
257
- window.PackSprite = PackSprite;
258
- window.normalizeName = normalizeName;
259
- window.getSpritePos = getSpritePos;
@@ -1,49 +0,0 @@
1
- // Balatro design tokens — from jaml-ui/src/ui/tokens.ts
2
- // DO NOT substitute with Lua HEX values; these are shader-output eyedropped.
3
-
4
- window.JimboColor = {
5
- RED: '#ff4c40',
6
- BLUE: '#0093ff',
7
- GREEN: '#429f79',
8
- ORANGE: '#ff9800',
9
- GOLD: '#e4b643',
10
- PURPLE: '#9e74ce',
11
-
12
- DARK_RED: '#a02721',
13
- DARK_BLUE: '#0057a1',
14
- DARK_ORANGE: '#a05b00',
15
- DARK_GREEN: '#215f46',
16
- DARK_PURPLE: '#5e437e',
17
-
18
- DARK_GREY: '#3a5055',
19
- DARKEST: '#1e2b2d',
20
- GREY: '#708386',
21
- TEAL_GREY: '#404c4e',
22
-
23
- PANEL_EDGE: '#1e2e32',
24
- INNER_BORDER: '#334461',
25
-
26
- BORDER_SILVER: '#b9c2d2',
27
- BORDER_SOUTH: '#777e89',
28
-
29
- GOLD_TEXT: '#e4b643',
30
- GREEN_TEXT: '#35bd86',
31
- ORANGE_TEXT: '#ff8f00',
32
- WHITE: '#ffffff',
33
- BLACK: '#000000',
34
- };
35
-
36
- // Sprite sheet grid dimensions (from jaml-ui/src/sprites/spriteData.ts)
37
- window.SHEETS = {
38
- jokers: { src: 'assets/Jokers.png', cols: 10, rows: 16 },
39
- tarots: { src: 'assets/Tarots.png', cols: 10, rows: 6 },
40
- vouchers: { src: 'assets/Vouchers.png', cols: 9, rows: 4 },
41
- tags: { src: 'assets/tags.png', cols: 6, rows: 5 },
42
- boosters: { src: 'assets/Boosters.png', cols: 4, rows: 9 },
43
- blinds: { src: 'assets/BlindChips.png',cols: 21, rows: 31 },
44
- editions: { src: 'assets/Editions.png', cols: 5, rows: 1 },
45
- enhancers: { src: 'assets/Enhancers.png', cols: 7, rows: 5 },
46
- stickers: { src: 'assets/stickers.png', cols: 5, rows: 3 },
47
- deck: { src: 'assets/8BitDeck.png', cols: 13, rows: 4 },
48
- stakes: { src: 'assets/stakes.png', cols: 4, rows: 2 },
49
- };
@@ -1,290 +0,0 @@
1
- // AntePageV2 — per the reorg:
2
- //
3
- // ANTE n smTag bgTag BOSS ← header row, tags+boss right
4
- // ┌────┬──────────────────────────────────────┐
5
- // │ V │ pack0 pack2 pack4 │ ← voucher column (left), packs grid (2 rows × N cols)
6
- // │ │ pack1 pack3 pack5 │
7
- // ├────┴──────────────────────────────────────┤
8
- // │ SHOP ▸ [joker][joker][tarot]… (tape) │ ← infinite horizontal tape, edge fades
9
- // └───────────────────────────────────────────┘
10
- //
11
- // Ante 1 special: two shop tapes stacked, labeled "Round 1" / "Round 2" (since ante 1 starts at
12
- // Small Blind — you haven't earned a shop yet — so only 4 booster packs, but shop refreshes twice).
13
-
14
- const { useState: aUS, useRef: aUR, useEffect: aUE } = React;
15
-
16
- const C = window.JimboColor;
17
-
18
- // ── bestHit / hitColor ──
19
- const bestHit = (hits) => {
20
- if (!hits || !hits.length) return null;
21
- if (hits.some(h => h.kind === 'must')) return 'must';
22
- return 'should';
23
- };
24
- const hitColor = (k) => k === 'must' ? C.BLUE : k === 'should' ? C.RED : null;
25
-
26
- // ── Panel ─
27
- function Panel({ children, style, bg = C.DARK_GREY, edge = C.PANEL_EDGE }) {
28
- return (
29
- <div style={{
30
- background: bg,
31
- border: `2px solid ${edge}`,
32
- borderRadius: 6,
33
- boxShadow: `inset 0 0 0 1px rgba(255,255,255,0.04), 0 2px 0 ${C.BLACK}`,
34
- ...style,
35
- }}>{children}</div>
36
- );
37
- }
38
-
39
- // ── GlowRing ──
40
- function GlowRing({ kind, children }) {
41
- if (!kind) return children;
42
- const color = hitColor(kind);
43
- return (
44
- <div style={{ position: 'relative', display: 'inline-block' }}>
45
- <div style={{
46
- position: 'absolute', inset: -3, borderRadius: 6,
47
- boxShadow: `0 0 0 2px ${color}, 0 0 10px ${color}`,
48
- opacity: 0.8,
49
- animation: 'pulseGlow 1.6s ease-in-out infinite',
50
- pointerEvents: 'none',
51
- zIndex: 1,
52
- }} />
53
- <div style={{ position: 'relative', zIndex: 2 }}>{children}</div>
54
- </div>
55
- );
56
- }
57
-
58
- // Inject keyframes once
59
- (function injectKeyframes(){
60
- if (document.getElementById('v2-kf')) return;
61
- const s = document.createElement('style');
62
- s.id = 'v2-kf';
63
- s.textContent = `
64
- @keyframes pulseGlow { 0%,100% { opacity:.55;} 50% { opacity:1;} }
65
- .v2-shop-scroll::-webkit-scrollbar { display: none; }
66
- `;
67
- document.head.appendChild(s);
68
- })();
69
-
70
- // ── Tag chip small ──
71
- function MiniChip({ children, tight, color = C.WHITE }) {
72
- return (
73
- <div style={{
74
- fontFamily: 'm6x11plus, monospace', fontSize: 9, color,
75
- letterSpacing: 1, textTransform: 'uppercase',
76
- padding: tight ? '1px 3px' : '2px 4px',
77
- lineHeight: 1,
78
- }}>{children}</div>
79
- );
80
- }
81
-
82
- // ── HeaderTile — compact tag/boss tile for top-right row ──
83
- function HeaderTile({ kind, value, hit, label }) {
84
- const sz = 32;
85
- const inner =
86
- kind === 'boss' ? <BossChip name={value} size={sz} /> :
87
- <TagChip name={value} size={sz} />;
88
- return (
89
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
90
- <div style={{ width: 40, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
91
- <GlowRing kind={hit}>{inner}</GlowRing>
92
- </div>
93
- <MiniChip color={hit ? hitColor(hit) : C.GREY}>{label}</MiniChip>
94
- </div>
95
- );
96
- }
97
-
98
- // ── VoucherSquare — tall left column tile ──
99
- function VoucherSquare({ name, hit }) {
100
- return (
101
- <div style={{
102
- display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4,
103
- background: C.DARKEST, border: `2px solid ${C.PANEL_EDGE}`, borderRadius: 6,
104
- padding: '8px 6px', minWidth: 68,
105
- }}>
106
- <MiniChip color={hit ? hitColor(hit) : C.GREY}>V</MiniChip>
107
- {name ? <GlowRing kind={hit}><VoucherMini name={name} size={42} /></GlowRing>
108
- : <div style={{ width: 42, height: 42, opacity: 0.25, border: `1px dashed ${C.GREY}`, borderRadius: 4 }} />}
109
- <div style={{
110
- fontFamily: 'm6x11plus, monospace', fontSize: 9, color: C.WHITE,
111
- maxWidth: 70, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
112
- textAlign: 'center', letterSpacing: 0.5,
113
- }}>{name || '—'}</div>
114
- </div>
115
- );
116
- }
117
-
118
- // ── PackCell — tap to fan ──
119
- function PackCell({ pack, idx }) {
120
- const [open, setOpen] = aUS(false);
121
- const anyHit = pack.itemHits?.some(h => h.length) ? bestHit(pack.itemHits.flat()) : null;
122
- return (
123
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', minWidth: 0, position: 'relative' }}>
124
- <GlowRing kind={anyHit}>
125
- <PackSprite kind={pack.type.replace('pack','')} size={44} open={open} onClick={() => setOpen(o => !o)} />
126
- </GlowRing>
127
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 8, color: anyHit ? hitColor(anyHit) : C.GREY, letterSpacing: 1, marginTop: 2 }}>
128
- P{idx + 1}
129
- </div>
130
- {open && (
131
- <div style={{
132
- position: 'absolute', top: 50, left: '50%', transform: 'translateX(-50%)',
133
- display: 'flex', gap: 4, padding: '6px 8px',
134
- background: C.DARKEST, border: `2px solid ${C.PANEL_EDGE}`, borderRadius: 5,
135
- zIndex: 10, whiteSpace: 'nowrap',
136
- }}>
137
- {pack.items.map((it, i) => {
138
- const itemHit = bestHit(pack.itemHits?.[i]);
139
- return <GlowRing key={i} kind={itemHit}><JokerMini name={it} size={30} /></GlowRing>;
140
- })}
141
- </div>
142
- )}
143
- </div>
144
- );
145
- }
146
-
147
- // ── PackGrid — 2 rows × ceil(n/2) cols ──
148
- function PackGrid({ packs }) {
149
- const cols = Math.max(1, Math.ceil(packs.length / 2));
150
- return (
151
- <div style={{
152
- display: 'grid',
153
- gridTemplateColumns: `repeat(${cols}, auto)`,
154
- gridTemplateRows: 'repeat(2, auto)',
155
- gridAutoFlow: 'column',
156
- gap: '10px 12px',
157
- alignItems: 'start', justifyItems: 'center',
158
- }}>
159
- {packs.map((p, i) => <PackCell key={i} pack={p} idx={i} />)}
160
- </div>
161
- );
162
- }
163
-
164
- // ── ShopTape — horizontal grab-scroll with edge fades ──
165
- function ShopTape({ items, label = 'SHOP' }) {
166
- const ref = aUR(null);
167
- const drag = aUR({ down: false, x0: 0, sl0: 0 });
168
-
169
- const onDown = (e) => {
170
- const el = ref.current; if (!el) return;
171
- const x = e.touches ? e.touches[0].clientX : e.clientX;
172
- drag.current = { down: true, x0: x, sl0: el.scrollLeft };
173
- el.style.cursor = 'grabbing';
174
- };
175
- const onMove = (e) => {
176
- if (!drag.current.down) return;
177
- const el = ref.current; if (!el) return;
178
- const x = e.touches ? e.touches[0].clientX : e.clientX;
179
- el.scrollLeft = drag.current.sl0 - (x - drag.current.x0);
180
- };
181
- const onUp = () => { const el = ref.current; if (el) el.style.cursor = 'grab'; drag.current.down = false; };
182
-
183
- return (
184
- <div>
185
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
186
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 10, color: C.GREY, letterSpacing: 2 }}>{label} ▸</div>
187
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 10, color: C.GREY }}>{items.length}</div>
188
- </div>
189
- <Panel style={{ padding: '8px 0', position: 'relative' }}>
190
- <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, width: 24, background: `linear-gradient(90deg, ${C.DARK_GREY}, transparent)`, pointerEvents: 'none', zIndex: 2 }} />
191
- <div style={{ position: 'absolute', top: 0, bottom: 0, right: 0, width: 24, background: `linear-gradient(-90deg, ${C.DARK_GREY}, transparent)`, pointerEvents: 'none', zIndex: 2 }} />
192
- <div
193
- ref={ref}
194
- className="v2-shop-scroll"
195
- onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp} onMouseLeave={onUp}
196
- onTouchStart={onDown} onTouchMove={onMove} onTouchEnd={onUp}
197
- style={{
198
- overflowX: 'auto', overflowY: 'hidden',
199
- display: 'flex', gap: 8, padding: '2px 18px',
200
- cursor: 'grab', userSelect: 'none', scrollbarWidth: 'none',
201
- }}
202
- >
203
- {items.map((item, i) => {
204
- const hit = bestHit(item.hits);
205
- return (
206
- <div key={i} style={{ flexShrink: 0, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
207
- <GlowRing kind={hit}>
208
- <JokerMini name={item.value} size={50} edition={item.edition} />
209
- </GlowRing>
210
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 8, color: hit ? hitColor(hit) : C.GREY, letterSpacing: 1, marginTop: 2 }}>
211
- #{i + 1}
212
- </div>
213
- </div>
214
- );
215
- })}
216
- </div>
217
- </Panel>
218
- </div>
219
- );
220
- }
221
-
222
- // ── AntePageV2 ───────
223
- function AntePageV2({ ante }) {
224
- const voucherHit = bestHit(ante._voucherHits);
225
- const smallHit = bestHit(ante._smallTagHits);
226
- const bigHit = bestHit(ante._bigTagHits);
227
- const bossHit = bestHit(ante._bossHits);
228
- const soulHit = bestHit(ante._soulHits);
229
-
230
- // Ante 1 quirk: split shopQueue into two rounds for display
231
- const isAnte1 = ante.ante === 1;
232
- const shopRounds = isAnte1
233
- ? [
234
- { label: 'SHOP · ROUND 1', items: ante.shopQueue.slice(0, Math.ceil(ante.shopQueue.length / 2)) },
235
- { label: 'SHOP · ROUND 2', items: ante.shopQueue.slice(Math.ceil(ante.shopQueue.length / 2)) },
236
- ]
237
- : [{ label: 'SHOP', items: ante.shopQueue }];
238
-
239
- return (
240
- <div style={{ padding: '12px 10px 32px', display: 'flex', flexDirection: 'column', gap: 10 }}>
241
- {/* Header row: ANTE n smTag bgTag Boss */}
242
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
243
- <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
244
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 10, color: C.GREY, letterSpacing: 2 }}>ANTE</div>
245
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 26, color: C.WHITE, lineHeight: 1 }}>{ante.ante}</div>
246
- </div>
247
- <div style={{ display: 'flex', gap: 8 }}>
248
- <HeaderTile kind="smalltag" value={ante.smallBlindTag} hit={smallHit} label="SMALL" />
249
- <HeaderTile kind="bigtag" value={ante.bigBlindTag} hit={bigHit} label="BIG" />
250
- <HeaderTile kind="boss" value={ante.boss} hit={bossHit} label="BOSS" />
251
- </div>
252
- </div>
253
-
254
- {/* Voucher + Packs row */}
255
- <Panel style={{ padding: 10 }}>
256
- <div style={{ display: 'flex', gap: 10, alignItems: 'stretch' }}>
257
- <VoucherSquare name={ante.voucher} hit={voucherHit} />
258
- <div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
259
- {ante.boosterPacks?.length > 0
260
- ? <PackGrid packs={ante.boosterPacks} />
261
- : <div style={{ color: C.GREY, fontSize: 10, fontFamily: 'm6x11plus, monospace' }}>NO PACKS</div>}
262
- </div>
263
- </div>
264
- </Panel>
265
-
266
- {/* Shop tape(s) */}
267
- {shopRounds.map((r, i) => (
268
- <ShopTape key={i} items={r.items} label={r.label} />
269
- ))}
270
-
271
- {/* Soul joker callout */}
272
- {ante.soulJoker && (
273
- <Panel style={{ padding: 8, borderColor: soulHit === 'must' ? C.GOLD : C.DARK_PURPLE }}>
274
- <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
275
- <GlowRing kind={soulHit}><JokerMini name={ante.soulJoker.value} size={44} edition={ante.soulJoker.edition} /></GlowRing>
276
- <div>
277
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 9, color: C.GOLD_TEXT, letterSpacing: 2 }}>SOUL JOKER</div>
278
- <div style={{ fontFamily: 'm6x11plus, monospace', fontSize: 14, color: C.WHITE }}>{ante.soulJoker.value}</div>
279
- </div>
280
- </div>
281
- </Panel>
282
- )}
283
- </div>
284
- );
285
- }
286
-
287
- window.AntePageV2 = AntePageV2;
288
- window.GlowRing = GlowRing;
289
- window.bestHit = bestHit;
290
- window.hitColor = hitColor;