Add species-face picker to item page previews
Note that it doesn't do any compatibility checking, graying out, hiding unneeded faces, etc. They just exist now is all!
This commit is contained in:
parent
7092d86b76
commit
f50de9b11e
1 changed files with 528 additions and 75 deletions
|
@ -14,6 +14,10 @@ import {
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useTheme,
|
useTheme,
|
||||||
useToast,
|
useToast,
|
||||||
|
useToken,
|
||||||
|
Stack,
|
||||||
|
Wrap,
|
||||||
|
WrapItem,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
@ -27,7 +31,7 @@ import { useQuery, useMutation } from "@apollo/client";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
|
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
|
||||||
import { Delay, usePageTitle } from "./util";
|
import { Delay, ErrorMessage, usePageTitle } from "./util";
|
||||||
import {
|
import {
|
||||||
itemAppearanceFragment,
|
itemAppearanceFragment,
|
||||||
petAppearanceFragment,
|
petAppearanceFragment,
|
||||||
|
@ -601,90 +605,539 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
const isIncompatible = Array.isArray(layers) && layers.length === 0;
|
const isIncompatible = Array.isArray(layers) && layers.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack spacing="3" width="100%">
|
<Stack direction={{ base: "column", md: "row" }} spacing="8">
|
||||||
<AspectRatio
|
<VStack spacing="3" width="100%">
|
||||||
width="300px"
|
<AspectRatio
|
||||||
maxWidth="100%"
|
width="300px"
|
||||||
ratio="1"
|
maxWidth="100%"
|
||||||
border="1px"
|
ratio="1"
|
||||||
borderColor={borderColor}
|
border="1px"
|
||||||
transition="border-color 0.2s"
|
borderColor={borderColor}
|
||||||
borderRadius="lg"
|
transition="border-color 0.2s"
|
||||||
boxShadow="lg"
|
borderRadius="lg"
|
||||||
overflow="hidden"
|
boxShadow="lg"
|
||||||
>
|
overflow="hidden"
|
||||||
<Box>
|
>
|
||||||
<OutfitPreview
|
<Box>
|
||||||
|
<OutfitPreview
|
||||||
|
speciesId={petState.speciesId}
|
||||||
|
colorId={petState.colorId}
|
||||||
|
pose={petState.pose}
|
||||||
|
appearanceId={petState.appearanceId}
|
||||||
|
wornItemIds={[itemId]}
|
||||||
|
isLoading={loading}
|
||||||
|
spinnerVariant="corner"
|
||||||
|
loadingDelayMs={2000}
|
||||||
|
engine="canvas"
|
||||||
|
onChangeHasAnimations={setHasAnimations}
|
||||||
|
/>
|
||||||
|
{hasAnimations && (
|
||||||
|
<PlayPauseButton
|
||||||
|
isPaused={isPaused}
|
||||||
|
onClick={() => setIsPaused(!isPaused)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</AspectRatio>
|
||||||
|
<Box display="flex" width="100%" alignItems="center">
|
||||||
|
<Box
|
||||||
|
// This empty box grows at the same rate as the box on the right, so
|
||||||
|
// the middle box will be centered, if there's space!
|
||||||
|
flex="1 0 0"
|
||||||
|
/>
|
||||||
|
<SpeciesColorPicker
|
||||||
speciesId={petState.speciesId}
|
speciesId={petState.speciesId}
|
||||||
colorId={petState.colorId}
|
colorId={petState.colorId}
|
||||||
pose={petState.pose}
|
pose={petState.pose}
|
||||||
appearanceId={petState.appearanceId}
|
idealPose={idealPose}
|
||||||
wornItemIds={[itemId]}
|
onChange={(species, color, _, closestPose) => {
|
||||||
isLoading={loading}
|
setPetState({
|
||||||
spinnerVariant="corner"
|
speciesId: species.id,
|
||||||
loadingDelayMs={2000}
|
colorId: color.id,
|
||||||
engine="canvas"
|
pose: closestPose,
|
||||||
onChangeHasAnimations={setHasAnimations}
|
appearanceId: null,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
showPlaceholders
|
||||||
|
// This is just a UX affordance: while we could handle invalid states
|
||||||
|
// from a UI perspective, we figure that, if a pet preview is already
|
||||||
|
// visible and responsive to changes, it feels better to treat the
|
||||||
|
// changes as atomic and always-valid.
|
||||||
|
stateMustAlwaysBeValid
|
||||||
/>
|
/>
|
||||||
{hasAnimations && (
|
<Box flex="1 0 0" lineHeight="1">
|
||||||
<IconButton
|
{isIncompatible && (
|
||||||
icon={isPaused ? <MdPlayArrow /> : <MdPause />}
|
<Tooltip label="No data yet" placement="top">
|
||||||
aria-label={isPaused ? "Play" : "Pause"}
|
<WarningIcon
|
||||||
onClick={() => setIsPaused(!isPaused)}
|
color={errorColor}
|
||||||
borderRadius="full"
|
transition="color 0.2"
|
||||||
boxShadow="md"
|
marginLeft="2"
|
||||||
color="gray.50"
|
/>
|
||||||
backgroundColor="blackAlpha.700"
|
</Tooltip>
|
||||||
position="absolute"
|
)}
|
||||||
bottom="2"
|
</Box>
|
||||||
left="2"
|
|
||||||
_hover={{ backgroundColor: "blackAlpha.900" }}
|
|
||||||
_focus={{ backgroundColor: "blackAlpha.900" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</AspectRatio>
|
</VStack>
|
||||||
<Box display="flex" width="100%" alignItems="center">
|
<Box maxWidth="400px">
|
||||||
<Box
|
<SpeciesFacesPicker
|
||||||
// This empty box grows at the same rate as the box on the right, so
|
itemId={itemId}
|
||||||
// the middle box will be centered, if there's space!
|
selectedSpeciesId={petState.speciesId}
|
||||||
flex="1 0 0"
|
onChange={({ speciesId, colorId }) =>
|
||||||
/>
|
|
||||||
<SpeciesColorPicker
|
|
||||||
speciesId={petState.speciesId}
|
|
||||||
colorId={petState.colorId}
|
|
||||||
pose={petState.pose}
|
|
||||||
idealPose={idealPose}
|
|
||||||
onChange={(species, color, _, closestPose) => {
|
|
||||||
setPetState({
|
setPetState({
|
||||||
speciesId: species.id,
|
speciesId,
|
||||||
colorId: color.id,
|
colorId,
|
||||||
pose: closestPose,
|
pose: idealPose,
|
||||||
appearanceId: null,
|
appearanceId: null,
|
||||||
});
|
})
|
||||||
}}
|
}
|
||||||
size="sm"
|
isLoading={loading}
|
||||||
showPlaceholders
|
|
||||||
// This is just a UX affordance: while we could handle invalid states
|
|
||||||
// from a UI perspective, we figure that, if a pet preview is already
|
|
||||||
// visible and responsive to changes, it feels better to treat the
|
|
||||||
// changes as atomic and always-valid.
|
|
||||||
stateMustAlwaysBeValid
|
|
||||||
/>
|
/>
|
||||||
<Box flex="1 0 0" lineHeight="1">
|
|
||||||
{isIncompatible && (
|
|
||||||
<Tooltip label="No data yet" placement="top">
|
|
||||||
<WarningIcon
|
|
||||||
color={errorColor}
|
|
||||||
transition="color 0.2"
|
|
||||||
marginLeft="2"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</VStack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PlayPauseButton({ isPaused, onClick }) {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
icon={isPaused ? <MdPlayArrow /> : <MdPause />}
|
||||||
|
aria-label={isPaused ? "Play" : "Pause"}
|
||||||
|
onClick={onClick}
|
||||||
|
borderRadius="full"
|
||||||
|
boxShadow="md"
|
||||||
|
color="gray.50"
|
||||||
|
backgroundColor="blackAlpha.700"
|
||||||
|
position="absolute"
|
||||||
|
bottom="2"
|
||||||
|
left="2"
|
||||||
|
_hover={{ backgroundColor: "blackAlpha.900" }}
|
||||||
|
_focus={{ backgroundColor: "blackAlpha.900" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpeciesFacesPicker({
|
||||||
|
itemId,
|
||||||
|
selectedSpeciesId,
|
||||||
|
onChange,
|
||||||
|
isLoading,
|
||||||
|
}) {
|
||||||
|
const selectedBorderColor = useColorModeValue("green.600", "green.400");
|
||||||
|
const selectedBackgroundColor = useColorModeValue("green.200", "green.600");
|
||||||
|
const [
|
||||||
|
selectedBorderColorValue,
|
||||||
|
selectedBackgroundColorValue,
|
||||||
|
] = useToken("colors", [selectedBorderColor, selectedBackgroundColor]);
|
||||||
|
|
||||||
|
const allSpeciesFaces = speciesFaces.sort((a, b) =>
|
||||||
|
a.speciesName.localeCompare(b.speciesName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClassNames>
|
||||||
|
{({ css }) => (
|
||||||
|
<Wrap
|
||||||
|
spacing="0"
|
||||||
|
justify="center"
|
||||||
|
// On mobile, give this a scroll container, and some extra padding so
|
||||||
|
// the selected-face effects still fit inside.
|
||||||
|
maxHeight={{ base: "200px", md: "none" }}
|
||||||
|
overflow={{ base: "auto", md: "visible" }}
|
||||||
|
padding={{ base: "8px", md: "0" }}
|
||||||
|
>
|
||||||
|
{allSpeciesFaces.map(({ speciesId, speciesName, colorId, src }) => (
|
||||||
|
<WrapItem
|
||||||
|
key={speciesId}
|
||||||
|
as="label"
|
||||||
|
cursor={isLoading ? "wait" : "pointer"}
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
|
<VisuallyHidden
|
||||||
|
as="input"
|
||||||
|
type="radio"
|
||||||
|
aria-label={speciesName}
|
||||||
|
name="species-faces-picker"
|
||||||
|
value={speciesId}
|
||||||
|
checked={speciesId === selectedSpeciesId}
|
||||||
|
disabled={isLoading}
|
||||||
|
onChange={() => onChange({ speciesId, colorId })}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
overflow="hidden"
|
||||||
|
transition="all 0.2s"
|
||||||
|
className={css`
|
||||||
|
input:checked + & {
|
||||||
|
background: ${selectedBackgroundColorValue};
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: ${selectedBorderColorValue} 0 0 0 3px;
|
||||||
|
transform: scale(1.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
as="img"
|
||||||
|
src={src}
|
||||||
|
alt={speciesName}
|
||||||
|
width={50}
|
||||||
|
height={50}
|
||||||
|
filter="saturate(90%)"
|
||||||
|
opacity="0.9"
|
||||||
|
transition="all 0.2s"
|
||||||
|
className={css`
|
||||||
|
input:checked + * > & {
|
||||||
|
opacity: 1;
|
||||||
|
filter: saturate(110%);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</WrapItem>
|
||||||
|
))}
|
||||||
|
</Wrap>
|
||||||
|
)}
|
||||||
|
</ClassNames>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: I'm just hardcoding all this, rather than connecting up to the
|
||||||
|
// database and adding a loading state. Tbh I'm not sure it's a good idea
|
||||||
|
// to load this dynamically until we have SSR to make it come in fast!
|
||||||
|
// And it's not so bad if this gets out of sync with the database,
|
||||||
|
// because the SpeciesColorPicker will still be usable!
|
||||||
|
const colors = { BLUE: "8", RED: "61", GREEN: "34", YELLOW: "84" };
|
||||||
|
const speciesFaces = [
|
||||||
|
{
|
||||||
|
speciesId: "1",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/obxdjm88/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Acara",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "2",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/n9ozx4z5/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Aisha",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "3",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/kfonqhdc/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Blumaroo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "4",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/sc2hhvhn/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Bori",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "5",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/wqz8xn4t/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Bruce",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "6",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/jc9klfxm/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Buzz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "7",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/4lrb4n3f/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Chia",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "8",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/bdml26md/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Chomby",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "9",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/xl6msllv/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Cybunny",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "10",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/bob39shq/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Draik",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "11",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/jhhhbrww/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Elephante",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "12",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/6kngmhvs/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Eyrie",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "13",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/47vt32x2/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Flotsam",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "14",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/5nrd2lvd/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Gelert",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "15",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/6c275jcg/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Gnorbu",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "16",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/j7q65fv4/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Grarrl",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "17",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/5xn4kjf8/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Grundo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "18",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/jsfvcqwt/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Hissi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "19",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/w32r74vo/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Ixi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "20",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/kz43rnld/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Jetsam",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "21",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/m267j935/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Jubjub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "22",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/4gsrb59g/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Kacheek",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "23",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/ktlxmrtr/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Kau",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "24",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/42j5q3zx/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Kiko",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "25",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/ncfn87wk/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Koi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "26",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/omx9c876/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Korbat",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "27",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/rfsbh59t/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Kougra",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "28",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/hxgsm5d4/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Krawk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "29",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/blxmjgbk/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Kyrii",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "30",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/8r94jhfq/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Lenny",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "31",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/z42535zh/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Lupe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "32",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/qgg6z8s7/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Lutari",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "33",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/kk2nn2jr/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Meerca",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "34",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/jgkoro5z/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Moehog",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "35",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/xwlo9657/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Mynci",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "36",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/bx7fho8x/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Nimmo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "37",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/rjzmx24v/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Ogrin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "38",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/kokc52kh/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Peophin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "39",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/fw6lvf3c/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Poogle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "40",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/tjhwbro3/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Pteri",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "41",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/jdto7mj4/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Quiggle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "42",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/qsgbm5f6/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Ruki",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "43",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/hkjoncsx/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Scorchio",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "44",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/mmvn4tkg/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Shoyru",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "45",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/fc4cxk3t/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Skeith",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "46",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/84gvowmj/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Techo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "47",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/jd433863/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Tonu",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "48",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/q39wn6vq/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Tuskaninny",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "49",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/njzvoflw/1/1.png",
|
||||||
|
colorId: colors.GREEN,
|
||||||
|
speciesName: "Uni",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "50",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/rox4mgh5/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Usul",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "51",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/dnr2kj4b/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Wocky",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "52",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/tdkqr2b6/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Xweetok",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "53",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/h95cs547/1/1.png",
|
||||||
|
colorId: colors.RED,
|
||||||
|
speciesName: "Yurble",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "54",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/x8c57g2l/1/1.png",
|
||||||
|
colorId: colors.BLUE,
|
||||||
|
speciesName: "Zafara",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speciesId: "55",
|
||||||
|
src: "https://pets.neopets-asset-proxy.openneo.net/cp/xkntzsww/1/1.png",
|
||||||
|
colorId: colors.YELLOW,
|
||||||
|
speciesName: "Vandagyre",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default ItemPage;
|
export default ItemPage;
|
||||||
|
|
Loading…
Reference in a new issue