ps99-api 2.4.0 → 2.6.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.
Files changed (92) hide show
  1. package/.github/workflows/release-on-main.yml +1 -2
  2. package/.idea/runConfigurations/test_changing.xml +1 -1
  3. package/debug_currency.json +57 -0
  4. package/debug_goals.json +271 -0
  5. package/dist/ps99-api.d.ts +2 -0
  6. package/dist/ps99-api.js +4 -1
  7. package/dist/ps99-api.js.map +1 -1
  8. package/dist/request-client/axios.js +6 -1
  9. package/dist/request-client/axios.js.map +1 -1
  10. package/dist/responses/collection/index.d.ts +1 -0
  11. package/dist/responses/collection/index.js +15 -0
  12. package/dist/responses/collection/index.js.map +1 -1
  13. package/dist/responses/collection/rarity.d.ts +1 -0
  14. package/example-web/react/package-lock.json +1504 -1470
  15. package/example-web/react2/package-lock.json +3089 -2766
  16. package/example-web/react2/package.json +6 -1
  17. package/example-web/react2/public/assets/gold_variant_icon.png +0 -0
  18. package/example-web/react2/public/assets/hot_cocoa_egg.png +0 -0
  19. package/example-web/react2/public/index.html +34 -31
  20. package/example-web/react2/src/App.tsx +15 -15
  21. package/example-web/react2/src/assets/guild_placeholder.png +0 -0
  22. package/example-web/react2/src/components/AchievementsComponent.tsx +74 -19
  23. package/example-web/react2/src/components/AutoSizer.tsx +49 -0
  24. package/example-web/react2/src/components/BoostsComponent.tsx +16 -5
  25. package/example-web/react2/src/components/BoothsComponent.tsx +22 -52
  26. package/example-web/react2/src/components/BoxesComponent.tsx +48 -16
  27. package/example-web/react2/src/components/BuffsComponent.tsx +82 -34
  28. package/example-web/react2/src/components/CharmsComponent.tsx +84 -24
  29. package/example-web/react2/src/components/CollectionConfigIndex.tsx +867 -33
  30. package/example-web/react2/src/components/CollectionsIndex.tsx +380 -27
  31. package/example-web/react2/src/components/CollectionsLayout.tsx +60 -0
  32. package/example-web/react2/src/components/CurrencyComponent.tsx +57 -39
  33. package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +172 -15
  34. package/example-web/react2/src/components/EggsComponent.tsx +50 -12
  35. package/example-web/react2/src/components/EnchantsComponent.tsx +88 -42
  36. package/example-web/react2/src/components/FishingRodsComponent.tsx +36 -22
  37. package/example-web/react2/src/components/Footer.tsx +18 -8
  38. package/example-web/react2/src/components/FruitsComponent.tsx +40 -17
  39. package/example-web/react2/src/components/GenericFetchComponent.tsx +9 -1
  40. package/example-web/react2/src/components/GuildBattlesComponent.tsx +41 -34
  41. package/example-web/react2/src/components/Header.tsx +39 -52
  42. package/example-web/react2/src/components/HomePage.tsx +15 -17
  43. package/example-web/react2/src/components/HoverboardsComponent.tsx +23 -99
  44. package/example-web/react2/src/components/ImageComponent.tsx +255 -45
  45. package/example-web/react2/src/components/ItemCard.tsx +240 -0
  46. package/example-web/react2/src/components/LootboxesComponent.tsx +22 -7
  47. package/example-web/react2/src/components/MasteryComponent.tsx +165 -30
  48. package/example-web/react2/src/components/MerchantsComponent.tsx +41 -16
  49. package/example-web/react2/src/components/MiscItemsComponent.tsx +26 -31
  50. package/example-web/react2/src/components/PetsComponent.tsx +100 -61
  51. package/example-web/react2/src/components/PotionsComponent.tsx +121 -27
  52. package/example-web/react2/src/components/RandomEventsComponent.tsx +32 -23
  53. package/example-web/react2/src/components/RanksComponent.tsx +187 -62
  54. package/example-web/react2/src/components/RarityComponent.tsx +123 -5
  55. package/example-web/react2/src/components/ReactWindowMock.tsx +73 -0
  56. package/example-web/react2/src/components/RebirthsComponent.tsx +36 -19
  57. package/example-web/react2/src/components/SecretRoomsComponent.tsx +5 -4
  58. package/example-web/react2/src/components/SeedsComponent.tsx +41 -21
  59. package/example-web/react2/src/components/ShovelsComponent.tsx +21 -9
  60. package/example-web/react2/src/components/Sidebar.tsx +105 -0
  61. package/example-web/react2/src/components/SprinklersComponent.tsx +25 -10
  62. package/example-web/react2/src/components/Tooltip.tsx +36 -0
  63. package/example-web/react2/src/components/UltimatesComponent.tsx +28 -16
  64. package/example-web/react2/src/components/UpgradesComponent.tsx +97 -47
  65. package/example-web/react2/src/components/WateringCansComponent.tsx +20 -14
  66. package/example-web/react2/src/components/WorldsComponent.tsx +21 -11
  67. package/example-web/react2/src/components/XPPotionsComponent.tsx +28 -11
  68. package/example-web/react2/src/components/ZoneFlagsComponent.tsx +25 -14
  69. package/example-web/react2/src/components/ZonesComponent.tsx +43 -60
  70. package/example-web/react2/src/constants/collectionIcons.ts +29 -0
  71. package/example-web/react2/src/context/CollectionDataContext.tsx +62 -0
  72. package/example-web/react2/src/context/ScrollContext.tsx +35 -0
  73. package/example-web/react2/src/hooks/useCollapsibleHeader.ts +69 -0
  74. package/example-web/react2/src/hooks/useExpandableList.ts +38 -0
  75. package/example-web/react2/src/hooks/useItemResolution.ts +351 -0
  76. package/example-web/react2/src/index.css +257 -0
  77. package/example-web/react2/src/index.tsx +2 -1
  78. package/example-web/react2/src/utils/gigantix.ts +40 -0
  79. package/example-web/react2/temp_model.rbxm +0 -0
  80. package/example-web/react2/webpack.config.js +103 -47
  81. package/package.json +11 -11
  82. package/ranks.json +1 -0
  83. package/repro_collection_fetch.ts +33 -0
  84. package/repro_image_fetch.ts +50 -0
  85. package/src/__tests__/__snapshots__/ps99-api-changes.ts.snap +34841 -10439
  86. package/src/__tests__/__snapshots__/ps99-api-live.ts.snap +160667 -67217
  87. package/src/ps99-api.ts +9 -5
  88. package/src/request-client/axios.ts +6 -2
  89. package/src/responses/collection/index.ts +1 -0
  90. package/src/responses/collection/rarity.ts +1 -0
  91. package/tsconfig.json +1 -1
  92. package/example-web/react2/public/service-worker.js +0 -63
