ps99-api 2.3.3 → 2.5.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 (99) hide show
  1. package/.github/workflows/release-on-main.yml +1 -2
  2. package/.idea/node-ps99-api.iml +1 -0
  3. package/.idea/runConfigurations/test_changing.xml +1 -1
  4. package/README.md +1 -1
  5. package/debug_currency.json +57 -0
  6. package/debug_goals.json +271 -0
  7. package/dist/ps99-api.d.ts +2 -0
  8. package/dist/ps99-api.js +4 -1
  9. package/dist/ps99-api.js.map +1 -1
  10. package/dist/request-client/axios.js +6 -1
  11. package/dist/request-client/axios.js.map +1 -1
  12. package/dist/responses/collection/achievement.d.ts +2 -0
  13. package/dist/responses/collection/guild-battle.d.ts +2 -4
  14. package/dist/responses/collection/index.d.ts +1 -0
  15. package/dist/responses/collection/index.js +15 -0
  16. package/dist/responses/collection/index.js.map +1 -1
  17. package/dist/responses/collection/rank.d.ts +1 -0
  18. package/dist/responses/collection/rarity.d.ts +1 -0
  19. package/dist/responses/collection/seed.d.ts +1 -0
  20. package/dist/responses/exists.d.ts +2 -0
  21. package/example-web/react/package-lock.json +1504 -1470
  22. package/example-web/react2/package-lock.json +3082 -2759
  23. package/example-web/react2/package.json +6 -1
  24. package/example-web/react2/public/assets/gold_variant_icon.png +0 -0
  25. package/example-web/react2/public/assets/hot_cocoa_egg.png +0 -0
  26. package/example-web/react2/public/index.html +34 -31
  27. package/example-web/react2/src/App.tsx +6 -9
  28. package/example-web/react2/src/assets/guild_placeholder.png +0 -0
  29. package/example-web/react2/src/components/AchievementsComponent.tsx +78 -30
  30. package/example-web/react2/src/components/BoostsComponent.tsx +18 -14
  31. package/example-web/react2/src/components/BoothsComponent.tsx +24 -22
  32. package/example-web/react2/src/components/BoxesComponent.tsx +46 -21
  33. package/example-web/react2/src/components/BuffsComponent.tsx +83 -13
  34. package/example-web/react2/src/components/CharmsComponent.tsx +47 -29
  35. package/example-web/react2/src/components/CollectionConfigIndex.tsx +398 -35
  36. package/example-web/react2/src/components/CollectionsIndex.tsx +132 -23
  37. package/example-web/react2/src/components/CollectionsLayout.tsx +50 -0
  38. package/example-web/react2/src/components/CurrencyComponent.tsx +59 -50
  39. package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +178 -11
  40. package/example-web/react2/src/components/EggsComponent.tsx +77 -44
  41. package/example-web/react2/src/components/EnchantsComponent.tsx +84 -34
  42. package/example-web/react2/src/components/FishingRodsComponent.tsx +38 -31
  43. package/example-web/react2/src/components/Footer.tsx +75 -18
  44. package/example-web/react2/src/components/FruitsComponent.tsx +41 -25
  45. package/example-web/react2/src/components/GenericFetchComponent.tsx +40 -22
  46. package/example-web/react2/src/components/GuildBattlesComponent.tsx +93 -65
  47. package/example-web/react2/src/components/Header.tsx +5 -37
  48. package/example-web/react2/src/components/HomePage.tsx +16 -16
  49. package/example-web/react2/src/components/HoverboardsComponent.tsx +25 -37
  50. package/example-web/react2/src/components/ImageComponent.tsx +255 -45
  51. package/example-web/react2/src/components/ItemCard.tsx +240 -0
  52. package/example-web/react2/src/components/LootboxesComponent.tsx +24 -16
  53. package/example-web/react2/src/components/MasteryComponent.tsx +93 -37
  54. package/example-web/react2/src/components/MerchantsComponent.tsx +46 -28
  55. package/example-web/react2/src/components/MiscItemsComponent.tsx +28 -26
  56. package/example-web/react2/src/components/PetsComponent.tsx +115 -46
  57. package/example-web/react2/src/components/PotionsComponent.tsx +53 -36
  58. package/example-web/react2/src/components/RandomEventsComponent.tsx +39 -35
  59. package/example-web/react2/src/components/RanksComponent.tsx +187 -71
  60. package/example-web/react2/src/components/RarityComponent.tsx +124 -13
  61. package/example-web/react2/src/components/RebirthsComponent.tsx +37 -26
  62. package/example-web/react2/src/components/SecretRoomsComponent.tsx +7 -13
  63. package/example-web/react2/src/components/SeedsComponent.tsx +45 -32
  64. package/example-web/react2/src/components/ShovelsComponent.tsx +23 -18
  65. package/example-web/react2/src/components/Sidebar.tsx +105 -0
  66. package/example-web/react2/src/components/SprinklersComponent.tsx +27 -19
  67. package/example-web/react2/src/components/Tooltip.tsx +36 -0
  68. package/example-web/react2/src/components/UltimatesComponent.tsx +29 -24
  69. package/example-web/react2/src/components/UpgradesComponent.tsx +99 -55
  70. package/example-web/react2/src/components/WateringCansComponent.tsx +23 -21
  71. package/example-web/react2/src/components/WorldsComponent.tsx +27 -24
  72. package/example-web/react2/src/components/XPPotionsComponent.tsx +29 -19
  73. package/example-web/react2/src/components/ZoneFlagsComponent.tsx +27 -23
  74. package/example-web/react2/src/components/ZonesComponent.tsx +56 -73
  75. package/example-web/react2/src/constants/collectionIcons.ts +29 -0
  76. package/example-web/react2/src/context/CollectionDataContext.tsx +62 -0
  77. package/example-web/react2/src/hooks/useExpandableList.ts +38 -0
  78. package/example-web/react2/src/hooks/useItemResolution.ts +351 -0
  79. package/example-web/react2/src/index.css +257 -0
  80. package/example-web/react2/src/index.tsx +2 -1
  81. package/example-web/react2/temp_model.rbxm +0 -0
  82. package/example-web/react2/webpack.config.js +103 -47
  83. package/package.json +11 -11
  84. package/ranks.json +1 -0
  85. package/repro_collection_fetch.ts +33 -0
  86. package/repro_image_fetch.ts +50 -0
  87. package/src/__tests__/__snapshots__/ps99-api-changes.ts.snap +34841 -10439
  88. package/src/__tests__/__snapshots__/ps99-api-live.ts.snap +160667 -67217
  89. package/src/ps99-api.ts +9 -5
  90. package/src/request-client/axios.ts +6 -2
  91. package/src/responses/collection/achievement.ts +2 -0
  92. package/src/responses/collection/guild-battle.ts +2 -4
  93. package/src/responses/collection/index.ts +1 -0
  94. package/src/responses/collection/rank.ts +1 -0
  95. package/src/responses/collection/rarity.ts +1 -0
  96. package/src/responses/collection/seed.ts +1 -0
  97. package/src/responses/exists.ts +2 -0
  98. package/tsconfig.json +1 -1
  99. package/example-web/react2/public/service-worker.js +0 -63
