Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<link
rel="preload"
href="https://cdn.jsdelivr.net/gh/neodgm/[email protected]/neodgm_pro/neodgm_pro.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
>

<meta name="theme-color" content="#7A38FF"> <!-- ๋ธŒ๋ผ์šฐ์ € ํ…Œ๋งˆ ์ƒ‰์ƒ (violet-500) -->
<meta name="description" content="์ž˜ ๊ทธ๋ ธ๋‚˜? ๋ง์ณค๋‚˜? ์ •์ฒด๋ฅผ ์ˆจ๊ธด ๋ฐฉํ•ด๊พผ์ด ์˜์•„์˜ฌ๋ฆฐ ํ˜ผ๋ˆ ์†์—์„œ, ๊ทธ๋ฆผ๊พผ๋“ค์˜ ์ง„์‹ค์„ ์ฐพ์•„๋‚ด๋Š” ๊ตฌ๊ฒฝ๊พผ๋“ค์˜ ํ›ˆ์ˆ˜๊ฐ€ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค! ๐ŸŽจ๐Ÿ•ต๏ธโ€โ™€๏ธ">
<meta name="keywords" content="๊ฒŒ์ž„, ๋“œ๋กœ์ž‰, ์‹ค์‹œ๊ฐ„, ๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด์–ด, ์›น๊ฒŒ์ž„, ์˜จ๋ผ์ธ๊ฒŒ์ž„, ํ€ด์ฆˆ๊ฒŒ์ž„">
Expand Down Expand Up @@ -91,8 +100,6 @@
<!-- ์›น ๋งค๋‹ˆํŽ˜์ŠคํŠธ -->
<link rel="manifest" href="/site.webmanifest">

<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/neodgm/neodgm-pro-webfont@latest/neodgm_pro/style.css" />

<title>๋ฐฉํ•ด๊พผ์€ ๋ชป๋ง๋ ค : ๊ทธ๋ฆผ๊พผ๋“ค์˜ ์—ญ์Šต</title>
</head>

Expand All @@ -102,4 +109,4 @@
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
</html>
Binary file added client/src/assets/background-tiny.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions client/src/components/ui/BackgroundImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEffect, useRef, useState } from 'react';
import tiny from '@/assets/background-tiny.png';
import { CDN } from '@/constants/cdn';
import { cn } from '@/utils/cn';

interface BackgroundImageProps {
className?: string;
}

const BackgroundImage = ({ className }: BackgroundImageProps) => {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);

useEffect(() => {
const img = imgRef.current;
if (img) {
img.onload = () => setIsLoaded(true);
}
}, []);

return (
<>
<div className={cn('absolute inset-0', className)}>
<img
src={tiny}
alt="๋ฐฐ๊ฒฝ ํŒจํ„ด"
className={cn(
'h-full w-full object-cover transition-opacity duration-300',
isLoaded ? 'opacity-0' : 'opacity-100',
)}
/>
</div>
<picture className={cn('absolute inset-0', className)}>
<source srcSet={CDN.BACKGROUND_IMAGE_AVIF} type="image/avif" />
<source srcSet={CDN.BACKGROUND_IMAGE_WEBP} type="image/webp" />
<img
src={CDN.BACKGROUND_IMAGE_PNG}
alt="๋ฐฐ๊ฒฝ ํŒจํ„ด"
className={cn(
'h-full w-full object-cover transition-opacity duration-300',
isLoaded ? 'opacity-100' : 'opacity-0',
)}
ref={imgRef}
loading="lazy"
decoding="async"
/>
</picture>
</>
);
};

