@rpg-engine/long-bow 0.7.97 → 0.7.98

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 (53) hide show
  1. package/dist/components/DPad/JoystickDPad.d.ts +21 -0
  2. package/dist/components/InformationCenter/InformationCenter.d.ts +29 -0
  3. package/dist/components/InformationCenter/InformationCenterCell.d.ts +14 -0
  4. package/dist/components/InformationCenter/InformationCenterTabView.d.ts +19 -0
  5. package/dist/components/InformationCenter/InformationCenterTypes.d.ts +79 -0
  6. package/dist/components/InformationCenter/sections/bestiary/BestiarySection.d.ts +12 -0
  7. package/dist/components/InformationCenter/sections/bestiary/InformationCenterNPCDetails.d.ts +12 -0
  8. package/dist/components/InformationCenter/sections/bestiary/InformationCenterNPCTooltip.d.ts +9 -0
  9. package/dist/components/InformationCenter/sections/faq/FaqSection.d.ts +8 -0
  10. package/dist/components/InformationCenter/sections/items/InformationCenterItemDetails.d.ts +11 -0
  11. package/dist/components/InformationCenter/sections/items/InformationCenterItemTooltip.d.ts +7 -0
  12. package/dist/components/InformationCenter/sections/items/ItemsSection.d.ts +11 -0
  13. package/dist/components/InformationCenter/sections/tutorials/TutorialsSection.d.ts +8 -0
  14. package/dist/components/InformationCenter/shared/BaseInformationDetails.d.ts +10 -0
  15. package/dist/components/shared/BaseTooltip.d.ts +12 -0
  16. package/dist/components/shared/Collapsible/Collapsible.d.ts +9 -0
  17. package/dist/components/shared/Portal/Portal.d.ts +6 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/long-bow.cjs.development.js +243 -5
  20. package/dist/long-bow.cjs.development.js.map +1 -1
  21. package/dist/long-bow.cjs.production.min.js +1 -1
  22. package/dist/long-bow.cjs.production.min.js.map +1 -1
  23. package/dist/long-bow.esm.js +244 -7
  24. package/dist/long-bow.esm.js.map +1 -1
  25. package/dist/mocks/informationCenter.mocks.d.ts +6 -0
  26. package/dist/stories/Features/craftbook/CraftBook.stories.d.ts +2 -0
  27. package/dist/stories/UI/info/InformationCenter.stories.d.ts +7 -0
  28. package/dist/stories/UI/joystick/JoystickDPad.stories.d.ts +6 -0
  29. package/package.json +1 -1
  30. package/src/components/CraftBook/CraftBook.tsx +70 -31
  31. package/src/components/DPad/JoystickDPad.tsx +318 -0
  32. package/src/components/InformationCenter/InformationCenter.tsx +155 -0
  33. package/src/components/InformationCenter/InformationCenterCell.tsx +96 -0
  34. package/src/components/InformationCenter/InformationCenterTabView.tsx +121 -0
  35. package/src/components/InformationCenter/InformationCenterTypes.ts +87 -0
  36. package/src/components/InformationCenter/sections/bestiary/BestiarySection.tsx +170 -0
  37. package/src/components/InformationCenter/sections/bestiary/InformationCenterNPCDetails.tsx +366 -0
  38. package/src/components/InformationCenter/sections/bestiary/InformationCenterNPCTooltip.tsx +204 -0
  39. package/src/components/InformationCenter/sections/faq/FaqSection.tsx +71 -0
  40. package/src/components/InformationCenter/sections/items/InformationCenterItemDetails.tsx +323 -0
  41. package/src/components/InformationCenter/sections/items/InformationCenterItemTooltip.tsx +88 -0
  42. package/src/components/InformationCenter/sections/items/ItemsSection.tsx +180 -0
  43. package/src/components/InformationCenter/sections/tutorials/TutorialsSection.tsx +144 -0
  44. package/src/components/InformationCenter/shared/BaseInformationDetails.tsx +162 -0
  45. package/src/components/InternalTabs/InternalTabs.tsx +1 -3
  46. package/src/components/shared/BaseTooltip.tsx +60 -0
  47. package/src/components/shared/Collapsible/Collapsible.tsx +70 -0
  48. package/src/components/shared/Portal/Portal.tsx +19 -0
  49. package/src/index.tsx +1 -0
  50. package/src/mocks/informationCenter.mocks.ts +562 -0
  51. package/src/stories/Features/craftbook/CraftBook.stories.tsx +15 -1
  52. package/src/stories/UI/info/InformationCenter.stories.tsx +58 -0
  53. package/src/stories/UI/joystick/JoystickDPad.stories.tsx +52 -0