@@ -1,49 +1,412 @@
1
+ // @ts-nocheck
1
2
  import React, { useEffect, useState } from "react";
2
- import { useParams, Link } from "react-router-dom";
3
- import { PetSimulator99API, CollectionName } from "ps99-api";
3
+ import { useParams, Link, useNavigate } from "react-router-dom";
4
+ import Tooltip from "./Tooltip";
5
+ import { PetSimulator99API, Collections, CollectionName } from "ps99-api";
6
+ import ItemCard from "./ItemCard";
7
+ import DynamicCollectionConfigData from "./DynamicCollectionConfigData";
8
+ import ImageComponent from "./ImageComponent";
4
9
 
5
- const CollectionConfigIndex: React.FC = () => {
6
- const { collectionName } = useParams<{ collectionName: CollectionName }>();
7
- const [configNames, setConfigNames] = useState<string[]>([]);
8
- const [error, setError] = useState<string | null>(null);
10
+ import { useItemResolution } from "../hooks/useItemResolution";
11
+ import { useCollectionData } from "../context/CollectionDataContext";
12
+ import { Grid as FixedSizeGrid } from "react-window";
13
+ import { AutoSizer } from "react-virtualized-auto-sizer/dist/react-virtualized-auto-sizer.cjs";
14
+ import ItemCard from "./ItemCard";
9
15
 
16
+ const GridCellRenderer = ({ columnIndex, rowIndex, style, items, columnCount, navigate, collectionName, variantFilter, shinyFilter, resolveIcon, GAP }: any) => {
17
+ const index = rowIndex * columnCount + columnIndex;
18
+ if (index >= items.length) return null;
19
+ const item = items[index];
20
+ const itemConfig = (item as any).configData || item;
21
+ const icon = resolveIcon(itemConfig);
22
+ const itemDataWithIcon = { ...itemConfig, icon };
23
+
24
+ return (
25
+ <div style={style}>
26
+ <div style={{
27
+ position: 'absolute',
28
+ top: GAP / 2,
29
+ left: GAP / 2,
30
+ right: GAP / 2,
31
+ bottom: GAP / 2,
32
+ // Using explicit specific size if needed, but absolute positioning with insets works well for gaps
33
+ }}>
34
+ <div
35
+ onClick={() => navigate(`/collections/${collectionName}/${item.configName}`)}
36
+ style={{ cursor: "pointer", height: '100%' }}
37
+ >
38
+ <ItemCard
39
+ id={item.configName}
40
+ amount={""}
41
+ label={itemConfig.DisplayName || itemConfig.name || item.configName}
42
+ itemData={itemDataWithIcon}
43
+ rarityColor={itemConfig.rarity?.Color || (itemConfig.Rarity?.Color)}
44
+ variant={collectionName === "Pets" ? (variantFilter as any) : undefined}
45
+ shiny={collectionName === "Pets" ? shinyFilter : undefined}
46
+ />
47
+ </div>
48
+ </div>
49
+ </div>
50
+ );
51
+ };
52
+
53
+
54
+
55
+ interface CollectionConfigIndexProps { }
56
+
57
+ const CollectionConfigIndex: React.FC<CollectionConfigIndexProps> = () => {
58
+ const { collectionName } = useParams<{ collectionName: string }>();
59
+ const navigate = useNavigate();
60
+ // Call the hook at the top level so we can use it
61
+ const { resolveIcon } = useItemResolution();
62
+
63
+ const { data, fetchCollection, isLoading } = useCollectionData();
64
+
65
+ const items = (data[collectionName || ""] || []) as Collections[];
66
+ const loading = isLoading(collectionName || "");
67
+
68
+ const [searchTerm, setSearchTerm] = useState<string>("");
69
+ const [windowWidth, setWindowWidth] = useState(window.innerWidth);
70
+
71
+ // State for Pets Filter
72
+ const [specialFilter, setSpecialFilter] = useState<"H" | "T" | "G" | null>(null);
73
+ const [variantFilter, setVariantFilter] = useState<"Normal" | "Golden" | "Rainbow">("Normal");
74
+ const [shinyFilter, setShinyFilter] = useState<boolean>(false);
75
+
76
+ // Responsive check
10
77
  useEffect(() => {
11
- const fetchConfigNames = async () => {
12
- if (!collectionName) return;
13
-
14
- const api = new PetSimulator99API();
15
- const response = await api.getCollection(collectionName);
16
-
17
- if (response.status === "ok") {
18
- const names = response.data.map((item) => item.configName);
19
- setConfigNames(names);
20
- } else {
21
- setError(response.error.message);
22
- }
78
+ const handleResize = () => {
79
+ setWindowWidth(window.innerWidth);
23
80
  };
81
+ window.addEventListener("resize", handleResize);
82
+ return () => window.removeEventListener("resize", handleResize);
83
+ }, []);
84
+
85
+ const isMobile = windowWidth < 768;
24
86
 
25
- fetchConfigNames();
26
- }, [collectionName]);
87
+ useEffect(() => {
88
+ if (collectionName) {
89
+ fetchCollection(collectionName as CollectionName);
90
+ }
91
+ }, [collectionName, fetchCollection]);
27
92
 
28
- if (error) {
29
- return <div>Error: {error}</div>;
93
+ // Loading State
94
+ if (loading) {
95
+ return (
96
+ <div
97
+ style={{
98
+ display: "flex",
99
+ justifyContent: "center",
100
+ alignItems: "center",
101
+ height: "50vh",
102
+ fontSize: "1.5rem",
103
+ fontWeight: "bold",
104
+ color: "#666",
105
+ }}
106
+ >
107
+ Loading {collectionName}...
108
+ </div>
109
+ );
110
+ }
111
+
112
+ // Process items for rendering
113
+ const processedItems = items.map((item) => {
114
+ const configData: any = (item as any).configData || item;
115
+ return {
116
+ ...item,
117
+ configData,
118
+ };
119
+ });
120
+
121
+ const filteredItems = processedItems.filter((item) => {
122
+ const config = item.configData;
123
+ const name =
124
+ config.DisplayName || config.name || item.configName || "";
125
+ if (
126
+ searchTerm &&
127
+ !name.toLowerCase().includes(searchTerm.toLowerCase())
128
+ ) {
129
+ return false;
130
+ }
131
+ return true;
132
+ });
133
+
134
+
135
+ // Apply Pet Filters
136
+ const finalItems = filteredItems.filter((item) => {
137
+ if (collectionName !== "Pets") return true;
138
+
139
+ const config = item.configData;
140
+ const name = config.DisplayName || config.name || item.configName || "";
141
+
142
+ // Special Filter
143
+ if (specialFilter === "H") {
144
+ if (!name.startsWith("Huge ")) return false;
145
+ } else if (specialFilter === "T") {
146
+ if (!name.startsWith("Titanic ")) return false;
147
+ } else if (specialFilter === "G") {
148
+ if (!name.startsWith("Gargantuan ")) return false;
149
+ } else {
150
+ // None selected: Exclude Huge, Titanic, Gargantuan
151
+ if (name.startsWith("Huge ") || name.startsWith("Titanic ") || name.startsWith("Gargantuan ")) return false;
152
+ }
153
+
154
+ return true;
155
+ });
156
+
157
+ interface ItemData {
158
+ items: typeof finalItems;
159
+ columnCount?: number;
160
+ navigate: any;
161
+ collectionName: string | undefined;
162
+ variantFilter: string;
163
+ shinyFilter: boolean;
164
+ resolveIcon: (item: any) => string | null;
165
+ GAP: number;
30
166
  }
31
167
 
32
168
  return (
33
- <div>
34
- <h2>{collectionName} Configurations</h2>
35
- <ul>
36
- {configNames.map((configName, index) => (
37
- <li key={index}>
38
- <Link
39
- to={`/collections/${collectionName}/${encodeURIComponent(configName)}`}
169
+ <div
170
+ style={{
171
+ flex: 1,
172
+ display: "flex",
173
+ flexDirection: "column",
174
+ overflow: "hidden",
175
+ backgroundColor: "#ffffff",
176
+ height: "100%", // ensure it fills parent
177
+ }}
178
+ >
179
+ {/* Header Bar inside Window */}
180
+ <div
181
+ style={{
182
+ padding: "15px 20px",
183
+ borderBottom: "4px solid #333",
184
+ display: "flex",
185
+ alignItems: "center",
186
+ gap: "15px",
187
+ backgroundColor: "#fff",
188
+ }}
189
+ >
190
+ <h2
191
+ style={{
192
+ margin: 0,
193
+ fontSize: "2.5rem",
194
+ fontWeight: "900",
195
+ color: "#333",
196
+ textShadow: "3px 3px 0px #eee",
197
+ marginRight: "auto",
198
+ fontFamily: "'Fredoka One', cursive, sans-serif",
199
+ letterSpacing: "1px",
200
+ }}
201
+ >
202
+ {collectionName}!
203
+ </h2>
204
+
205
+ {/* Filter Ribbon (Pets Only) */}
206
+ {collectionName === "Pets" && (
207
+ <div style={{ display: "flex", gap: "8px", marginRight: "20px" }}>
208
+ {/* Specials: H, T, G */}
209
+ {['H', 'T', 'G'].map((symbol) => (
210
+ <button
211
+ key={symbol}
212
+ onClick={() => setSpecialFilter(specialFilter === symbol ? null : symbol as any)}
213
+ style={{
214
+ width: "36px",
215
+ height: "36px",
216
+ borderRadius: "50%",
217
+ border: "3px solid #333",
218
+ backgroundColor: specialFilter === symbol ? "#333" : "#fff",
219
+ color: specialFilter === symbol ? "#fff" : "#333",
220
+ fontSize: "1.2rem",
221
+ fontWeight: "bold",
222
+ cursor: "pointer",
223
+ display: "flex",
224
+ alignItems: "center",
225
+ justifyContent: "center",
226
+ boxShadow: "0 3px 0 #ccc",
227
+ }}
228
+ >
229
+ {symbol}
230
+ </button>
231
+ ))}
232
+
233
+ {/* Variants: Normal, Golden, Rainbow */}
234
+ {[
235
+ { symbol: '🐾', mode: 'Normal', color: '#fff' },
236
+ { symbol: '★', mode: 'Golden', color: '#FFD700' },
237
+ { symbol: '🌈', mode: 'Rainbow', color: 'inherit' }
238
+ ].map(({ symbol, mode, color: iconColor }) => {
239
+ const isActive = variantFilter === mode;
240
+ const isRainbow = mode === 'Rainbow';
241
+ const isGold = mode === 'Golden';
242
+
243
+ return (
244
+ <button
245
+ key={mode}
246
+ onClick={() => setVariantFilter(mode as any)}
247
+ className={(isActive && isRainbow) ? "sheen-effect" : ""}
248
+ style={{
249
+ width: "48px",
250
+ height: "48px",
251
+ borderRadius: "50%",
252
+ border: "3px solid #333",
253
+ backgroundColor: isActive
254
+ ? (mode === 'Normal' ? "#e0e0e0" : "#333")
255
+ : "#fff",
256
+ color: isActive
257
+ ? (mode === 'Normal' ? "#333" : "#fff")
258
+ : "#333",
259
+ fontSize: "1.2rem",
260
+ fontWeight: "bold",
261
+ cursor: "pointer",
262
+ display: "flex",
263
+ alignItems: "center",
264
+ justifyContent: "center",
265
+ boxShadow: "0 3px 0 #ccc",
266
+ filter: "none",
267
+ position: 'relative',
268
+ overflow: 'hidden',
269
+ padding: 0
270
+ }}
271
+ title={mode}
272
+ >
273
+ {isGold ? (
274
+ <img
275
+ src="./assets/gold_variant_icon.png"
276
+ alt="Gold"
277
+ style={{
278
+ width: '70%',
279
+ height: '70%',
280
+ objectFit: 'contain',
281
+ filter: isActive ? 'none' : 'grayscale(1) opacity(0.5)'
282
+ }}
283
+ />
284
+ ) : (
285
+ <span style={{
286
+ filter: mode === 'Golden' && !isActive ? "grayscale(1)" : "none",
287
+ color: (isActive && mode !== 'Rainbow' && mode !== 'Normal') ? iconColor : 'inherit'
288
+ }}>
289
+ {symbol}
290
+ </span>
291
+ )}
292
+ </button>
293
+ )
294
+ })}
295
+
296
+ {/* Shiny Toggle */}
297
+ {/* Shiny Toggle */}
298
+ <button
299
+ onClick={() => setShinyFilter(!shinyFilter)}
300
+ className={shinyFilter ? "sheen-effect" : ""}
301
+ style={{
302
+ width: "48px",
303
+ height: "48px",
304
+ borderRadius: "50%",
305
+ border: "3px solid #333",
306
+ backgroundColor: shinyFilter ? "#333" : "#fff",
307
+ color: shinyFilter ? "#fff" : "#333",
308
+ fontSize: "1.2rem",
309
+ fontWeight: "bold",
310
+ cursor: "pointer",
311
+ display: "flex",
312
+ alignItems: "center",
313
+ justifyContent: "center",
314
+ boxShadow: "0 3px 0 #ccc",
315
+ position: 'relative',
316
+ overflow: 'hidden'
317
+ }}
40
318
  >
41
- {configName}
42
- </Link>
43
- </li>
44
- ))}
45
- </ul>
46
- </div>
319
+
320
+ </button>
321
+ </div>
322
+ )}
323
+
324
+
325
+
326
+ {/* Search Bar */}
327
+ <div style={{ position: "relative" }}>
328
+ <input
329
+ type="text"
330
+ placeholder="Search"
331
+ value={searchTerm}
332
+ onChange={(e) => setSearchTerm(e.target.value)}
333
+ style={{
334
+ padding: "10px 20px",
335
+ borderRadius: "50px",
336
+ border: "3px solid #ccc",
337
+ outline: "none",
338
+ fontSize: "1.2rem",
339
+ width: "200px",
340
+ fontWeight: "800",
341
+ backgroundColor: "#fff",
342
+ color: "#ccc",
343
+ textAlign: "right"
344
+ }}
345
+ />
346
+ </div>
347
+
348
+ {/* Red Close Button */}
349
+ <button
350
+ onClick={() => navigate("/collections")}
351
+ style={{
352
+ width: "48px",
353
+ height: "48px",
354
+ borderRadius: "12px",
355
+ backgroundColor: "#ff0055", // Hot pink/red
356
+ color: "white",
357
+ border: "4px solid #900", // Dark red border
358
+ fontSize: "24px",
359
+ fontWeight: "900",
360
+ display: "flex",
361
+ alignItems: "center",
362
+ justifyContent: "center",
363
+ cursor: "pointer",
364
+ boxShadow: "inset 0 4px 4px rgba(255,255,255,0.4), 0 4px 0 #500",
365
+ }}
366
+ >
367
+ X
368
+ </button>
369
+ </div>
370
+
371
+ {/* Virtualized and AutoSized Content */}
372
+ <div style={{ height: "calc(100vh - 80px)", width: "100%" }}>
373
+ {/* @ts-ignore */}
374
+ <AutoSizer style={{ width: "100%", height: "100%" }} renderProp={({ height, width }: { height: number; width: number }) => {
375
+ if (!height || !width) return <div style={{ color: 'red' }}>AutoSizer returned 0 dimensions</div>;
376
+ const GAP = 10;
377
+ const MIN_COL_WIDTH = 150;
378
+
379
+ const columnCount = Math.floor(width / MIN_COL_WIDTH) || 1;
380
+ const columnWidth = width / columnCount;
381
+ const rowHeight = 220; // Increased to ensure no cutoff
382
+ const rowCount = Math.ceil(finalItems.length / columnCount);
383
+
384
+ return (
385
+ // @ts-ignore
386
+ <FixedSizeGrid
387
+ columnCount={columnCount}
388
+ columnWidth={columnWidth}
389
+ height={height}
390
+ rowCount={rowCount}
391
+ rowHeight={rowHeight}
392
+ width={width}
393
+ cellComponent={GridCellRenderer}
394
+ cellProps={{
395
+ items: finalItems,
396
+ columnCount,
397
+ navigate,
398
+ collectionName,
399
+ variantFilter,
400
+ shinyFilter,
401
+ resolveIcon,
402
+ GAP
403
+ }}
404
+ />
405
+ );
406
+ }} />
407
+ </div>
408
+
409
+ </div >
47
410
  );
48
411
  };
49
412
 
@@ -1,33 +1,142 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { Link } from "react-router-dom";
3
3
  import { PetSimulator99API, CollectionName } from "ps99-api";
4
+ import { COLLECTION_ICONS } from "../constants/collectionIcons";
4
5
 
5
6
  const CollectionsIndex: React.FC = () => {
6
- const [collections, setCollections] = useState<CollectionName[]>([]);
7
+ const [collections, setCollections] = useState<CollectionName[]>([]);
7
8
 
8
- useEffect(() => {
9
- const fetchCollections = async () => {
10
- const api = new PetSimulator99API();
11
- const response = await api.getCollections();
12
- if (response.status === "ok") {
13
- setCollections(response.data);
14
- }
15
- };
16
- fetchCollections();
17
- }, []);
9
+ useEffect(() => {
10
+ const fetchCollections = async () => {
11
+ const api = new PetSimulator99API();
12
+ const response = await api.getCollections();
13
+ if (response.status === "ok") {
14
+ setCollections(response.data);
15
+ }
16
+ };
17
+ fetchCollections();
18
+ }, []);
18
19
 
19
- return (
20
- <div>
21
- <h2>Collections</h2>
22
- <ul>
23
- {collections.map((collection, index) => (
24
- <li key={index}>
25
- <Link to={`/collections/${collection}`}>{collection}</Link>
26
- </li>
27
- ))}
28
- </ul>
29
- </div>
30
- );
20
+ return (
21
+ <div
22
+ style={{
23
+ display: "flex",
24
+ justifyContent: "center",
25
+ alignItems: "center",
26
+ minHeight: "calc(100vh - 140px)", // Matches CollectionsLayout outer container spacing
27
+ padding: "20px",
28
+ // backgroundColor: "rgba(0,0,0,0.05)", // REMOVED
29
+ }}
30
+ >
31
+ <div style={{
32
+ width: "100%",
33
+ maxWidth: "1200px", // Match CollectionsLayout
34
+ height: "80vh", // Match CollectionsLayout
35
+ backgroundColor: "#fff",
36
+ borderRadius: "24px",
37
+ boxShadow: "0 10px 40px rgba(0,0,0,0.2)",
38
+ overflow: "hidden", // Important for internal scrolling
39
+ position: "relative",
40
+ border: "4px solid #333",
41
+ display: "flex",
42
+ flexDirection: "column",
43
+ }}>
44
+ <div style={{ position: "relative", textAlign: "center", margin: "20px 0" }}>
45
+ <h1 style={{
46
+ fontFamily: "'Fredoka One', cursive, sans-serif",
47
+ fontSize: "3rem",
48
+ color: "#333",
49
+ margin: 0,
50
+ textShadow: "4px 4px 0px #eee",
51
+ display: "inline-block",
52
+ }}>
53
+ Select a Collection
54
+ </h1>
55
+
56
+ {/* Home Button (X) */}
57
+ <Link
58
+ to="/"
59
+ style={{
60
+ position: "absolute",
61
+ right: "20px",
62
+ top: "50%",
63
+ transform: "translateY(-50%)",
64
+ width: "48px",
65
+ height: "48px",
66
+ borderRadius: "12px",
67
+ backgroundColor: "#ff0055",
68
+ color: "white",
69
+ border: "4px solid #900",
70
+ fontSize: "24px",
71
+ fontWeight: "900",
72
+ display: "flex",
73
+ alignItems: "center",
74
+ justifyContent: "center",
75
+ textDecoration: "none",
76
+ boxShadow: "inset 0 4px 4px rgba(255,255,255,0.4), 0 4px 0 #500",
77
+ }}
78
+ >
79
+ X
80
+ </Link>
81
+ </div>
82
+
83
+ <div style={{
84
+ display: "grid",
85
+ gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
86
+ gap: "20px",
87
+ padding: "0 40px 40px 40px",
88
+ overflowY: "auto", // Allow grid to scroll INSIDE the box
89
+ flex: 1,
90
+ }}>
91
+ {collections.map((collection, index) => {
92
+ const icon = COLLECTION_ICONS[collection] || "📦";
93
+
94
+ return (
95
+ <Link
96
+ to={`/collections/${collection}`}
97
+ key={index}
98
+ style={{
99
+ display: "flex",
100
+ flexDirection: "column",
101
+ alignItems: "center",
102
+ justifyContent: "center",
103
+ backgroundColor: "#fff",
104
+ borderRadius: "24px",
105
+ padding: "15px",
106
+ textDecoration: "none",
107
+ border: "4px solid #333",
108
+ boxShadow: "0 6px 0 #ccc",
109
+ transition: "transform 0.1s ease, box-shadow 0.1s ease",
110
+ aspectRatio: "1/1",
111
+ }}
112
+ onMouseEnter={(e) => {
113
+ e.currentTarget.style.transform = "translateY(4px)";
114
+ e.currentTarget.style.boxShadow = "0 2px 0 #ccc";
115
+ }}
116
+ onMouseLeave={(e) => {
117
+ e.currentTarget.style.transform = "translateY(0)";
118
+ e.currentTarget.style.boxShadow = "0 6px 0 #ccc";
119
+ }}
120
+ >
121
+ <span style={{ fontSize: "3rem", marginBottom: "8px" }}>
122
+ {icon}
123
+ </span>
124
+ <span style={{
125
+ fontSize: "1.2rem",
126
+ fontWeight: "900",
127
+ color: "#333",
128
+ textAlign: "center",
129
+ fontFamily: "'Fredoka One', cursive, sans-serif",
130
+ }}>
131
+ {collection}
132
+ </span>
133
+ </Link>
134
+ );
135
+ })}
136
+ </div>
137
+ </div>
138
+ </div>
139
+ );
31
140
  };
32
141
 
33
142
  export default CollectionsIndex;
@@ -0,0 +1,50 @@
1
+ import React from "react";
2
+ import { Outlet } from "react-router-dom";
3
+ // import Sidebar from "./Sidebar"; // REMOVED per user request
4
+ import { useParams } from "react-router-dom";
5
+ import { CollectionDataProvider } from "../context/CollectionDataContext";
6
+
7
+ const CollectionsLayout: React.FC = () => {
8
+ const { collectionName } = useParams<{ collectionName: string }>();
9
+
10
+ return (
11
+ <div
12
+ style={{
13
+ display: "flex",
14
+ justifyContent: "center",
15
+ alignItems: "center",
16
+ minHeight: "calc(100vh - 140px)", // Matches previous main container spacing
17
+ padding: "20px",
18
+ // backgroundColor: "rgba(0,0,0,0.05)", // REMOVED
19
+ }}
20
+ >
21
+ <CollectionDataProvider>
22
+ {/* Main Window Container - Lifted from CollectionConfigIndex */}
23
+ <div
24
+ style={{
25
+ display: "flex",
26
+ width: "100%",
27
+ maxWidth: "1200px",
28
+ height: "80vh",
29
+ backgroundColor: "#fff",
30
+ borderRadius: "24px",
31
+ boxShadow: "0 10px 40px rgba(0,0,0,0.2)",
32
+ overflow: "hidden",
33
+ position: "relative",
34
+ border: "4px solid #333",
35
+ }}
36
+ >
37
+ {/* Persistent Sidebar REMOVED */}
38
+ {/* <Sidebar currentCollection={collectionName} /> */}
39
+
40
+ {/* Content Area */}
41
+ <div style={{ flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }}>
42
+ <Outlet />
43
+ </div>
44
+ </div>
45
+ </CollectionDataProvider>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ export default CollectionsLayout;