@rpg-engine/long-bow 0.8.182 → 0.8.184

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 (29) hide show
  1. package/dist/components/Marketplace/CharacterListingForm.d.ts +4 -2
  2. package/dist/components/Marketplace/CharacterListingModal.d.ts +17 -0
  3. package/dist/components/Marketplace/CharacterMarketplacePanel.d.ts +4 -0
  4. package/dist/components/Marketplace/CharacterMarketplaceRows.d.ts +6 -0
  5. package/dist/components/Marketplace/Marketplace.d.ts +3 -2
  6. package/dist/components/Marketplace/MyCharacterListingsPanel.d.ts +4 -0
  7. package/dist/components/shared/DCRateStrip.d.ts +2 -0
  8. package/dist/components/shared/RadioOption.d.ts +22 -0
  9. package/dist/long-bow.cjs.development.js +476 -371
  10. package/dist/long-bow.cjs.development.js.map +1 -1
  11. package/dist/long-bow.cjs.production.min.js +1 -1
  12. package/dist/long-bow.cjs.production.min.js.map +1 -1
  13. package/dist/long-bow.esm.js +477 -372
  14. package/dist/long-bow.esm.js.map +1 -1
  15. package/dist/stories/Features/marketplace/CharacterListingModal.stories.d.ts +8 -0
  16. package/dist/stories/shared/RadioOption.stories.d.ts +8 -0
  17. package/package.json +1 -1
  18. package/src/components/DCWallet/DCWalletContent.tsx +5 -47
  19. package/src/components/Marketplace/CharacterListingForm.tsx +56 -351
  20. package/src/components/Marketplace/CharacterListingModal.tsx +404 -0
  21. package/src/components/Marketplace/CharacterMarketplacePanel.tsx +112 -67
  22. package/src/components/Marketplace/CharacterMarketplaceRows.tsx +21 -9
  23. package/src/components/Marketplace/Marketplace.tsx +11 -6
  24. package/src/components/Marketplace/MyCharacterListingsPanel.tsx +15 -14
  25. package/src/components/shared/DCRateStrip.tsx +67 -0
  26. package/src/components/shared/RadioOption.tsx +93 -0
  27. package/src/stories/Features/marketplace/CharacterListingModal.stories.tsx +131 -0
  28. package/src/stories/Features/marketplace/CharacterMarketplace.stories.tsx +39 -23
  29. package/src/stories/shared/RadioOption.stories.tsx +93 -0