export default BackgroundImage;
8 changes: 3 additions & 5 deletions client/src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from 'react';
import { ButtonHTMLAttributes, forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/utils/cn';

Expand All @@ -23,13 +23,11 @@ const buttonVariants = cva(
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {
const Button = forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {
return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
});
Button.displayName = 'Button';
Expand Down
22 changes: 17 additions & 5 deletions client/src/components/ui/HelpContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import { MouseEvent } from 'react';
import { lazy, MouseEvent, Suspense, useState } from 'react';
import helpIcon from '@/assets/help-icon.svg';
import HelpRollingModal from '@/components/modal/HelpRollingModal';
import { Button } from '@/components/ui/Button';
import { useModal } from '@/hooks/useModal';

const HelpContainer = ({}) => {
const HelpRollingModal = lazy(() => import('@/components/modal/HelpRollingModal'));

const HelpContainer = () => {
const { isModalOpened, closeModal, openModal, handleKeyDown } = useModal();
const [shouldLoadModal, setShouldLoadModal] = useState(false);

const handleOpenHelpModal = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();

openModal();
};

const handleMouseEnter = () => {
setShouldLoadModal(true);
};

return (
<nav className="fixed right-4 top-4 z-30 xs:right-8 xs:top-8">
<Button
variant="transperent"
size="icon"
onClick={handleOpenHelpModal}
onPointerEnter={handleMouseEnter}
aria-label="๋„์›€๋ง ๋ณด๊ธฐ"
className="hover:brightness-75"
>
<img src={helpIcon} alt="๋„์›€๋ง ๋ณด๊ธฐ ๋ฒ„ํŠผ" />
</Button>
<HelpRollingModal isModalOpened={isModalOpened} handleCloseModal={closeModal} handleKeyDown={handleKeyDown} />

{shouldLoadModal && (
<Suspense fallback={null}>
<HelpRollingModal isModalOpened={isModalOpened} handleCloseModal={closeModal} handleKeyDown={handleKeyDown} />
</Suspense>
)}
</nav>
);
};
Expand Down
10 changes: 10 additions & 0 deletions client/src/components/ui/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
import loading from '@/assets/lottie/loading.lottie';

export const Loading = () => {
return (
<div className="flex h-screen w-full items-center justify-center">
<DotLottieReact src={loading} loop autoplay className="h-96 w-96" />
</div>
);
};
39 changes: 25 additions & 14 deletions client/src/components/ui/Logo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from 'react';
import { forwardRef, ImgHTMLAttributes } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { CDN } from '@/constants/cdn';
import { cn } from '@/utils/cn';
Expand All @@ -18,47 +18,58 @@ const logoVariants = cva('w-auto', {
export type LogoVariant = 'main' | 'side';

interface LogoInfo {
src: string;
avif: string;
webp: string;
png: string;
alt: string;
description: string;
}

const LOGO_INFO: Record<LogoVariant, LogoInfo> = {
main: {
src: CDN.MAIN_LOGO,
avif: CDN.MAIN_LOGO_AVIF,
webp: CDN.MAIN_LOGO_WEBP,
png: CDN.MAIN_LOGO_PNG,
alt: '๋ฉ”์ธ ๋กœ๊ณ ',
description: '์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” ๋ฉ”์ธ ๋กœ๊ณ  ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค',
},
side: {
src: CDN.SIDE_LOGO,
avif: CDN.SIDE_LOGO_AVIF,
webp: CDN.SIDE_LOGO_WEBP,
png: CDN.MAIN_LOGO_PNG,
alt: '๋ณด์กฐ ๋กœ๊ณ ',
description: '์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” ๋ณด์กฐ ๋กœ๊ณ  ์ด๋ฏธ์ง€์ž…๋‹ˆ๋‹ค',
},
} as const;

export interface LogoProps
extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'aria-label'>,
extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'aria-label'>,
VariantProps<typeof logoVariants> {
/**
* ๋กœ๊ณ  ์ด๋ฏธ์ง€ ์„ค๋ช…์„ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ aria-label
*/
ariaLabel?: string;
}

const Logo = React.forwardRef<HTMLImageElement, LogoProps>(
({ className, variant = 'main', ariaLabel, ...props }, ref) => {
return (
const Logo = forwardRef<HTMLImageElement, LogoProps>(({ className, variant = 'main', ariaLabel, ...props }, ref) => {
const logoInfo = LOGO_INFO[variant as LogoVariant];

return (
<picture>
<source srcSet={logoInfo.avif} type="image/avif" />
<source srcSet={logoInfo.webp} type="image/webp" />
<img
src={LOGO_INFO[variant as LogoVariant].src}
alt={LOGO_INFO[variant as LogoVariant].alt}
aria-label={ariaLabel ?? LOGO_INFO[variant as LogoVariant].description}
src={logoInfo.png}
alt={logoInfo.alt}
aria-label={ariaLabel ?? logoInfo.description}
className={cn(logoVariants({ variant, className }))}
ref={ref}
{...props}
/>
);
},
);
</picture>
);
});

Logo.displayName = 'Logo';

export { Logo, logoVariants };
15 changes: 13 additions & 2 deletions client/src/constants/cdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@ const CDN_BASE = 'https://kr.object.ncloudstorage.com/troublepainter-assets';

export const CDN = {
BACKGROUND_MUSIC: `${CDN_BASE}/sounds/background-music.mp3`,
MAIN_LOGO: `${CDN_BASE}/logo/main-logo.png`,
SIDE_LOGO: `${CDN_BASE}/logo/side-logo.png`,

BACKGROUND_IMAGE_PNG: `${CDN_BASE}/patterns/background.png`,
BACKGROUND_IMAGE_WEBP: `${CDN_BASE}/patterns/background.webp`,
BACKGROUND_IMAGE_AVIF: `${CDN_BASE}/patterns/background.avif`,

MAIN_LOGO_PNG: `${CDN_BASE}/logo/main-logo.png`,
MAIN_LOGO_WEBP: `${CDN_BASE}/logo/main-logo.webp`,
MAIN_LOGO_AVIF: `${CDN_BASE}/logo/main-logo.avif`,

SIDE_LOGO_PNG: `${CDN_BASE}/logo/side-logo.png`,
SIDE_LOGO_WEBP: `${CDN_BASE}/logo/side-logo.webp`,
SIDE_LOGO_AVIF: `${CDN_BASE}/logo/side-logo.avif`,

// tailwind config ์„ค์ •
// BACKGROUND: `${CDN_BASE}/patterns/background.png`,
} as const;
1 change: 1 addition & 0 deletions client/src/hooks/useBackgroundMusic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const useBackgroundMusic = () => {

useEffect(() => {
audioRef.current = new Audio(CDN.BACKGROUND_MUSIC);
audioRef.current.preload = 'metadata';
audioRef.current.loop = true;
audioRef.current.volume = volume;

Expand Down
10 changes: 10 additions & 0 deletions client/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
@tailwind components;
@tailwind utilities;

@font-face {
font-family: 'NeoDunggeunmo Pro';
src:
url('https://cdn.jsdelivr.net/gh/neodgm/[email protected]/neodgm_pro/neodgm_pro.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/gh/neodgm/[email protected]/neodgm_pro/neodgm_pro.woff') format('woff'),
url('https://cdn.jsdelivr.net/gh/neodgm/[email protected]/neodgm_pro/neodgm_pro.ttf') format('truetype');
font-weight: normal;
font-display: swap;
}

@layer components {
/* ์Šคํฌ๋กค๋ฐ” hide ๊ธฐ๋Šฅ */
/* Hide scrollbar for Chrome, Safari and Opera */
Expand Down
5 changes: 3 additions & 2 deletions client/src/layouts/GameLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import loading from '@/assets/lottie/loading.lottie';
import { ChatContatiner } from '@/components/chat/ChatContatiner';
import { NavigationModal } from '@/components/modal/NavigationModal';
import { PlayerCardList } from '@/components/player/PlayerCardList';
import BackgroundImage from '@/components/ui/BackgroundImage';
import { useGameSocket } from '@/hooks/socket/useGameSocket';
import BrowserNavigationGuard from '@/layouts/BrowserNavigationGuard';
import GameHeader from '@/layouts/GameHeader';
Expand All @@ -30,11 +31,11 @@ const GameLayout = () => {
<BrowserNavigationGuard />
<NavigationModal />
<div
className={`relative flex min-h-screen flex-col justify-start bg-gradient-to-b from-violet-950 via-violet-800 to-fuchsia-800 before:absolute before:left-0 before:top-0 before:h-full before:w-full before:bg-patternImg before:bg-cover before:bg-center lg:py-5`}
className={`relative z-10 flex min-h-screen flex-col justify-start bg-gradient-to-b from-violet-950 via-violet-800 to-fuchsia-800 before:absolute before:left-0 before:top-0 before:h-full before:w-full before:bg-cover before:bg-center lg:py-5`}
>
<BackgroundImage className="-z-10" />
{/* ์ƒ๋‹จ ํ—ค๋” */}
<GameHeader />

<main className="mx-auto">
<div
className={cn(
Expand Down
6 changes: 4 additions & 2 deletions client/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StrictMode } from 'react';
import { StrictMode, Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import '@/index.css';
import { RouterProvider } from 'react-router-dom';
Expand All @@ -8,7 +8,9 @@ import { router } from '@/routes';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App>
<RouterProvider router={router} future={{ v7_startTransition: true }} />
<Suspense fallback={null}>
<RouterProvider router={router} future={{ v7_startTransition: true }} />
</Suspense>
</App>
</StrictMode>,
);
25 changes: 17 additions & 8 deletions client/src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import Background from '@/components/ui/BackgroundCanvas';
import BackgroundCanvas from '@/components/ui/BackgroundCanvas';
import BackgroundImage from '@/components/ui/BackgroundImage';
import { Button } from '@/components/ui/Button';
import { Logo } from '@/components/ui/Logo';
import { PixelTransitionContainer } from '@/components/ui/PixelTransitionContainer';
Expand All @@ -11,6 +12,15 @@ const MainPage = () => {
const { createRoom, isLoading } = useCreateRoom();
const { isExiting, transitionTo } = usePageTransition();

const preloadGamePage = async () => {
await Promise.all([
import('@/layouts/GameLayout'),
import('@/pages/LobbyPage'),
import('@/pages/GameRoomPage'),
import('@/pages/ResultPage'),
]);
};

useEffect(() => {
// ํ˜„์žฌ URL์„ ๋ฃจํŠธ๋กœ ๋ณ€๊ฒฝ
window.history.replaceState(null, '', '/');
Expand All @@ -32,19 +42,18 @@ const MainPage = () => {
isExiting ? 'bg-transparent' : 'bg-gradient-to-b from-violet-950 via-violet-800 to-fuchsia-700',
)}
>
<Background
className={cn(
'before:contents-[""] absolute -z-10 h-full w-full before:absolute before:left-0 before:top-0 before:h-full before:w-full before:bg-patternImg before:bg-cover before:bg-center',
)}
/>
<div className="duration-1000 animate-in fade-in slide-in-from-top-8">
<BackgroundImage className="-z-30" />
<BackgroundCanvas className="pointer-events-auto absolute inset-0 -z-20" />

<div className="-z-10 duration-1000 animate-in fade-in slide-in-from-top-8">
<Logo variant="main" className="w-full transition duration-300 hover:scale-110 hover:brightness-[1.12]" />
</div>

<Button
onClick={() => void handleCreateRoom()}
disabled={isLoading || isExiting}
className="h-12 max-w-72 animate-pulse"
className="-z-10 h-12 max-w-72 animate-pulse"
onPointerEnter={() => void preloadGamePage()}
>
{isLoading || isExiting ? '๋ฐฉ ์ƒ์„ฑ์ค‘...' : '๋ฐฉ ๋งŒ๋“ค๊ธฐ'}
</Button>
Expand Down
Loading
Loading