@@ -17,15 +17,20 @@
17
17
  "ps99-api": "file:../..",
18
18
  "react": "^18.3.1",
19
19
  "react-dom": "^18.3.1",
20
- "react-router-dom": "^6.24.1"
20
+ "react-router-dom": "^6.24.1",
21
+ "react-virtualized-auto-sizer": "^2.0.3",
22
+ "react-window": "^2.2.7"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@types/react": "^18.3.3",
24
26
  "@types/react-dom": "^18.3.0",
27
+ "@types/react-virtualized-auto-sizer": "^1.0.4",
28
+ "@types/react-window": "^1.8.8",
25
29
  "clean-webpack-plugin": "^4.0.0",
26
30
  "copy-webpack-plugin": "^12.0.2",
27
31
  "css-loader": "^7.1.2",
28
32
  "html-webpack-plugin": "^5.6.0",
33
+ "style-loader": "^4.0.0",
29
34
  "ts-loader": "^9.5.1",
30
35
  "typescript": "^5.5.3",
31
36
  "webpack": "^5.92.1",
@@ -1,33 +1,36 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Pet Simulator 99 API</title>
7
- <link rel="manifest" href="/node-ps99-api/manifest.json" />
8
- <link rel="icon" href="/node-ps99-api/icons/icon-192x192.png" />
9
- </head>
10
- <body>
11
- <div id="root"></div>
12
- <script src="/node-ps99-api/bundle.js"></script>
13
- <script>
14
- if ("serviceWorker" in navigator) {
15
- window.addEventListener("load", function () {
16
- navigator.serviceWorker
17
- .register("/node-ps99-api/service-worker.js")
18
- .then(
19
- function (registration) {
20
- console.log(
21
- "ServiceWorker registration successful with scope: ",
22
- registration.scope,
23
- );
24
- },
25
- function (error) {
26
- console.log("ServiceWorker registration failed: ", error);
27
- },
28
- );
29
- });
30
- }
31
- </script>
32
- </body>
33
- </html>
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Pet Simulator 99 API</title>
8
+ <link rel="manifest" href="/node-ps99-api/manifest.json" />
9
+ <link rel="icon" href="/node-ps99-api/icons/icon-192x192.png" />
10
+ </head>
11
+
12
+ <body>
13
+ <div id="root"></div>
14
+
15
+ <script>
16
+ // if ("serviceWorker" in navigator) {
17
+ // window.addEventListener("load", function () {
18
+ // navigator.serviceWorker
19
+ // .register("/node-ps99-api/service-worker.js")
20
+ // .then(
21
+ // function (registration) {
22
+ // console.log(
23
+ // "ServiceWorker registration successful with scope: ",
24
+ // registration.scope,
25
+ // );
26
+ // },
27
+ // function (error) {
28
+ // console.log("ServiceWorker registration failed: ", error);
29
+ // },
30
+ // );
31
+ // });
32
+ // }
33
+ </script>
34
+ </body>
35
+
36
+ </html>
@@ -1,29 +1,29 @@
1
1
  import React from "react";