@@ -0,0 +1,404 @@
1
+ import { ICharacter } from '@rpg-engine/shared';
2
+ import { ShoppingBag } from 'pixelarticons/react/ShoppingBag';
3
+ import React, { useCallback, useState } from 'react';
4
+ import { FaTimes } from 'react-icons/fa';
5
+ import styled, { keyframes } from 'styled-components';
6
+ import ModalPortal from '../Abstractions/ModalPortal';
7
+ import { ConfirmModal } from '../ConfirmModal';
8
+ import { Input } from '../Input';
9
+ import { CTAButton } from '../shared/CTAButton/CTAButton';
10
+ import { DCRateStrip } from '../shared/DCRateStrip';
11
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
12
+
13
+ export interface ICharacterListingModalProps {
14
+ isOpen: boolean;
15
+ onClose: () => void;
16
+ accountCharacters: ICharacter[];
17
+ /** Items atlas — for UI sprites like the DC coin */
18
+ atlasJSON: any;
19
+ atlasIMG: any;
20
+ /** Entities atlas — for character sprites */
21
+ characterAtlasJSON: any;
22
+ characterAtlasIMG: any;
23
+ onCharacterList: (characterId: string, price: number) => void;
24
+ enableHotkeys?: () => void;
25
+ disableHotkeys?: () => void;
26
+ }
27
+
28
+ export const CharacterListingModal: React.FC<ICharacterListingModalProps> = ({
29
+ isOpen,
30
+ onClose,
31
+ accountCharacters,
32
+ atlasJSON,
33
+ atlasIMG,
34
+ characterAtlasJSON,
35
+ characterAtlasIMG,
36
+ onCharacterList,
37
+ enableHotkeys,
38
+ disableHotkeys,
39
+ }) => {
40
+ const [selectedId, setSelectedId] = useState<string | null>(null);
41
+ const [price, setPrice] = useState('');
42
+ const [isConfirming, setIsConfirming] = useState(false);
43
+
44
+ if (!isOpen) return null;
45
+
46
+ const eligibleCharacters = accountCharacters.filter(
47
+ c => !c.isListedForSale && !c.tradedAt
48
+ );
49
+
50
+ const canList = !!selectedId && Number(price) > 0;
51
+
52
+ const handleClose = () => {
53
+ setSelectedId(null);
54
+ setPrice('');
55
+ enableHotkeys?.();
56
+ onClose();
57
+ };
58
+
59
+ const handleListClick = () => {
60
+ if (canList) setIsConfirming(true);
61
+ };
62
+
63
+ const handleConfirm = () => {
64
+ if (!selectedId || !canList) return;
65
+ onCharacterList(selectedId, Number(price));
66
+ setSelectedId(null);
67
+ setPrice('');
68
+ setIsConfirming(false);
69
+ enableHotkeys?.();
70
+ onClose();
71
+ };
72
+
73
+ const stopPropagation = useCallback(
74
+ (e: React.MouseEvent | React.TouchEvent | React.PointerEvent) => {
75
+ e.stopPropagation();
76
+ },
77
+ []
78
+ );
79
+
80
+ const getLevel = (c: ICharacter) => c.skills?.level || 1;
81
+
82
+ return (
83
+ <ModalPortal>
84
+ {isConfirming && (
85
+ <ConfirmModal
86
+ onConfirm={handleConfirm}
87
+ onClose={() => { setIsConfirming(false); enableHotkeys?.(); }}
88
+ message="Are you sure you want to list this character for sale?"
89
+ />
90
+ )}
91
+
92
+ <Overlay onPointerDown={handleClose} />
93
+ <ModalContainer>
94
+ <ModalContent
95
+ onClick={stopPropagation as React.MouseEventHandler}
96
+ onTouchStart={stopPropagation as React.TouchEventHandler}
97
+ onPointerDown={stopPropagation as React.PointerEventHandler}
98
+ >
99
+ <Header>
100
+ <Title>List Character for Sale</Title>
101
+ <CloseButton onPointerDown={handleClose} aria-label="Close" type="button">
102
+ <FaTimes />
103
+ </CloseButton>
104
+ </Header>
105
+
106
+ <CharacterList>
107
+ {eligibleCharacters.length === 0 ? (
108
+ <EmptyState>No eligible characters to list.</EmptyState>
109
+ ) : (
110
+ eligibleCharacters.map(character => {
111
+ const isSelected = selectedId === character._id;
112
+ return (
113
+ <CharacterRow
114
+ key={character._id}
115
+ $selected={isSelected}
116
+ onPointerDown={() => setSelectedId(character._id!)}
117
+ role="radio"
118
+ aria-checked={isSelected}
119
+ tabIndex={0}
120
+ onKeyDown={e => {
121
+ if (e.key === 'Enter' || e.key === ' ') {
122
+ e.preventDefault();
123
+ setSelectedId(character._id!);
124
+ }
125
+ }}
126
+ >
127
+ <RadioCircle $selected={isSelected} />
128
+ <SpriteWrapper>
129
+ <SpriteFromAtlas
130
+ atlasIMG={characterAtlasIMG}
131
+ atlasJSON={characterAtlasJSON}
132
+ spriteKey={`${character.textureKey}/down/standing/0.png`}
133
+ imgScale={2}
134
+ height={40}
135
+ width={40}
136
+ />
137
+ </SpriteWrapper>
138
+ <CharacterInfo>
139
+ <CharacterName>{character.name || 'Unknown'}</CharacterName>
140
+ <CharacterMeta>Level {getLevel(character)}</CharacterMeta>
141
+ </CharacterInfo>
142
+ </CharacterRow>
143
+ );
144
+ })
145
+ )}
146
+ </CharacterList>
147
+
148
+ <PriceSection>
149
+ <PriceLabel>Set Price (DC)</PriceLabel>
150
+ <PriceRow>
151
+ <Input
152
+ onChange={e => setPrice(e.target.value)}
153
+ value={price}
154
+ placeholder="Amount..."
155
+ type="number"
156
+ disabled={eligibleCharacters.length === 0}
157
+ onFocus={disableHotkeys}
158
+ onBlur={enableHotkeys}
159
+ className="price-input"
160
+ />
161
+ <ListBtn
162
+ icon={<ShoppingBag width={18} height={18} />}
163
+ label="List"
164
+ disabled={!canList}
165
+ onClick={handleListClick}
166
+ />
167
+ </PriceRow>
168
+ {price && Number(price) > 0 && (
169
+ <PricePreview>
170
+ <DCCoinWrapper>
171
+ <SpriteFromAtlas
172
+ atlasIMG={atlasIMG}
173
+ atlasJSON={atlasJSON}
174
+ spriteKey="others/definya-coin.png"
175
+ imgScale={1}
176
+ />
177
+ </DCCoinWrapper>
178
+ <PricePreviewAmount>{Number(price).toLocaleString()} DC</PricePreviewAmount>
179
+ </PricePreview>
180
+ )}
181
+ <DCRateStrip />
182
+ </PriceSection>
183
+ </ModalContent>
184
+ </ModalContainer>
185
+ </ModalPortal>
186
+ );
187
+ };
188
+
189
+ const scaleIn = keyframes`
190
+ from { transform: scale(0.85); opacity: 0; }
191
+ to { transform: scale(1); opacity: 1; }
192
+ `;
193
+
194
+ const Overlay = styled.div`
195
+ position: fixed;
196
+ inset: 0;
197
+ background: rgba(0, 0, 0, 0.7);
198
+ z-index: 1000;
199
+ `;
200
+
201
+ const ModalContainer = styled.div`
202
+ position: fixed;
203
+ inset: 0;
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ z-index: 1001;
208
+ pointer-events: none;
209
+ `;
210
+
211
+ const ModalContent = styled.div`
212
+ background: #1a1a2e;
213
+ border: 2px solid #f59e0b;
214
+ border-radius: 8px;
215
+ padding: 20px 24px;
216
+ width: 440px;
217
+ max-width: 92%;
218
+ max-height: 80dvh;
219
+ display: flex;
220
+ flex-direction: column;
221
+ gap: 16px;
222
+ overflow: hidden;
223
+ pointer-events: auto;
224
+ animation: ${scaleIn} 0.15s ease-out;
225
+ `;
226
+
227
+ const Header = styled.div`
228
+ display: flex;
229
+ align-items: center;
230
+ justify-content: space-between;
231
+ `;
232
+
233
+ const Title = styled.h3`
234
+ margin: 0;
235
+ font-family: 'Press Start 2P', cursive !important;
236
+ font-size: 0.65rem !important;
237
+ color: #fef08a !important;
238
+ `;
239
+
240
+ const CloseButton = styled.button`
241
+ background: none;
242
+ border: none;
243
+ color: rgba(255, 255, 255, 0.6);
244
+ cursor: pointer;
245
+ font-size: 1rem;
246
+ padding: 4px;
247
+ display: flex;
248
+ align-items: center;
249
+ pointer-events: auto;
250
+
251
+ &:hover { color: #ffffff; }
252
+ `;
253
+
254
+ const CharacterList = styled.div`
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 6px;
258
+ overflow-y: auto;
259
+ max-height: 260px;
260
+ padding: 2px;
261
+
262
+ &::-webkit-scrollbar { width: 6px; }
263
+ &::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; }
264
+ &::-webkit-scrollbar-thumb { background: rgba(245,158,11,0.3); border-radius: 4px; }
265
+ `;
266
+
267
+ const CharacterRow = styled.div<{ $selected: boolean }>`
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 12px;
271
+ padding: 10px 12px;
272
+ border: 1px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.08)')};
273
+ border-radius: 6px;
274
+ background: ${({ $selected }) => ($selected ? 'rgba(245,158,11,0.1)' : 'rgba(255,255,255,0.02)')};
275
+ cursor: pointer;
276
+ transition: border-color 0.15s, background 0.15s;
277
+
278
+ &:hover {
279
+ border-color: ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(245,158,11,0.4)')};
280
+ background: ${({ $selected }) => ($selected ? 'rgba(245,158,11,0.1)' : 'rgba(245,158,11,0.05)')};
281
+ }
282
+
283
+ &:focus-visible {
284
+ outline: 2px solid rgba(245,158,11,0.6);
285
+ outline-offset: 2px;
286
+ }
287
+ `;
288
+
289
+ const RadioCircle = styled.div<{ $selected: boolean }>`
290
+ width: 14px;
291
+ height: 14px;
292
+ border-radius: 50%;
293
+ border: 2px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.4)')};
294
+ flex-shrink: 0;
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+
299
+ &::after {
300
+ content: '';
301
+ width: 6px;
302
+ height: 6px;
303
+ border-radius: 50%;
304
+ background: #f59e0b;
305
+ opacity: ${({ $selected }) => ($selected ? 1 : 0)};
306
+ transition: opacity 0.15s;
307
+ }
308
+ `;
309
+
310
+ const SpriteWrapper = styled.div`
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ image-rendering: pixelated;
315
+ flex-shrink: 0;
316
+ width: 40px;
317
+ height: 40px;
318
+ `;
319
+
320
+ const CharacterInfo = styled.div`
321
+ display: flex;
322
+ flex-direction: column;
323
+ gap: 4px;
324
+ flex: 1;
325
+ `;
326
+
327
+ const CharacterName = styled.span`
328
+ font-family: 'Press Start 2P', cursive !important;
329
+ font-size: 0.5rem !important;
330
+ color: #f3f4f6 !important;
331
+ `;
332
+
333
+ const CharacterMeta = styled.span`
334
+ font-family: 'Press Start 2P', cursive !important;
335
+ font-size: 0.4rem !important;
336
+ color: #888 !important;
337
+ text-transform: uppercase;
338
+ letter-spacing: 0.5px;
339
+ `;
340
+
341
+ const PricePreview = styled.div`
342
+ display: flex;
343
+ align-items: center;
344
+ gap: 6px;
345
+ `;
346
+
347
+ const DCCoinWrapper = styled.span`
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ flex-shrink: 0;
352
+ `;
353
+
354
+ const PricePreviewAmount = styled.span`
355
+ font-family: 'Press Start 2P', cursive !important;
356
+ font-size: 0.55rem !important;
357
+ color: #fef08a !important;
358
+ `;
359
+
360
+ const PriceSection = styled.div`
361
+ display: flex;
362
+ flex-direction: column;
363
+ gap: 8px;
364
+ border-top: 1px solid rgba(255,255,255,0.06);
365
+ padding-top: 12px;
366
+ `;
367
+
368
+ const PriceLabel = styled.p`
369
+ margin: 0;
370
+ font-family: 'Press Start 2P', cursive !important;
371
+ font-size: 0.55rem !important;
372
+ color: #888 !important;
373
+ text-transform: uppercase;
374
+ letter-spacing: 0.5px;
375
+ `;
376
+
377
+ const PriceRow = styled.div`
378
+ display: flex;
379
+ gap: 8px;
380
+ align-items: center;
381
+
382
+ .price-input { flex: 1; height: 12px; }
383
+ `;
384
+
385
+ const ListBtn = styled(CTAButton)`
386
+ flex-shrink: 0;
387
+ padding: 10px 16px;
388
+ height: 32px;
389
+
390
+ span { font-size: 0.6rem; }
391
+ svg { font-size: 1.1rem; }
392
+ `;
393
+
394
+ const EmptyState = styled.div`
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ padding: 32px 16px;
399
+ font-family: 'Press Start 2P', cursive !important;
400
+ font-size: 0.5rem !important;
401
+ color: #666 !important;
402
+ text-transform: uppercase;
403
+ letter-spacing: 1px;
404
+ `;
@@ -1,10 +1,11 @@
1
- import { ICharacterListing, ICharacterListingSnapshot } from '@rpg-engine/shared';
1
+ import { formatDCAmount, ICharacterListing, ICharacterListingSnapshot } from '@rpg-engine/shared';
2
2
  import { User } from 'pixelarticons/react/User';
