diff --git a/src/renderer/src/pages/achievement/achievements.css.ts b/src/renderer/src/pages/achievement/achievements.css.ts index b4de2aed..792548ae 100644 --- a/src/renderer/src/pages/achievement/achievements.css.ts +++ b/src/renderer/src/pages/achievement/achievements.css.ts @@ -2,20 +2,36 @@ import { SPACING_UNIT, vars } from "../../theme.css"; import { style } from "@vanilla-extract/css"; import { recipe } from "@vanilla-extract/recipes"; -export const container = style({ - width: "100%", +export const HERO_HEIGHT = 300; + +export const wrapper = style({ display: "flex", flexDirection: "column", - gap: `${SPACING_UNIT * 2}px`, + overflow: "hidden", + width: "100%", + height: "100%", + transition: "all ease 0.3s", }); export const header = style({ display: "flex", + height: `${HERO_HEIGHT}px`, + minHeight: `${HERO_HEIGHT}px`, gap: `${SPACING_UNIT}px`, flexDirection: "column", + position: "relative", + transition: "all ease 0.2s", + "@media": { + "(min-width: 1250px)": { + height: "350px", + minHeight: "350px", + }, + }, }); export const headerImage = style({ + position: "absolute", + inset: "0", borderRadius: "4px", objectFit: "cover", cursor: "pointer", @@ -23,6 +39,45 @@ export const headerImage = style({ transition: "all ease 0.2s", }); +export const gameLogo = style({ + padding: `${SPACING_UNIT * 2}px`, + width: "300px", +}); + +export const container = style({ + width: "100%", + height: "100%", + display: "flex", + flexDirection: "column", + overflow: "auto", + zIndex: "1", +}); + +export const panel = recipe({ + base: { + width: "100%", + height: "100px", + minHeight: "100px", + padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`, + backgroundColor: vars.color.darkBackground, + display: "flex", + flexDirection: "column", + transition: "all ease 0.2s", + borderBottom: `solid 1px ${vars.color.border}`, + position: "sticky", + overflow: "hidden", + top: "0", + zIndex: "1", + }, + variants: { + stuck: { + true: { + boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.8)", + }, + }, + }, +}); + export const list = style({ listStyle: "none", margin: "0", @@ -30,6 +85,7 @@ export const list = style({ flexDirection: "column", gap: `${SPACING_UNIT * 2}px`, padding: `${SPACING_UNIT * 2}px`, + backgroundColor: vars.color.background, }); export const listItem = style({ diff --git a/src/renderer/src/pages/achievement/achievements.tsx b/src/renderer/src/pages/achievement/achievements.tsx index 7625ad60..457cf938 100644 --- a/src/renderer/src/pages/achievement/achievements.tsx +++ b/src/renderer/src/pages/achievement/achievements.tsx @@ -2,16 +2,15 @@ import { setHeaderTitle } from "@renderer/features"; import { useAppDispatch, useDate } from "@renderer/hooks"; import { steamUrlBuilder } from "@shared"; import type { GameShop, UserAchievement } from "@types"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useNavigate, useSearchParams } from "react-router-dom"; +import { useSearchParams } from "react-router-dom"; import * as styles from "./achievements.css"; -import { - buildGameDetailsPath, - formatDownloadProgress, -} from "@renderer/helpers"; +import { formatDownloadProgress } from "@renderer/helpers"; import { TrophyIcon } from "@primer/octicons-react"; -import { SPACING_UNIT, vars } from "@renderer/theme.css"; +import { vars } from "@renderer/theme.css"; + +const HERO_ANIMATION_THRESHOLD = 25; export function Achievement() { const [searchParams] = useSearchParams(); @@ -21,10 +20,14 @@ export function Achievement() { const userId = searchParams.get("userId"); const displayName = searchParams.get("displayName"); + const heroRef = useRef(null); + const containerRef = useRef(null); + const [isHeaderStuck, setIsHeaderStuck] = useState(false); + const [backdropOpactiy, setBackdropOpacity] = useState(1); + const { t } = useTranslation("achievement"); const { format } = useDate(); - const navigate = useNavigate(); const dispatch = useAppDispatch(); @@ -42,18 +45,30 @@ export function Achievement() { useEffect(() => { if (title) { - dispatch( - setHeaderTitle( - displayName - ? t("user_achievements", { - displayName, - }) - : t("your_achievements") - ) - ); + dispatch(setHeaderTitle(title)); } }, [dispatch, title]); + const onScroll: React.UIEventHandler = (event) => { + const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT; + + const scrollY = (event.target as HTMLDivElement).scrollTop; + const opacity = Math.max( + 0, + 1 - scrollY / (heroHeight - HERO_ANIMATION_THRESHOLD) + ); + + if (scrollY >= heroHeight && !isHeaderStuck) { + setIsHeaderStuck(true); + } + + if (scrollY <= heroHeight && isHeaderStuck) { + setIsHeaderStuck(false); + } + + setBackdropOpacity(opacity); + }; + if (!objectId || !shop || !title) return null; const unlockedAchievementCount = achievements.filter( @@ -62,40 +77,48 @@ export function Achievement() { const totalAchievementCount = achievements.length; - const handleClickGame = () => { - navigate( - buildGameDetailsPath({ - shop: shop as GameShop, - objectId, - title, - }) - ); - }; - return ( -
-
- -
-

{title}

+ + {title} +
+ +
+

+ {displayName + ? t("user_achievements", { + displayName, + }) + : t("your_achievements")} +

@@ -124,29 +147,29 @@ export function Achievement() { className={styles.achievementsProgressBar} />
-
-
    - {achievements.map((achievement, index) => ( -
  • - {achievement.displayName} -
    -

    {achievement.displayName}

    -

    {achievement.description}

    - - {achievement.unlockTime && format(achievement.unlockTime)} - -
    -
  • - ))} -
+
    + {achievements.map((achievement, index) => ( +
  • + {achievement.displayName} +
    +

    {achievement.displayName}

    +

    {achievement.description}

    + + {achievement.unlockTime && format(achievement.unlockTime)} + +
    +
  • + ))} +
+
); }