2
- import { HashRouter as Router, Routes, Route } from "react-router-dom";
2
+ import { HashRouter as Router, Routes, Route, Navigate } from "react-router-dom";
3
3
  import HomePage from "./components/HomePage";
4
4
  import Header from "./components/Header";
5
5
  import CollectionsIndex from "./components/CollectionsIndex";
6
6
  import CollectionConfigIndex from "./components/CollectionConfigIndex";
7
7
  import DynamicCollectionConfigData from "./components/DynamicCollectionConfigData";
8
+ import CollectionsLayout from "./components/CollectionsLayout";
8
9
  import Footer from "./components/Footer";
10
+ import { ScrollProvider } from "./context/ScrollContext";
9
11
 
10
12
  const App: React.FC = () => {
11
13
  return (
12
14
  <Router>
13
- <Header />
14
- <Routes>
15
- <Route path="/" element={<HomePage />} />
16
- <Route path="/collections" element={<CollectionsIndex />} />
17
- <Route
18
- path="/collections/:collectionName"
19
- element={<CollectionConfigIndex />}
20
- />
21
- <Route
22
- path="/collections/:collectionName/:configName"
23
- element={<DynamicCollectionConfigData />}
24
- />
25
- </Routes>
26
- <Footer />
15
+ <ScrollProvider>
16
+ <Header />
17
+ <Routes>
18
+ <Route path="/" element={<HomePage />} />
19
+ <Route path="/collections" element={<CollectionsIndex />} />
20
+ <Route element={<CollectionsLayout />}>
21
+ <Route path="/collections/:collectionName" element={<CollectionConfigIndex />} />
22
+ <Route path="/collections/:collectionName/:configName" element={<DynamicCollectionConfigData />} />
23
+ </Route>
24
+ </Routes>
25
+ <Footer />
26
+ </ScrollProvider>
27
27
  </Router>
28
28
  );
29
29
  };
@@ -1,32 +1,87 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
3
  import ImageComponent from "./ImageComponent";
4
+ import ItemCard from "./ItemCard";
5
+ import { useItemResolution } from "../hooks/useItemResolution";
6
+ import { useExpandableList } from "../hooks/useExpandableList";
4
7
 