@@ -0,0 +1,323 @@
1
+ import { isMobileOrTablet } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../../../constants/uiColors';
5
+ import { Collapsible } from '../../../shared/Collapsible/Collapsible';
6
+ import {
7
+ IInformationCenterItem,
8
+ IInformationCenterNPC,
9
+ } from '../../InformationCenterTypes';
10
+ import { BaseInformationDetails } from '../../shared/BaseInformationDetails';
11
+
12
+ interface IInformationCenterItemDetailsProps {
13
+ item: IInformationCenterItem;
14
+ itemsAtlasJSON: Record<string, any>;
15
+ itemsAtlasIMG: string;
16
+ droppedBy: IInformationCenterNPC[];
17
+ onBack: () => void;
18
+ }
19
+
20
+ export const InformationCenterItemDetails: React.FC<IInformationCenterItemDetailsProps> = ({
21
+ item,
22
+ itemsAtlasJSON,
23
+ itemsAtlasIMG,
24
+ droppedBy,
25
+ onBack,
26
+ }) => {
27
+ const isMobile = isMobileOrTablet();
28
+
29
+ const renderAllowedSlots = () => {
30
+ if (!item.allowedEquipSlotType?.length) return null;
31
+ return (
32
+ <InfoItem>
33
+ <Label>Equip Slots:</Label>
34
+ <Value>{item.allowedEquipSlotType.join(', ')}</Value>
35
+ </InfoItem>
36
+ );
37
+ };
38
+
39
+ const renderRequirements = () => {
40
+ if (!item.minRequirements) return null;
41
+ return (
42
+ <StyledCollapsible title="Requirements" defaultOpen={!isMobile}>
43
+ <RequirementsGrid>
44
+ {item.minRequirements.level && (
45
+ <RequirementItem>
46
+ <Label>Level:</Label>
47
+ <Value>{item.minRequirements.level}</Value>
48
+ </RequirementItem>
49
+ )}
50
+ {item.minRequirements.skill && (
51
+ <RequirementItem>
52
+ <Label>{item.minRequirements.skill.name}:</Label>
53
+ <Value>{item.minRequirements.skill.level}</Value>
54
+ </RequirementItem>
55
+ )}
56
+ </RequirementsGrid>
57
+ </StyledCollapsible>
58
+ );
59
+ };
60
+
61
+ return (
62
+ <BaseInformationDetails
63
+ name={item.name}
64
+ spriteKey={item.texturePath}
65
+ atlasJSON={itemsAtlasJSON}
66
+ atlasIMG={itemsAtlasIMG}
67
+ onBack={onBack}
68
+ >
69
+ <InfoSection>
70
+ <InfoItem>
71
+ <Label>Type:</Label>
72
+ <Value>{item.type}</Value>
73
+ </InfoItem>
74
+ <InfoItem>
75
+ <Label>Subtype:</Label>
76
+ <Value>{item.subType}</Value>
77
+ </InfoItem>
78
+ <InfoItem>
79
+ <Label>Tier:</Label>
80
+ <Value>{item.tier}</Value>
81
+ </InfoItem>
82
+ <InfoItem>
83
+ <Label>Rarity:</Label>
84
+ <Value>{item.rarity}</Value>
85
+ </InfoItem>
86
+ {renderAllowedSlots()}
87
+ </InfoSection>
88
+
89
+ <StyledCollapsible title="Description" defaultOpen={!isMobile}>
90
+ <Description>
91
+ {item.description || 'No description available.'}
92
+ </Description>
93
+ </StyledCollapsible>
94
+
95
+ <StyledCollapsible title="Stats" defaultOpen={!isMobile}>
96
+ <StatGrid>
97
+ <StatItem>Weight: {item.weight}</StatItem>
98
+ <StatItem>Stack Size: {item.maxStackSize}</StatItem>
99
+ {item.rangeType && <StatItem>Range Type: {item.rangeType}</StatItem>}
100
+ {item.basePrice > 0 && (
101
+ <StatItem>Base Price: {item.basePrice}</StatItem>
102
+ )}
103
+ </StatGrid>
104
+ </StyledCollapsible>
105
+
106
+ {renderRequirements()}
107
+
108
+ {item.entityEffects && item.entityEffects.length > 0 && (
109
+ <StyledCollapsible title="Effects" defaultOpen={!isMobile}>
110
+ <EffectsList>
111
+ {item.entityEffects.map((effect, index) => (
112
+ <EffectItem key={index}>
113
+ {effect}
114
+ {item.entityEffectChance && (
115
+ <EffectChance>({item.entityEffectChance}%)</EffectChance>
116
+ )}
117
+ </EffectItem>
118
+ ))}
119
+ </EffectsList>
120
+ {item.usableEffectDescription && (
121
+ <EffectDescription>
122
+ {item.usableEffectDescription}
123
+ </EffectDescription>
124
+ )}
125
+ </StyledCollapsible>
126
+ )}
127
+
128
+ {item.equippedBuff && item.equippedBuff.length > 0 && (
129
+ <StyledCollapsible title="Equipped Buffs" defaultOpen={!isMobile}>
130
+ <BuffsList>
131
+ {item.equippedBuff.map((buff, index) => (
132
+ <BuffItem key={index}>
133
+ <BuffName>{buff.trait}</BuffName>
134
+ <BuffValue>+{buff.buffPercentage}%</BuffValue>
135
+ </BuffItem>
136
+ ))}
137
+ </BuffsList>
138
+ {item.equippedBuffDescription && (
139
+ <BuffDescription>{item.equippedBuffDescription}</BuffDescription>
140
+ )}
141
+ </StyledCollapsible>
142
+ )}
143
+
144
+ {droppedBy.length > 0 && (
145
+ <StyledCollapsible title="Dropped By" defaultOpen={!isMobile}>
146
+ <DropList>
147
+ {droppedBy.map(npc => {
148
+ const loot = npc.loots.find(l => l.itemBlueprintKey === item.key);
149
+ return (
150
+ <DropItem key={npc.id}>
151
+ <NPCName>{npc.name}</NPCName>
152
+ <DropRate>{loot?.chance}%</DropRate>
153
+ </DropItem>
154
+ );
155
+ })}
156
+ </DropList>
157
+ </StyledCollapsible>
158
+ )}
159
+ </BaseInformationDetails>
160
+ );
161
+ };
162
+
163
+ const InfoSection = styled.div`
164
+ display: grid;
165
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
166
+ gap: 8px;
167
+ background: rgba(255, 255, 255, 0.05);
168
+ padding: 12px;
169
+ border-radius: 4px;
170
+ `;
171
+
172
+ const InfoItem = styled.div`
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 8px;
176
+ `;
177
+
178
+ const Label = styled.span`
179
+ color: ${uiColors.yellow};
180
+ font-size: 0.5rem;
181
+ opacity: 0.8;
182
+ `;
183
+
184
+ const Value = styled.span`
185
+ color: ${uiColors.white};
186
+ font-size: 0.5rem;
187
+ `;
188
+
189
+ const StyledCollapsible = styled(Collapsible)`
190
+ background: rgba(255, 255, 255, 0.05);
191
+ border-radius: 4px;
192
+ overflow: hidden;
193
+ `;
194
+
195
+ const Description = styled.p`
196
+ color: ${uiColors.white};
197
+ font-size: 0.5rem;
198
+ margin: 0;
199
+ padding: 12px;
200
+ line-height: 1.5;
201
+ `;
202
+
203
+ const StatGrid = styled.div`
204
+ display: grid;
205
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
206
+ gap: 8px;
207
+ padding: 12px;
208
+ `;
209
+
210
+ const StatItem = styled.div`
211
+ color: ${uiColors.white};
212
+ font-size: 0.5rem;
213
+ background: rgba(255, 255, 255, 0.05);
214
+ padding: 8px;
215
+ border-radius: 4px;
216
+ `;
217
+
218
+ const EffectsList = styled.div`
219
+ display: flex;
220
+ flex-wrap: wrap;
221
+ gap: 8px;
222
+ padding: 12px;
223
+ `;
224
+
225
+ const EffectItem = styled.div`
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 4px;
229
+ color: ${uiColors.white};
230
+ font-size: 0.5rem;
231
+ background: rgba(255, 255, 255, 0.1);
232
+ padding: 4px 8px;
233
+ border-radius: 4px;
234
+ `;
235
+
236
+ const EffectChance = styled.span`
237
+ color: ${uiColors.yellow};
238
+ opacity: 0.8;
239
+ `;
240
+
241
+ const BuffsList = styled.div`
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 8px;
245
+ padding: 12px;
246
+ `;
247
+
248
+ const BuffItem = styled.div`
249
+ display: flex;
250
+ justify-content: space-between;
251
+ align-items: center;
252
+ background: rgba(255, 255, 255, 0.05);
253
+ padding: 8px;
254
+ border-radius: 4px;
255
+ `;
256
+
257
+ const BuffName = styled.span`
258
+ color: ${uiColors.white};
259
+ font-size: 0.5rem;
260
+ `;
261
+
262
+ const BuffValue = styled.span`
263
+ color: ${uiColors.yellow};
264
+ font-size: 0.5rem;
265
+ `;
266
+
267
+ const BuffDescription = styled.p`
268
+ color: ${uiColors.lightGray};
269
+ font-size: 0.45rem;
270
+ margin: 0;
271
+ padding: 0 12px 12px;
272
+ font-style: italic;
273
+ `;
274
+
275
+ const DropList = styled.div`
276
+ display: flex;
277
+ flex-direction: column;
278
+ gap: 0.5rem;
279
+ padding: 12px;
280
+ `;
281
+
282
+ const DropItem = styled.div`
283
+ display: flex;
284
+ justify-content: space-between;
285
+ align-items: center;
286
+ padding: 0.5rem;
287
+ background: rgba(255, 255, 255, 0.05);
288
+ border-radius: 4px;
289
+ `;
290
+
291
+ const NPCName = styled.span`
292
+ color: ${uiColors.white};
293
+ font-size: 0.5rem;
294
+ `;
295
+
296
+ const DropRate = styled.span`
297
+ color: ${uiColors.yellow};
298
+ font-size: 0.5rem;
299
+ `;
300
+
301
+ const RequirementsGrid = styled.div`
302
+ display: grid;
303
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
304
+ gap: 8px;
305
+ padding: 12px;
306
+ `;
307
+
308
+ const RequirementItem = styled.div`
309
+ display: flex;
310
+ align-items: center;
311
+ gap: 8px;
312
+ background: rgba(255, 255, 255, 0.05);
313
+ padding: 8px;
314
+ border-radius: 4px;
315
+ `;
316
+
317
+ const EffectDescription = styled.p`
318
+ color: ${uiColors.lightGray};
319
+ font-size: 0.45rem;
320
+ margin: 8px 0 0;
321
+ padding: 0 12px;
322
+ font-style: italic;
323
+ `;
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { uiColors } from '../../../../constants/uiColors';
4
+ import BaseTooltip, {
5
+ Section,
6
+ SectionTitle,
7
+ StatItem,
8
+ StatsContainer,
9
+ TooltipTitle,
10
+ } from '../../../shared/BaseTooltip';
11
+ import { IInformationCenterItem } from '../../InformationCenterTypes';
12
+
13
+ interface IItemTooltipProps {
14
+ item: IInformationCenterItem;
15
+ }
16
+
17
+ const ItemHeader = styled.div`
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 8px;
21
+ `;
22
+
23
+ const Description = styled.div`
24
+ color: ${uiColors.white};
25
+ font-size: 0.5rem;
26
+ line-height: 1.4;
27
+ margin-top: 8px;
28
+ opacity: 0.8;
29
+ `;
30
+
31
+ const RarityText = styled.span<{ rarity: string }>`
32
+ color: ${props => {
33
+ switch (props.rarity.toLowerCase()) {
34
+ case 'legendary':
35
+ return '#ff8c00';
36
+ case 'rare':
37
+ return '#0070dd';
38
+ case 'uncommon':
39
+ return '#1eff00';
40
+ default:
41
+ return '#ffffff';
42
+ }
43
+ }};
44
+ font-size: 0.5rem;
45
+ `;
46
+
47
+ export const InformationCenterItemTooltip: React.FC<IItemTooltipProps> = ({
48
+ item,
49
+ }) => {
50
+ return (
51
+ <BaseTooltip>
52
+ <ItemHeader>
53
+ <TooltipTitle>{item.name}</TooltipTitle>
54
+ </ItemHeader>
55
+
56
+ <Description>{item.description}</Description>
57
+
58
+ <Section>
59
+ <SectionTitle>Details</SectionTitle>
60
+ <StatsContainer>
61
+ <StatItem>Type: {item.type}</StatItem>
62
+ <StatItem>Weight: {item.weight}</StatItem>
63
+ {item.attack !== undefined && (
64
+ <StatItem>Attack: {item.attack}</StatItem>
65
+ )}
66
+ {item.defense !== undefined && (
67
+ <StatItem>Defense: {item.defense}</StatItem>
68
+ )}
69
+ {item.tier !== undefined && <StatItem>Tier: {item.tier}</StatItem>}
70
+ {item.rangeType && <StatItem>Range: {item.rangeType}</StatItem>}
71
+ </StatsContainer>
72
+ </Section>
73
+
74
+ <Section>
75
+ <SectionTitle>Market</SectionTitle>
76
+ <StatsContainer>
77
+ <StatItem>Price: {item.basePrice}</StatItem>
78
+ {item.rarity && (
79
+ <StatItem>
80
+ Rarity:{' '}
81
+ <RarityText rarity={item.rarity}>{item.rarity}</RarityText>
82
+ </StatItem>
83
+ )}
84
+ </StatsContainer>
85
+ </Section>
86
+ </BaseTooltip>
87
+ );
88
+ };
@@ -0,0 +1,180 @@
1
+ import { ItemType } from '@rpg-engine/shared';
2
+ import React, { useState } from 'react';
3
+ import styled from 'styled-components';
4
+ import { IOptionsProps } from '../../../Dropdown';
5
+ import { InformationCenterCell } from '../../InformationCenterCell';
6
+ import { InformationCenterTabView } from '../../InformationCenterTabView';
7
+ import {
8
+ IInformationCenterItem,
9
+ IInformationCenterNPC,
10
+ } from '../../InformationCenterTypes';
11
+ import { InformationCenterItemDetails } from './InformationCenterItemDetails';
12
+ import { InformationCenterItemTooltip } from './InformationCenterItemTooltip';
13
+
14
+ const TOOLTIP_OFFSET = 20;
15
+
16
+ interface IItemsSectionProps {
17
+ items: IInformationCenterItem[];
18
+ bestiaryItems: IInformationCenterNPC[];
19
+ itemsAtlasJSON: Record<string, any>;
20
+ itemsAtlasIMG: string;
21
+ initialSearchQuery: string;
22
+ }
23
+
24
+ export const ItemsSection: React.FC<IItemsSectionProps> = ({
25
+ items,
26
+ bestiaryItems,
27
+ itemsAtlasJSON,
28
+ itemsAtlasIMG,
29
+ initialSearchQuery,
30
+ }) => {
31
+ const [itemsSearchQuery, setItemsSearchQuery] = useState(initialSearchQuery);
32
+ const [selectedItemCategory, setSelectedItemCategory] = useState<string>(
33
+ 'all'
34
+ );
35
+ const [hoveredItem, setHoveredItem] = useState<IInformationCenterItem | null>(
36
+ null
37
+ );
38
+ const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
39
+ const [
40
+ selectedItem,
41
+ setSelectedItem,
42
+ ] = useState<IInformationCenterItem | null>(null);
43
+
44
+ const itemCategoryOptions: IOptionsProps[] = [
45
+ { id: 0, value: 'all', option: 'All Items' },
46
+ { id: 1, value: ItemType.Consumable, option: 'Consumables' },
47
+ { id: 2, value: ItemType.Weapon, option: 'Weapons' },
48
+ { id: 3, value: ItemType.Armor, option: 'Armor' },
49
+ ];
50
+
51
+ const filterItems = (items: IInformationCenterItem[]) => {
52
+ return items.filter(
53
+ item =>
54
+ (selectedItemCategory === 'all' ||
55
+ item.type === selectedItemCategory) &&
56
+ item.name.toLowerCase().includes(itemsSearchQuery.toLowerCase())
57
+ );
58
+ };
59
+
60
+ const getDroppedByNPCs = (
61
+ itemId: string,
62
+ npcs: IInformationCenterNPC[]
63
+ ): IInformationCenterNPC[] => {
64
+ return (
65
+ npcs.filter(npc =>
66
+ npc.loots?.some(loot => loot.itemBlueprintKey === itemId)
67
+ ) || []
68
+ );
69
+ };
70
+
71
+ const handleMouseEnter = (
72
+ e: React.MouseEvent,
73
+ item: IInformationCenterItem
74
+ ) => {
75
+ setTooltipPosition({
76
+ x: e.clientX + TOOLTIP_OFFSET,
77
+ y: e.clientY,
78
+ });
79
+ setHoveredItem(item);
80
+ };
81
+
82
+ const handleMouseMove = (e: React.MouseEvent) => {
83
+ if (hoveredItem) {
84
+ setTooltipPosition({
85
+ x: e.clientX + TOOLTIP_OFFSET,
86
+ y: e.clientY,
87
+ });
88
+ }
89
+ };
90
+
91
+ const handleMouseLeave = () => {
92
+ setHoveredItem(null);
93
+ };
94
+
95
+ const handleTouchStart = (
96
+ e: React.TouchEvent,
97
+ item: IInformationCenterItem
98
+ ) => {
99
+ const touch = e.touches[0];
100
+ setTooltipPosition({
101
+ x: touch.clientX + TOOLTIP_OFFSET,
102
+ y: touch.clientY,
103
+ });
104
+ setHoveredItem(item);
105
+ };
106
+
107
+ const handleItemClick = (item: IInformationCenterItem) => {
108
+ setSelectedItem(item);
109
+ setHoveredItem(null);
110
+ };
111
+
112
+ const renderContent = (items: IInformationCenterItem[]) => (
113
+ <ItemsGrid>
114
+ {items.map(item => (
115
+ <InformationCenterCell
116
+ key={item.key}
117
+ name={item.name}
118
+ spriteKey={item.texturePath}
119
+ atlasJSON={itemsAtlasJSON}
120
+ atlasIMG={itemsAtlasIMG}
121
+ onMouseEnter={e => handleMouseEnter(e, item)}
122
+ onMouseMove={handleMouseMove}
123
+ onMouseLeave={handleMouseLeave}
124
+ onTouchStart={e => handleTouchStart(e, item)}
125
+ onClick={() => handleItemClick(item)}
126
+ />
127
+ ))}
128
+ </ItemsGrid>
129
+ );
130
+
131
+ return (
132
+ <>
133
+ <InformationCenterTabView
134
+ items={items}
135
+ searchQuery={itemsSearchQuery}
136
+ onSearchChange={setItemsSearchQuery}
137
+ filterItems={filterItems}
138
+ renderContent={renderContent}
139
+ searchPlaceholder="Search items..."
140
+ filterOptions={{
141
+ options: itemCategoryOptions,
142
+ selectedOption: selectedItemCategory,
143
+ onOptionChange: setSelectedItemCategory,
144
+ }}
145
+ emptyMessage="No items found"
146
+ dependencies={[selectedItemCategory]}
147
+ />
148
+ {hoveredItem && (
149
+ <TooltipWrapper
150
+ style={{ top: tooltipPosition.y, left: tooltipPosition.x }}
151
+ >
152
+ <InformationCenterItemTooltip item={hoveredItem} />
153
+ </TooltipWrapper>
154
+ )}
155
+ {selectedItem && (
156
+ <InformationCenterItemDetails
157
+ item={selectedItem}
158
+ itemsAtlasJSON={itemsAtlasJSON}
159
+ itemsAtlasIMG={itemsAtlasIMG}
160
+ droppedBy={getDroppedByNPCs(selectedItem.key, bestiaryItems)}
161
+ onBack={() => setSelectedItem(null)}
162
+ />
163
+ )}
164
+ </>
165
+ );
166
+ };
167
+
168
+ const ItemsGrid = styled.div`
169
+ display: grid;
170
+ grid-template-columns: repeat(4, 1fr);
171
+ gap: 0.5rem;
172
+ padding: 0 1rem;
173
+ `;
174
+
175
+ const TooltipWrapper = styled.div`
176
+ position: fixed;
177
+ z-index: 1000;
178
+ pointer-events: none;
179
+ transition: transform 0.1s ease;
180
+ `;