diff --git a/src/components/AmbassadorCard.tsx b/src/components/AmbassadorCard.tsx index 892c200a..ea98a31c 100644 --- a/src/components/AmbassadorCard.tsx +++ b/src/components/AmbassadorCard.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, type Ref } from "react"; +import { useCallback, useEffect, useRef, type Ref, useState } from "react"; import type { CreateTypes } from "canvas-confetti"; import Confetti from "react-canvas-confetti"; @@ -16,6 +16,7 @@ import Ring from "./Ring"; import moderatorBadge from "../assets/mod.svg"; import partyHat from "../assets/party.svg"; +import Dogear from "./Dogear"; const headingClass = "text-base text-alveus-green-400"; const rowClass = "flex flex-wrap gap-x-6 gap-y-1 [&>*]:mr-auto"; @@ -41,6 +42,16 @@ export default function AmbassadorCard(props: AmbassadorCardProps) { } = props; const ambassador = useAmbassador(ambassadorKey); + const [dogearIsHovered, setDogearIsHovered] = useState(false); + + const [flipped, setFlipped] = useState(false); + + const flipCard = useCallback(() => { + setFlipped((prev) => { + return !prev; + }); + }, []); + const mod = window?.Twitch?.ext?.viewer?.role === "broadcaster" || window?.Twitch?.ext?.viewer?.role === "moderator"; @@ -125,10 +136,29 @@ export default function AmbassadorCard(props: AmbassadorCardProps) { className={classes( "relative flex max-h-full min-h-[min(28rem,100%)] w-80 max-w-full flex-col justify-start rounded-lg bg-alveus-green-900 align-top text-xs shadow-xl", className, + // TODO + dogearIsHovered && "", )} + // If the card is flippable, we need to cut away at the card to make room for the dogear. + style={ + ambassador.fact + ? { + clipPath: + "polygon(0 0, calc(100% - 2.5rem) 0, 100% 2.5rem, 100% 100%, 0 100%)", + } + : undefined + } ref={callbackRef} {...extras} > + {ambassador.fact && ( + setDogearIsHovered(true)} + onMouseLeave={() => setDogearIsHovered(false)} + onClick={flipCard} + /> + )} + {birthday && ( )} - {ambassador.image.alt} - -
- {onClose && ( - - )} - -

- {ambassador.name} -

-
-
- {mod && ( -
- Moderator badge -

- Show this card to everyone by using{" "} - !{ambassador.commands[0]} in chat. -

-
- )} - -
-

Species

-

{ambassador.species.name}

-

- {ambassador.species.scientificName}{" "} - - ({ambassador.species.class.title}) - -

-
-
-
-

Sex

-

{ambassador.sex || "Unknown"}

-
-
-

Age

-

- {age[0] === "~" && ( - - ~ - - )} - {age.slice(age[0] === "~" ? 1 : 0)} -

+ {!flipped && ( + <> + {ambassador.image.alt} +
+ {onClose && ( + + )} + +

+ {ambassador.name} +

-
-

Birthday

-

- {birth[0] === "~" && ( - - ~ +

+ {mod && ( +
+ Moderator badge +

+ Show this card to everyone by using{" "} + !{ambassador.commands[0]} in chat. +

+
+ )} + +
+

Species

+

{ambassador.species.name}

+

+ {ambassador.species.scientificName}{" "} + + ({ambassador.species.class.title}) - )} - {birth.slice(birth[0] === "~" ? 1 : 0)} -

-
-
+

+
-
-

Story

-

{ambassador.story}

-
+
+
+

Sex

+

{ambassador.sex || "Unknown"}

+
+
+

Age

+

+ {age[0] === "~" && ( + + ~ + + )} + {age.slice(age[0] === "~" ? 1 : 0)} +

+
+
+

Birthday

+

+ {birth[0] === "~" && ( + + ~ + + )} + {birth.slice(birth[0] === "~" ? 1 : 0)} +

+
+
-
-

Conservation Mission

-

{ambassador.mission}

-
+
+

Story

+

{ambassador.story}

+
-
- -
-

Conservation Status

- +
+

Conservation Mission

+

{ambassador.mission}

- -

IUCN: {ambassador.species.iucn.title}

-
-
-

Native To

-

{ambassador.species.native.text}

-
+
+ +
+

Conservation Status

+ +
+
+

IUCN: {ambassador.species.iucn.title}

+
-
-

Species Lifespan

-

- Wild:{" "} - {"wild" in ambassador.species.lifespan && - ambassador.species.lifespan.wild !== undefined ? ( - <> - - ~ - - {stringifyLifespan(ambassador.species.lifespan.wild)} years - - ) : ( - "Unknown" - )} -

-

- Captivity:{" "} - {"captivity" in ambassador.species.lifespan && - ambassador.species.lifespan.captivity !== undefined ? ( - <> - - ~ - - {stringifyLifespan(ambassador.species.lifespan.captivity)}{" "} - years - - ) : ( - "Unknown" + {ambassador.fact && ( +

+ +
+

Did you know?

+ +
+
+
)} -

-
-
- -
-

Arrived at Alveus

-

- {ambassador.arrival - ? formatDate(ambassador.arrival, false) - : "Unknown"} -

-
-
+
+

Native To

+

{ambassador.species.native.text}

+
+ +
+

Species Lifespan

+

+ Wild:{" "} + {"wild" in ambassador.species.lifespan && + ambassador.species.lifespan.wild !== undefined ? ( + <> + + ~ + + {stringifyLifespan(ambassador.species.lifespan.wild)}{" "} + years + + ) : ( + "Unknown" + )} +

+

+ Captivity:{" "} + {"captivity" in ambassador.species.lifespan && + ambassador.species.lifespan.captivity !== undefined ? ( + <> + + ~ + + {stringifyLifespan(ambassador.species.lifespan.captivity)}{" "} + years + + ) : ( + "Unknown" + )} +

+
-
-

- Learn more about {ambassador.name} on the{" "} - +

+
+

Arrived at Alveus

+

+ {ambassador.arrival + ? formatDate(ambassador.arrival, false) + : "Unknown"} +

+
+
+ +
+

+ Learn more about {ambassador.name} on the{" "} + + Alveus Sanctuary website{" "} + + +

+
+
+ + )} + {flipped && ( +
+
+ + +

+ {ambassador.name} +

+
+

+ Did you know?

+

+ {ambassador.fact} +

+
+ +
-
- + )}
diff --git a/src/components/Dogear.tsx b/src/components/Dogear.tsx new file mode 100644 index 00000000..34957d43 --- /dev/null +++ b/src/components/Dogear.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import Tooltip from "./Tooltip"; +import { classes } from "../utils/classes"; + +export default function CardDogear(props: { + onClick?: () => void; + onHover?: () => void; + onMouseLeave?: () => void; +}) { + return ( + + + + ); +} diff --git a/src/hooks/useAmbassadors.tsx b/src/hooks/useAmbassadors.tsx index 2f86eb9f..4245e158 100644 --- a/src/hooks/useAmbassadors.tsx +++ b/src/hooks/useAmbassadors.tsx @@ -53,6 +53,7 @@ const apiAmbassadorSchema = ambassadorSchema.extend({ key: z.string(), title: z.string(), }), + fact: z.string().optional(), }); type Ambassador = z.infer;