diff --git a/package.json b/package.json index cb8ab1a..2068385 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-tooltip": "^1.0.7", "@tanstack/react-query": "^4.20.0", "@tanstack/react-query-devtools": "^4.32.0", "@trpc/client": "^10.9.0", diff --git a/public/cats/cat05_gifs/cat05_attack_12fps.gif b/public/cats/cat05_gifs/cat05_attack_12fps.gif new file mode 100644 index 0000000..f4713b6 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_attack_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_crouch_8fps.gif b/public/cats/cat05_gifs/cat05_crouch_8fps.gif new file mode 100644 index 0000000..91cd158 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_crouch_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_dash_12fps.gif b/public/cats/cat05_gifs/cat05_dash_12fps.gif new file mode 100644 index 0000000..1ca45e8 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_dash_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_die_12fps.gif b/public/cats/cat05_gifs/cat05_die_12fps.gif new file mode 100644 index 0000000..3d17015 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_die_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_fall_12fps.gif b/public/cats/cat05_gifs/cat05_fall_12fps.gif new file mode 100644 index 0000000..f7923f0 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_fall_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_fright_12fps.gif b/public/cats/cat05_gifs/cat05_fright_12fps.gif new file mode 100644 index 0000000..e484472 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_fright_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_hurt_12fps.gif b/public/cats/cat05_gifs/cat05_hurt_12fps.gif new file mode 100644 index 0000000..71a8d8e Binary files /dev/null and b/public/cats/cat05_gifs/cat05_hurt_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_idle_8fps.gif b/public/cats/cat05_gifs/cat05_idle_8fps.gif new file mode 100644 index 0000000..aeb2b94 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_idle_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_idle_blink_8fps.gif b/public/cats/cat05_gifs/cat05_idle_blink_8fps.gif new file mode 100644 index 0000000..f75b4c8 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_idle_blink_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_jump_12fps.gif b/public/cats/cat05_gifs/cat05_jump_12fps.gif new file mode 100644 index 0000000..0fc6f84 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_jump_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_land_12fps.gif b/public/cats/cat05_gifs/cat05_land_12fps.gif new file mode 100644 index 0000000..9c6ef4d Binary files /dev/null and b/public/cats/cat05_gifs/cat05_land_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_ledgeclimb_12fps.gif b/public/cats/cat05_gifs/cat05_ledgeclimb_12fps.gif new file mode 100644 index 0000000..2c89a08 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_ledgeclimb_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_ledgeclimb_struggle_12fps.gif b/public/cats/cat05_gifs/cat05_ledgeclimb_struggle_12fps.gif new file mode 100644 index 0000000..b9f5967 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_ledgeclimb_struggle_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_ledgegrab_12fps.gif b/public/cats/cat05_gifs/cat05_ledgegrab_12fps.gif new file mode 100644 index 0000000..1d8fd05 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_ledgegrab_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_ledgeidle_8fps.gif b/public/cats/cat05_gifs/cat05_ledgeidle_8fps.gif new file mode 100644 index 0000000..3e576f6 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_ledgeidle_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_liedown_8fps.gif b/public/cats/cat05_gifs/cat05_liedown_8fps.gif new file mode 100644 index 0000000..d7f7489 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_liedown_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_run_12fps.gif b/public/cats/cat05_gifs/cat05_run_12fps.gif new file mode 100644 index 0000000..edcf611 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_run_12fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_sit_8fps.gif b/public/cats/cat05_gifs/cat05_sit_8fps.gif new file mode 100644 index 0000000..ea3f3a2 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_sit_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_sneak_8fps.gif b/public/cats/cat05_gifs/cat05_sneak_8fps.gif new file mode 100644 index 0000000..96ba512 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_sneak_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_walk_8fps.gif b/public/cats/cat05_gifs/cat05_walk_8fps.gif new file mode 100644 index 0000000..d2a86d9 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_walk_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_wallclimb_8fps.gif b/public/cats/cat05_gifs/cat05_wallclimb_8fps.gif new file mode 100644 index 0000000..d91a480 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_wallclimb_8fps.gif differ diff --git a/public/cats/cat05_gifs/cat05_wallgrab_8fps.gif b/public/cats/cat05_gifs/cat05_wallgrab_8fps.gif new file mode 100644 index 0000000..7898018 Binary files /dev/null and b/public/cats/cat05_gifs/cat05_wallgrab_8fps.gif differ diff --git a/src/components/Pets/Cat.tsx b/src/components/Pets/Cat.tsx new file mode 100644 index 0000000..eab2416 --- /dev/null +++ b/src/components/Pets/Cat.tsx @@ -0,0 +1,180 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +import type { Action, CatType } from "./Cats"; +import { cat1, cat5, CatTalk } from "./Cats"; + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +import Image from "next/image"; + +// Pixels per second +const DEFAULT_SPEED = 30; +const RUNNING_SPEED = 60; + +type CatProps = { + id: string; + name: string; + onClick: () => void; +}; + +function Cat({ name }: CatProps) { + let currPet: CatType; + switch (name) { + case "cat1": + currPet = cat1; + break; + case "cat5": + currPet = cat5; + break; + default: + currPet = cat1; + } + const petRef = useRef(null); + const actionRef = useRef("idle"); + + const [position, setPosition] = useState({ x: 90, y: -4 }); + const [moveDuration, setMoveDuration] = useState(0); + const [faceRight, setFaceRight] = useState(true); + const [currAction, setCurrAction] = useState("laying"); + const [catSpeech, setCatSpeech] = useState(""); + + const getRelativePosition = useCallback(() => { + if (!petRef.current) return { x: 0, y: 0 }; + + const petRect = petRef.current.getBoundingClientRect(); + const container = petRef.current.parentNode as HTMLElement; + const containerRect = container.getBoundingClientRect(); + + return { + x: petRect.left - containerRect.left, + y: petRect.top - containerRect.top, + }; + }, []); + + const rest = useCallback(() => { + actionRef.current = "idle"; + setCurrAction("idle"); + setMoveDuration(0); + }, []); + + const move = useCallback(() => { + if (actionRef.current === "walking") { + return; + } + + actionRef.current = "walking"; + + // First determine the direction + let direction = Math.random() > 0.5 ? 1 : -1; + // Then determine the distance without running into the bounding box + const currentPosition = getRelativePosition(); + const distance = Math.floor(Math.random() * 200 + 50); + const futureX = currentPosition.x + direction * distance; + + // Collision detection with container boundaries + const container = petRef.current?.parentNode as HTMLElement; + if (container) { + const containerWidth = container.offsetWidth; + + if (futureX < 0 || futureX > containerWidth - 40) { + direction *= -1; // Reverse the direction + } + } + // set new direction + setFaceRight(direction === 1); + + const gottaRun = distance > 150; + setCurrAction(gottaRun ? "running" : "walking"); + + // Calculate duration + const moveDuration = gottaRun + ? distance / RUNNING_SPEED + : distance / DEFAULT_SPEED; + setMoveDuration(moveDuration); + + // Update position with possibly reversed direction + setPosition((prev) => { + return { x: prev.x + direction * distance, y: prev.y }; + }); + + // Set a timeout to stop walking after the calculated duration + setTimeout(() => { + rest(); + }, moveDuration * 1000); + }, [getRelativePosition, rest]); + + // Simulate some autonomous behavior + useEffect(() => { + // Interval to change the cat's behavior periodically + + const interval = setInterval(() => { + if (actionRef.current === "walking" || actionRef.current === "running") { + return; + } + const shouldMove = Math.random() > 0.5; + if (shouldMove) { + move(); + } else { + rest(); + } + }, 7000); + + return () => clearInterval(interval); + }, [move, rest]); + + const generateMessage = useCallback(() => { + const randomIndex = Math.floor(Math.random() * CatTalk.length); + setCatSpeech(CatTalk[randomIndex] as string); + }, []); + + return ( +
+ cat + + { + if (open) { + generateMessage(); + } + }} + > + +
+ + +