5
8
  const AchievementsComponent: React.FC<{
6
9
  configData: CollectionConfigData<"Achievements">;
7
10
  }> = ({ configData }) => {
11
+ const { resolveItem, getRarityColor, loading } = useItemResolution();
12
+ const { expandedIndices, toggle, expandAll, collapseAll, isExpanded } = useExpandableList(configData.Tiers.length);
13
+
14
+ if (loading) return <div>Loading data...</div>;
15
+
8
16
  return (
9
- <div>
10
- <h2>{configData.Name}</h2>
11
- <ImageComponent src={configData.Icon} alt={configData.Name} />
12
- <ul>
17
+ <div style={{ width: "100%", height: "100%", boxSizing: "border-box" }}>
18
+
19
+ <div style={{ marginBottom: '20px', display: 'flex', gap: '10px', justifyContent: 'center' }}>
20
+ <button onClick={expandAll} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #ddd', background: '#fff', cursor: 'pointer' }}>Expand All</button>
21
+ <button onClick={collapseAll} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #ddd', background: '#fff', cursor: 'pointer' }}>Collapse All</button>
22
+ </div>
23
+ <div style={{ maxWidth: "150px", margin: "0 auto 20px" }}>
24
+ <ImageComponent src={configData.Icon} alt={configData.Name} />
25
+ </div>
26
+ <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
13
27
  {configData.Tiers.map((tier, index) => (
14
- <li key={index}>
15
- <h3>{tier.Title}</h3>
16
- <p>{tier.Desc.replace("{amount}", tier.Amount.toString())}</p>
17
- <p>Difficulty: {tier.Difficulty.Name}</p>
18
- <p>Amount: {tier.Amount}</p>
19
- <ul>
20
- {tier.Rewards.map((reward, rewardIndex) => (
21
- <li key={rewardIndex}>
22
- Reward ID: {reward.Reward._data.id} Amount:{" "}
23
- {reward.Reward._data._am}
24
- </li>
25
- ))}
26
- </ul>
27
- </li>
28
+ <div
29
+ key={index}
30
+ style={{
31
+ border: "1px solid #eee",
32
+ padding: "15px",
33
+ borderRadius: "12px",
34
+ textAlign: 'left'
35
+ }}
36
+ >
37
+ <div
38
+ onClick={() => toggle(index)}
39
+ style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer', paddingBottom: isExpanded(index) ? '10px' : '0' }}
40
+ >
41
+ <h3 style={{ margin: 0, fontSize: '1.2em', display: 'flex', alignItems: 'center', gap: '8px' }}>
42
+ {isExpanded(index) ? '▼' : '▶'} {tier.Title}
43
+ </h3>
44
+ <div style={{ display: "flex", gap: "10px" }}>
45
+ <span className="badge">Difficulty: {tier.Difficulty.Name}</span>
46
+ <span className="badge">Amount: {tier.Amount.toLocaleString()} {(tier.Desc.match(/{amount}\s*([a-zA-Z]+)/)?.[1] || '')}</span>
47
+ </div>
48
+ </div>
49
+
50
+ {isExpanded(index) && (
51
+ <div style={{ borderTop: '1px solid #eee', paddingTop: '15px' }}>
52
+
53
+ <p style={{ marginBottom: '15px', color: '#555' }}>{tier.Desc.replace("{amount}", tier.Amount.toLocaleString())}</p>
54
+
55
+ <h4 style={{ fontSize: '1em', marginBottom: '10px' }}>Rewards</h4>
56
+ <div
57
+ style={{
58
+ display: "grid",
59
+ gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))",
60
+ gap: "10px",
61
+ }}
62
+ >
63
+ {tier.Rewards.map((reward, rewardIndex) => {
64
+ const id = reward.Reward._data.id;
65
+ const amount = reward.Reward._data._am;
66
+ const itemData = resolveItem(id);
67
+ return (
68
+ <div key={rewardIndex}>
69
+ <ItemCard
70
+ id={id}
71
+ amount={amount}
72
+ label={id}
73
+ itemData={itemData}
74
+ rarityColor={itemData?.rarity ? getRarityColor(itemData.rarity) : null}
75
+ />
76
+ </div>
77
+ );
78
+ })}
79
+ </div>
80
+ </div>
81
+ )}
82
+ </div>
28
83
  ))}
29
- </ul>
84
+ </div>
30
85
  </div>
31
86
  );
32
87
  };