3
3
  import React, { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import styled, { keyframes } from 'styled-components';
5
5
  import { Input } from '../Input';
6
- import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
7
6
  import { ConfirmModal } from '../ConfirmModal';
7
+ import { Pagination } from '../shared/Pagination/Pagination';
8
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
8
9
 
9
10
  export interface ICharacterMarketplacePanelProps {
10
11
  characterListings: ICharacterListing[];
@@ -13,8 +14,12 @@ export interface ICharacterMarketplacePanelProps {
13
14
  itemsPerPage: number;
14
15
  onPageChange: (page: number) => void;
15
16
  onCharacterBuy: (listingId: string) => void;
17
+ /** Items atlas — for UI sprites like the DC coin */
16
18
  atlasJSON: any;
17
19
  atlasIMG: any;
20
+ /** Entities atlas — for character sprites */
21
+ characterAtlasJSON: any;
22
+ characterAtlasIMG: any;
18
23
  enableHotkeys?: () => void;
19
24
  disableHotkeys?: () => void;
20
25
  nameFilter?: string;
@@ -31,6 +36,8 @@ export const CharacterMarketplacePanel: React.FC<ICharacterMarketplacePanelProps
31
36
  onCharacterBuy,
32
37
  atlasJSON,
33
38
  atlasIMG,
39
+ characterAtlasJSON,
40
+ characterAtlasIMG,
34
41
  enableHotkeys,
35
42
  disableHotkeys,
36
43
  nameFilter = '',
@@ -76,15 +83,12 @@ export const CharacterMarketplacePanel: React.FC<ICharacterMarketplacePanelProps
76
83
  }
77
84
  };
78
85
 
79
- const getListingPrice = (listing: ICharacterListing): string => {
80
- return listing.price.toLocaleString();
81
- };
82
86
 
83
87
  const renderCharacterSprite = (snapshot: ICharacterListingSnapshot) => {
84
88
  return (
85
89
  <SpriteFromAtlas
86
- atlasIMG={atlasIMG}
87
- atlasJSON={atlasJSON}
90
+ atlasIMG={characterAtlasIMG}
91
+ atlasJSON={characterAtlasJSON}
88
92
  spriteKey={`${snapshot.textureKey}/down/standing/0.png`}
89
93
  imgScale={3}
90
94
  height={64}
@@ -144,19 +148,34 @@ export const CharacterMarketplacePanel: React.FC<ICharacterMarketplacePanelProps
144
148
  <CharacterInfo>
145
149
  <CharacterName>{listing.characterSnapshot.name || 'Unknown'}</CharacterName>
146
150
  <CharacterMeta>
147
- Level {listing.characterSnapshot.level} · {listing.characterSnapshot.class}
151
+ Lv.{listing.characterSnapshot.level} · {listing.characterSnapshot.class}
148
152
  </CharacterMeta>
153
+ <CharacterDetails>
154
+ {listing.characterSnapshot.race} · {listing.characterSnapshot.faction}
155
+ </CharacterDetails>
156
+ <ModeBadge $hardcore={listing.characterSnapshot.mode?.toLowerCase() === 'hardcore'}>
157
+ {listing.characterSnapshot.mode || 'Standard'}
158
+ </ModeBadge>
159
+ {listing.characterSnapshot.equipment?.length > 0 && (
160
+ <EquipmentRow>
161
+ {listing.characterSnapshot.equipment.slice(0, 3).map((eq, i) => (
162
+ <EquipBadge key={i} $rarity={eq.rarity}>
163
+ {eq.rarity || 'Common'}
164
+ </EquipBadge>
165
+ ))}
166
+ </EquipmentRow>
167
+ )}
149
168
  <SellerInfo>by {listing.listedByCharacterName}</SellerInfo>
150
169
  <ListingPrice>
151
- <GoldIcon>
170
+ <DCCoinWrapper>
152
171
  <SpriteFromAtlas
153
172
  atlasIMG={atlasIMG}
154
173
  atlasJSON={atlasJSON}
155
- spriteKey="others/gold-coin-qty-5.png"
174
+ spriteKey="others/definya-coin.png"
156
175
  imgScale={1}
157
176
  />
158
- </GoldIcon>
159
- {getListingPrice(listing)} Gold
177
+ </DCCoinWrapper>
178
+ {formatDCAmount(listing.price)} DC
160
179
  </ListingPrice>
161
180
  </CharacterInfo>
162
181
  {listing.isBeingBought && (
@@ -170,18 +189,11 @@ export const CharacterMarketplacePanel: React.FC<ICharacterMarketplacePanelProps
170
189
 
171
190
  {totalCount > itemsPerPage && (
172
191
  <PagerFooter>
173
- <Pagination>
174
- {Array.from({ length: Math.ceil(totalCount / itemsPerPage) }, (_, i) => i + 1).map(page => (
175
- <PageButton
176
- key={page}
177
- $active={currentPage === page}
178
- type="button"
179
- onClick={() => onPageChange(page)}
180
- >
181
- {page}
182
- </PageButton>
183
- ))}
184
- </Pagination>
192
+ <Pagination
193
+ currentPage={currentPage}
194
+ totalPages={Math.ceil(totalCount / itemsPerPage)}
195
+ onPageChange={onPageChange}
196
+ />
185
197
  </PagerFooter>
186
198
  )}
187
199
  </>
@@ -261,6 +273,9 @@ const CharacterSprite = styled.div`
261
273
  align-items: center;
262
274
  justify-content: center;
263
275
  image-rendering: pixelated;
276
+ width: 64px;
277
+ height: 64px;
278
+ flex-shrink: 0;
264
279
  `;
265
280
 
266
281
  const CharacterInfo = styled.div`
@@ -269,26 +284,74 @@ const CharacterInfo = styled.div`
269
284
  align-items: center;
270
285
  gap: 4px;
271
286
  text-align: center;
287
+ width: 100%;
272
288
  `;
273
289
 
274
290
  const CharacterName = styled.span`
275
- font-family: 'Press Start 2P', cursive;
276
- font-size: 0.55rem;
277
- color: #f3f4f6;
291
+ font-family: 'Press Start 2P', cursive !important;
292
+ font-size: 0.55rem !important;
293
+ color: #f3f4f6 !important;
278
294
  text-transform: uppercase;
279
295
  letter-spacing: 0.5px;
280
296
  `;
281
297
 
282
298
  const CharacterMeta = styled.span`
283
- font-size: 0.45rem;
284
- color: #888;
299
+ font-family: 'Press Start 2P', cursive !important;
300
+ font-size: 0.45rem !important;
301
+ color: #888 !important;
302
+ text-transform: uppercase;
303
+ letter-spacing: 0.5px;
304
+ `;
305
+
306
+ const CharacterDetails = styled.span`
307
+ font-family: 'Press Start 2P', cursive !important;
308
+ font-size: 0.38rem !important;
309
+ color: #9ca3af !important;
285
310
  text-transform: uppercase;
286
311
  letter-spacing: 0.5px;
287
312
  `;
288
313
 
314
+ const RARITY_COLORS: Record<string, string> = {
315
+ legendary: '#f59e0b',
316
+ epic: '#a855f7',
317
+ rare: '#3b82f6',
318
+ uncommon: '#22c55e',
319
+ common: '#6b7280',
320
+ };
321
+
322
+ const ModeBadge = styled.span<{ $hardcore?: boolean }>`
323
+ font-family: 'Press Start 2P', cursive !important;
324
+ font-size: 0.32rem !important;
325
+ color: ${({ $hardcore }) => ($hardcore ? '#ef4444' : '#6b7280')} !important;
326
+ border: 1px solid ${({ $hardcore }) => ($hardcore ? 'rgba(239,68,68,0.4)' : 'rgba(107,114,128,0.3)')};
327
+ border-radius: 3px;
328
+ padding: 1px 4px;
329
+ text-transform: uppercase;
330
+ letter-spacing: 0.5px;
331
+ `;
332
+
333
+ const EquipmentRow = styled.div`
334
+ display: flex;
335
+ flex-wrap: wrap;
336
+ gap: 3px;
337
+ justify-content: center;
338
+ `;
339
+
340
+ const EquipBadge = styled.span<{ $rarity?: string }>`
341
+ font-family: 'Press Start 2P', cursive !important;
342
+ font-size: 0.3rem !important;
343
+ color: ${({ $rarity }) => RARITY_COLORS[($rarity ?? '').toLowerCase()] ?? RARITY_COLORS.common} !important;
344
+ border: 1px solid ${({ $rarity }) => RARITY_COLORS[($rarity ?? '').toLowerCase()] ?? RARITY_COLORS.common}44;
345
+ border-radius: 2px;
346
+ padding: 1px 3px;
347
+ text-transform: uppercase;
348
+ letter-spacing: 0.3px;
349
+ `;
350
+
289
351
  const SellerInfo = styled.span`
290
- font-size: 0.4rem;
291
- color: #666;
352
+ font-family: 'Press Start 2P', cursive !important;
353
+ font-size: 0.4rem !important;
354
+ color: #666 !important;
292
355
  text-transform: uppercase;
293
356
  letter-spacing: 0.5px;
294
357
  `;
@@ -296,19 +359,20 @@ const SellerInfo = styled.span`
296
359
  const ListingPrice = styled.div`
297
360
  display: flex;
298
361
  align-items: center;
299
- gap: 6px;
300
- font-family: 'Press Start 2P', cursive;
301
- font-size: 0.5rem;
302
- color: #fef08a;
362
+ justify-content: center;
363
+ gap: 4px;
364
+ font-family: 'Press Start 2P', cursive !important;
365
+ font-size: 0.5rem !important;
366
+ color: #fef08a !important;
367
+ line-height: 1;
303
368
  `;
304
369
 
305
- const GoldIcon = styled.span`
370
+ const DCCoinWrapper = styled.span`
306
371
  display: flex;
307
372
  align-items: center;
308
373
  justify-content: center;
309
- position: relative;
310
- top: -0.4rem;
311
- left: -0.3rem;
374
+ flex-shrink: 0;
375
+ line-height: 0;
312
376
  `;
313
377
 
314
378
  const BeingBoughtBadge = styled.span`
@@ -319,8 +383,9 @@ const BeingBoughtBadge = styled.span`
319
383
  border: 1px solid rgba(239, 68, 68, 0.4);
320
384
  border-radius: 4px;
321
385
  padding: 2px 6px;
322
- font-size: 0.35rem;
323
- color: #ef4444;
386
+ font-family: 'Press Start 2P', cursive !important;
387
+ font-size: 0.35rem !important;
388
+ color: #ef4444 !important;
324
389
  text-transform: uppercase;
325
390
  letter-spacing: 0.5px;
326
391
  `;
@@ -335,28 +400,6 @@ const PagerFooter = styled.div`
335
400
  margin: 0 auto;
336
401
  `;
337
402
 
338
- const Pagination = styled.div`
339
- display: flex;
340
- gap: 6px;
341
- `;
342
-
343
- const PageButton = styled.button<{ $active: boolean }>`
344
- padding: 6px 10px;
345
- font-family: 'Press Start 2P', cursive;
346
- font-size: 0.5rem;
347
- border-radius: 4px;
348
- border: 1px solid ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.12)')};
349
- background: ${({ $active }) => ($active ? 'rgba(245,158,11,0.15)' : 'rgba(0,0,0,0.3)')};
350
- color: ${({ $active }) => ($active ? '#f59e0b' : '#777')};
351
- cursor: pointer;
352
- transition: border-color 0.15s, background 0.15s, color 0.15s;
353
-
354
- &:hover {
355
- border-color: ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.3)')};
356
- color: ${({ $active }) => ($active ? '#f59e0b' : '#bbb')};
357
- }
358
- `;
359
-
360
403
  const LoadingState = styled.div`
361
404
  display: flex;
362
405
  flex-direction: column;
@@ -381,8 +424,9 @@ const Spinner = styled.div`
381
424
  `;
382
425
 
383
426
  const LoadingText = styled.span`
384
- font-size: 0.48rem;
385
- color: #8a8a8a;
427
+ font-family: 'Press Start 2P', cursive !important;
428
+ font-size: 0.48rem !important;
429
+ color: #8a8a8a !important;
386
430
  text-transform: uppercase;
387
431
  letter-spacing: 1px;
388
432
  `;
@@ -398,8 +442,9 @@ const EmptyState = styled.div`
398
442
  `;
399
443
 
400
444
  const EmptyText = styled.span`
401
- font-size: 0.48rem;
402
- color: #71717a;
445
+ font-family: 'Press Start 2P', cursive !important;
446
+ font-size: 0.48rem !important;
447
+ color: #71717a !important;
403
448
  text-transform: uppercase;
404
449
  letter-spacing: 1px;
405
450
  `;