{catSpeech}

+
+ + +
+ ); +} + +export default Cat; diff --git a/src/components/Pets/Cats.ts b/src/components/Pets/Cats.ts new file mode 100644 index 0000000..41a8eb8 --- /dev/null +++ b/src/components/Pets/Cats.ts @@ -0,0 +1,43 @@ +import cat1Walking from "public/cats/cat01_gifs/cat01_walk_8fps.gif"; +import cat1Idle from "public/cats/cat01_gifs/cat01_idle_blink_8fps.gif"; +import cat1Laying from "public/cats/cat01_gifs/cat01_liedown_8fps.gif"; +import cat1Running from "public/cats/cat01_gifs/cat01_run_12fps.gif"; + +import cat5Walking from "public/cats/cat05_gifs/cat05_walk_8fps.gif"; +import cat5Idle from "public/cats/cat05_gifs/cat05_idle_blink_8fps.gif"; +import cat5Laying from "public/cats/cat05_gifs/cat05_liedown_8fps.gif"; +import cat5Running from "public/cats/cat05_gifs/cat05_run_12fps.gif"; + +import type { StaticImageData } from "next/image"; + +export type Action = "idle" | "walking" | "laying" | "running"; + +export type CatType = { + [K in Action]: StaticImageData; +}; + +const cat1: CatType = { + walking: cat1Walking, + idle: cat1Idle, + laying: cat1Laying, + running: cat1Running, +}; + +const cat5: CatType = { + walking: cat5Walking, + idle: cat5Idle, + laying: cat5Laying, + running: cat5Running, +}; + +// TODO: make very dramatic reminders to complete habits +const CatTalk = [ + "Meow", + "Purrrrr", + "Hi, I'm Puffin!", + "I love you!", + "Feed me!", + "I'm hungry~", +]; + +export { cat1, cat5, CatTalk }; diff --git a/src/components/Pets/PetsContainer.tsx b/src/components/Pets/PetsContainer.tsx index 60bc44f..8569f9f 100644 --- a/src/components/Pets/PetsContainer.tsx +++ b/src/components/Pets/PetsContainer.tsx @@ -1,9 +1,30 @@ -import { Pets } from "./Pets"; +import { useState } from "react"; +import Cat from "./Cat"; function PetsContainer() { + const [pets] = useState([ + // { + // name: "cat1", + // id: "1", + // onClick: () => console.log("pet clicked"), + // }, + { + name: "cat5", + id: "2", + onClick: () => console.log("pet clicked"), + }, + ]); + return ( -
- +
+ {pets.map((pet) => ( + + ))}
); } diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..71a8a30 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/src/pages/tracker/[uid].tsx b/src/pages/tracker/[uid].tsx index 4658b8f..ece7a99 100644 --- a/src/pages/tracker/[uid].tsx +++ b/src/pages/tracker/[uid].tsx @@ -11,6 +11,13 @@ import HabitLoading from "@/components/HabitLoading"; import Refresh from "@/components/Refresh"; import TrackerBackground from "@/components/TrackerBackground"; import { DatePicker2 } from "@/components/ui/datepicker"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { PetsContainer } from "@/components/Pets/PetsContainer"; + import { ClipboardDocumentCheckIcon, ClipboardDocumentIcon, @@ -27,16 +34,6 @@ import { type NextPage } from "next"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { useTime } from "@/hooks/useTime"; -import { Button } from "@/components/ui/button"; -import Image from "next/image"; - -import cat1 from "public/cats/cat01_gifs/cat01_walk_8fps.gif"; -import { Pets } from "@/components/Pets/Pets"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; const habitAPI = api.habit; @@ -182,6 +179,7 @@ SN_End: ${!!serverTime ? serverTime.newDayEnd.toISOString() : "undefined"} {slides} + {/* Debug info popover */}