@@ -0,0 +1,49 @@
1
+ import React, { useState, useLayoutEffect, useRef } from "react";
2
+
3
+ export default function AutoSizer({
4
+ children,
5
+ className,
6
+ defaultHeight,
7
+ defaultWidth,
8
+ style,
9
+ renderProp,
10
+ ...rest
11
+ }: any) {
12
+ const [size, setSize] = useState({ height: defaultHeight || 0, width: defaultWidth || 0 });
13
+ const ref = useRef<HTMLDivElement>(null);
14
+
15
+ useLayoutEffect(() => {
16
+ const element = ref.current;
17
+ if (!element) return;
18
+
19
+ const ResizeObserver = (window as any).ResizeObserver;
20
+ if (!ResizeObserver) return;
21
+
22
+ const observer = new ResizeObserver((entries: any) => {
23
+ for (const entry of entries) {
24
+ if (entry.contentRect) {
25
+ const { width, height } = entry.contentRect;
26
+ // Rounding for stability
27
+ setSize({ width: Math.floor(width), height: Math.floor(height) });
28
+ }
29
+ }
30
+ });
31
+
32
+ observer.observe(element);
33
+ return () => observer.disconnect();
34
+ }, []);
35
+
36
+ const childParams = size;
37
+
38
+ // Render prop takes precedence if provided (based on my usage in other files)
39
+ const content = renderProp
40
+ ? renderProp(childParams)
41
+ : (typeof children === 'function' ? children(childParams) : children);
42
+
43
+ return (
44
+ <div className={className} ref={ref} style={{ ...style, width: '100%', height: '100%', overflow: 'hidden' }}>
45
+ {/* Only render content if we have size, or if default provided to avoid jumpiness */}
46
+ {(size.width > 0 && size.height > 0) ? content : null}
47
+ </div>
48
+ );
49
+ }
@@ -1,15 +1,26 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
4
 
5
5
  const BoostsComponent: React.FC<{
6
6
  configData: CollectionConfigData<"Boosts">;
7
7
  }> = ({ configData }) => {
8
8
  return (
9
- <div>
10
- <h2>{configData.DisplayName}</h2>
11
- <ImageComponent src={configData.Icon} alt={configData.DisplayName} />
12
- <p>Maximum Percent: {configData.MaximumPercent}%</p>
9
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
10
+ <ItemCard
11
+ id={configData.DisplayName}
12
+ amount={1}
13
+ label={configData.DisplayName}
14
+ itemData={{
15
+ icon: configData.Icon,
16
+ rarity: undefined,
17
+ name: configData.DisplayName
18
+ }}
19
+ rarityColor={null}
20
+ />
21
+ <div style={{ marginTop: '10px', fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
22
+ <p>Max Percent: {configData.MaximumPercent}%</p>
23
+ </div>
13
24
  </div>
14
25
  );
15
26
  };
@@ -1,62 +1,32 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
+ import { useItemResolution } from "../hooks/useItemResolution";
4
5
 
5
6
  const BoothsComponent: React.FC<{
6
7
  configData: CollectionConfigData<"Booths">;
7
8
  }> = ({ configData }) => {
9
+ const { getRarityColor } = useItemResolution();
10
+ const rarityColor = configData.Rarity ? getRarityColor(configData.Rarity) : null;
11
+
8
12
  return (
9
- <div
10
- style={{
11
- padding: "1em",
12
- border: "1px solid #ccc",
13
- borderRadius: "8px",
14
- backgroundColor: "#f9f9f9",
15
- }}
16
- >
17
- <h2 style={{ borderBottom: "2px solid #ccc", paddingBottom: "0.5em" }}>
18
- {configData.DisplayName}
19
- </h2>
20
- <ImageComponent src={configData.Icon} alt={configData.DisplayName} />
21
- <p>
22
- <strong>Description:</strong> {configData.Desc}
23
- </p>
24
- <p>
25
- <strong>Rarity:</strong> {configData.Rarity.DisplayName}
26
- </p>
27
- <p>
28
- <strong>Rarity Number:</strong> {configData.Rarity.RarityNumber}
29
- </p>
30
- {configData.Hidden && (
31
- <p>
32
- <strong>Hidden:</strong> Yes
33
- </p>
34
- )}
35
- {configData.Tradable && (
36
- <p>
37
- <strong>Tradable:</strong> Yes
38
- </p>
39
- )}
40
- {configData.OffSale && (
41
- <p>
42
- <strong>Off Sale:</strong> Yes
43
- </p>
44
- )}
45
- {configData.ProductId && (
46
- <p>
47
- <strong>Product ID:</strong> {configData.ProductId}
48
- </p>
49
- )}
50
- {configData.DiamondPrice && (
51
- <p>
52
- <strong>Diamond Price:</strong> {configData.DiamondPrice}
53
- </p>
54
- )}
55
- {configData.Sittable && (
56
- <p>
57
- <strong>Sittable:</strong> Yes
58
- </p>
59
- )}
13
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
14
+ <ItemCard
15
+ id={configData.DisplayName}
16
+ amount={1}
17
+ label={configData.DisplayName}
18
+ itemData={{
19
+ icon: configData.Icon,
20
+ rarity: configData.Rarity,
21
+ name: configData.DisplayName
22
+ }}
23
+ rarityColor={rarityColor}
24
+ />
25
+ <div style={{ marginTop: '10px', fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
26
+ <p>{configData.Desc}</p>
27
+ {configData.ProductId && <p>ID: {configData.ProductId}</p>}
28
+ {configData.DiamondPrice && <p>💎 {configData.DiamondPrice}</p>}
29
+ </div>
60
30
  </div>
61
31
  );
62
32
  };
@@ -1,26 +1,58 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
+ import { useItemResolution } from "../hooks/useItemResolution";
4
5
 
5
6
  const BoxesComponent: React.FC<{
6
7
  configData: CollectionConfigData<"Boxes">;
7
8
  }> = ({ configData }) => {
9
+ const { getRarityColor } = useItemResolution();
10
+ const rarityColor = configData.Rarity ? getRarityColor(configData.Rarity) : null;
11
+
8
12
  return (
9
- <div>
10
- <h2>{configData.DisplayName}</h2>
11
- <p>Description: {configData.Desc}</p>
12
- <p>Capacity: {configData.Capacity}</p>
13
- <p>Rarity: {configData.Rarity.DisplayName}</p>
14
- <p>Rarity Number: {configData.Rarity.RarityNumber}</p>
15
- <h3>Icons:</h3>
16
- <ul>
17
- {configData.Icons.map((icon, index) => (
18
- <li key={index}>
19
- <ImageComponent src={icon.Icon} alt={icon.Name} />
20
- <span>{icon.Name}</span>
21
- </li>
22
- ))}
23
- </ul>
13
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
14
+
15
+ <div style={{ maxWidth: '300px', margin: '0 auto 15px auto' }}>
16
+ <ItemCard
17
+ id={configData.DisplayName}
18
+ amount={1}
19
+ label={configData.DisplayName}
20
+ itemData={{
21
+ icon: configData.Icons && configData.Icons.length > 0 ? configData.Icons[0].Icon : undefined,
22
+ rarity: configData.Rarity,
23
+ name: configData.DisplayName
24
+ }}
25
+ rarityColor={rarityColor}
26
+ />
27
+ </div>
28
+
29
+ <div style={{ marginBottom: '20px', fontSize: '0.9em', color: '#666', textAlign: 'center' }}>
30
+ <p>{configData.Desc}</p>
31
+ <p style={{ fontWeight: 'bold' }}>Capacity: {configData.Capacity}</p>
32
+ </div>
33
+
34
+ {configData.Icons && configData.Icons.length > 0 && (
35
+ <div>
36
+ <h3 style={{ fontSize: '1.2em', borderBottom: '1px solid #eee', paddingBottom: '5px' }}>Variants</h3>
37
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))", gap: "10px", marginTop: '15px' }}>
38
+ {configData.Icons.map((icon, index) => (
39
+ <div key={index} style={{ textAlign: 'center' }}>
40
+ <ItemCard
41
+ id={icon.Name}
42
+ amount={1}
43
+ label={icon.Name}
44
+ itemData={{
45
+ icon: icon.Icon,
46
+ rarity: configData.Rarity,
47
+ name: icon.Name
48
+ }}
49
+ rarityColor={rarityColor}
50
+ />
51
+ </div>
52
+ ))}
53
+ </div>
54
+ </div>
55
+ )}
24
56
  </div>
25
57
  );
26
58
  };
@@ -1,45 +1,93 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import DynamicCollectionConfigData from "./DynamicCollectionConfigData";
3
+ import { GenericFetchComponent } from "./GenericFetchComponent";
4
+ import ItemCard from "./ItemCard";
4
5
 
5
6
  const BuffsComponent: React.FC<{
6
7
  configData: CollectionConfigData<"Buffs">;
7
8
  }> = ({ configData }) => {
8
9
  return (
9
- <div
10
- style={{
11
- padding: "1em",
12
- border: "1px solid #ccc",
13
- borderRadius: "8px",
14
- marginBottom: "2em",
15
- }}
16
- >
17
- <h2 style={{ borderBottom: "2px solid #ccc", paddingBottom: "0.5em" }}>
18
- {configData.DisplayName}
19
- </h2>
20
- <p>
21
- <strong>Associated Item ID:</strong> {configData.AssociatedItemID}
22
- </p>
23
- <p>
24
- <strong>Associated Item Class:</strong> {configData.AssociatedItemClass}
25
- </p>
26
- <p>
27
- <strong>Length:</strong> {configData.Length} seconds
28
- </p>
29
- {configData.IgnoreInstancePause && (
30
- <p>
31
- <strong>Ignore Instance Pause:</strong> Yes
32
- </p>
33
- )}
34
- {configData.AssociatedItemClass === "Misc" && (
35
- <div style={{ marginTop: "2em" }}>
36
- <h3>Associated Misc Item</h3>
37
- <DynamicCollectionConfigData
38
- collectionName="MiscItems"
39
- configName={configData.AssociatedItemID}
40
- />
10
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
11
+
12
+ {/* Grid Layout */}
13
+ <div style={{
14
+ display: 'grid',
15
+ gridTemplateColumns: '1fr 1fr',
16
+ gap: '40px',
17
+ alignItems: 'start'
18
+ }}>
19
+
20
+ {/* Left Column: Associated Item (Visuals) */}
21
+ <div style={{
22
+ background: '#f9f9f9',
23
+ padding: '30px',
24
+ borderRadius: '24px',
25
+ border: '2px solid #eee',
26
+ display: 'flex',
27
+ flexDirection: 'column',
28
+ alignItems: 'center',
29
+ gap: '20px'
30
+ }}>
31
+ {configData.AssociatedItemClass === "Misc" && (
32
+ <>
33
+ <h3 style={{ margin: 0, color: '#888', textTransform: 'uppercase', fontSize: '0.9rem' }}>Associated Item</h3>
34
+ <div style={{ width: '100%', maxWidth: '300px' }}>
35
+ <GenericFetchComponent
36
+ collectionName="MiscItems"
37
+ configName={configData.AssociatedItemID}
38
+ render={(data: any) => (
39
+ <ItemCard
40
+ id={data.DisplayName}
41
+ amount={1}
42
+ label={data.DisplayName}
43
+ itemData={{
44
+ icon: data.Icon,
45
+ rarity: data.Rarity,
46
+ name: data.DisplayName
47
+ }}
48
+ // Simple non-interactive card style
49
+ />
50
+ )}
51
+ />
52
+ </div>
53
+ </>
54
+ )}
55
+ {/* If no associated item, maybe show a generic buff icon? */}
41
56
  </div>
42
- )}
57
+
58
+ {/* Right Column: Stats & Data */}
59
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
60
+
61
+ {/* Stats Grid */}
62
+ <div style={{
63
+ display: 'grid',
64
+ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
65
+ gap: '20px',
66
+ width: '100%'
67
+ }}>
68
+ <div style={{ background: '#e0f7fa', padding: '15px', borderRadius: '12px', border: '1px solid #b2ebf2' }}>
69
+ <strong style={{ display: 'block', fontSize: '0.9rem', color: '#006064', marginBottom: '5px' }}>CLASS</strong>
70
+ <span style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#00838f' }}>{configData.AssociatedItemClass}</span>
71
+ </div>
72
+ <div style={{ background: '#fff3e0', padding: '15px', borderRadius: '12px', border: '1px solid #ffe0b2' }}>
73
+ <strong style={{ display: 'block', fontSize: '0.9rem', color: '#e65100', marginBottom: '5px' }}>DURATION</strong>
74
+ <span style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#ef6c00' }}>{configData.Length}s</span>
75
+ </div>
76
+ {configData.IgnoreInstancePause && (
77
+ <div style={{ background: '#fce4ec', padding: '15px', borderRadius: '12px', border: '1px solid #f8bbd0' }}>
78
+ <strong style={{ display: 'block', fontSize: '0.9rem', color: '#880e4f', marginBottom: '5px' }}>SPECIAL</strong>
79
+ <span style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#ad1457' }}>Ignores Pause</span>
80
+ </div>
81
+ )}
82
+ <div style={{ background: '#f5f5f5', padding: '15px', borderRadius: '12px', border: '1px solid #ddd' }}>
83
+ <strong style={{ display: 'block', fontSize: '0.9rem', color: '#666', marginBottom: '5px' }}>ITEM ID</strong>
84
+ <span style={{ fontSize: '0.9rem', fontFamily: 'monospace', color: '#333' }}>{configData.AssociatedItemID}</span>
85
+ </div>
86
+ </div>
87
+
88
+ </div>
89
+
90
+ </div>
43
91
  </div>
44
92
  );
45
93
  };