Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
57db87c
[SCRUM-236]:게스트 로그인 닉네임 수정 기능 추가
Anas-wg Jul 18, 2025
03bd4d2
Merge pull request #149 from Pick-px/fix/SCRUM-236
daven-park Jul 18, 2025
a23e05f
SCRUM-232 : 도움말 모달 스켈레톤 코드
daven-park Jul 18, 2025
b6f1606
Merge pull request #150 from Pick-px/feat/SCRUM-232-help-modal-detail
daven-park Jul 18, 2025
5adc2ff
SCRUM-240-admin-page
yoominlee00 Jul 18, 2025
78d761d
Merge pull request #151 from Pick-px/feat/SCRUM-240-admin-page
Anas-wg Jul 18, 2025
55dc7a6
게임내 BGM 켜기/끄기 버튼 추가
Anas-wg Jul 18, 2025
030bf19
Merge pull request #152 from Pick-px/feat/SCRUM-241
daven-park Jul 18, 2025
98dd644
SCRUM-238 : game 캔버스 모달
daven-park Jul 19, 2025
0879fbb
Merge pull request #153 from Pick-px/feat/SCRUM-238-game-canvas-modal
Anas-wg Jul 19, 2025
7e89634
관리자 페이지 추가
Anas-wg Jul 19, 2025
9b4b4ba
Merge pull request #154 from Pick-px/feat/adminPage
daven-park Jul 19, 2025
3b42dff
SCRUM-245-help-modal
yoominlee00 Jul 20, 2025
761e64f
Merge pull request #155 from Pick-px/feat/SCRUM-245-help-modal
daven-park Jul 20, 2025
7b665a6
SCRUM-246-pixel-update-array
yoominlee00 Jul 20, 2025
7f5fefb
Merge pull request #156 from Pick-px/feat/SCRUM-246-pixel-update-array
yoominlee00 Jul 20, 2025
810aef1
fix : mypage 집계중 스피너 표시 삭제
daven-park Jul 20, 2025
25afaba
feat : 접속자 수 툴팁 추가
daven-park Jul 20, 2025
921d5d8
fix : 도움말 버튼 위치 이동
daven-park Jul 20, 2025
9e7a6d7
Merge pull request #157 from Pick-px/fix/usercount-tooltip
yoominlee00 Jul 20, 2025
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
Binary file added public/help-images/album_1.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/album_2.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/canvas_1.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/canvas_2.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/canvas_3.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/chat_1.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/chat_2.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/chat_3.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/final.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_1.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_2.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_3.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_4.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_5.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_6.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/group_7.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_1.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_10.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_2.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_3.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_4.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_5.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_6.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_7.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_8.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/help-images/imageguide_9.PNG
Binary file added public/help-images/mypage_1.PNG
Binary file added public/help-images/mypage_2.PNG
Binary file added public/help-images/pick_1.PNG
Binary file added public/help-images/pick_2.PNG
Binary file added public/help-images/pick_3.PNG
Binary file added public/help-images/pick_4.PNG
Binary file added public/help-images/pick_5.PNG
Binary file added public/help-images/pick_6.PNG
Binary file added public/help-images/tutorial.jpeg
12 changes: 11 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ import HelpModalContent from './components/modal/HelpModalContent';
import CanvasEndedModal from './components/modal/CanvasEndedModal'; // CanvasEndedModal import 추가
import NotificationToast from './components/toast/NotificationToast'; // NotificationToast import 추가
import { useToastStore } from './store/toastStore'; // useToastStore import 추가
import GameModalContent from './components/modal/GameModalContent';

type DecodedToken = {
sub: {
userId: string;
nickName: string;
email?: string;
role?: string;
};
jti: string;
exp: number;
Expand All @@ -52,6 +55,8 @@ function App() {
closeMyPageModal,
isGroupModalOpen,
closeGroupModal,
isGameModalOpen,
closeGameModal,
isCanvasModalOpen,
closeCanvasModal,
isAlbumModalOpen,
Expand Down Expand Up @@ -105,6 +110,8 @@ function App() {
const user = {
userId: decodedToken.sub.userId,
nickname: decodedToken.sub.nickName,
email: decodedToken.sub.email,
role: decodedToken.sub.role,
};
console.log(user);
setAuth(newAccessToken, user);
Expand Down Expand Up @@ -158,9 +165,12 @@ function App() {
<Modal isOpen={isAlbumModalOpen} onClose={closeAlbumModal}>
<AlbumModalContent onClose={closeAlbumModal} />
</Modal>
<Modal isOpen={isHelpModalOpen} onClose={handleCloseHelpModal}>
<Modal isOpen={isHelpModalOpen} onClose={handleCloseHelpModal} fullWidth={true}>
<HelpModalContent />
</Modal>
<Modal isOpen={isGameModalOpen} onClose={closeGameModal}>
<GameModalContent onClose={closeGameModal} />
</Modal>
{isCanvasEndedModalOpen && <CanvasEndedModal />}
{/* NotificationToast 컴포넌트 추가 */}
<NotificationToast />
Expand Down
49 changes: 49 additions & 0 deletions src/api/AdminAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { data } from 'react-router-dom';
import apiClient from '../services/apiClient';

// 관리자 API 함수들
export const AdminAPI = {
// 캔버스 리스트
getCanvases: async () => {
const response = await apiClient.get('/admin/canvas/list');
// 백엔드 응답의 canvasId를 id로 매핑
return response.data.map((canvas: any) => ({
...canvas,
id: canvas.canvasId,
}));
},

// 캔버스 생성
createCanvas: async (canvasData: {
title: string;
type: string;
// created_at: string;
started_at: string;
ended_at: string | null;
size_x: number;
size_y: number;
}) => {
const response = await apiClient.post('/admin/canvas', canvasData);
return response.data;
},

// 캔버스 삭제
deleteCanvas: async (canvasId: number) => {
const response = await apiClient.delete('/admin/canvas', {
data: {
canvasId: canvasId,
},
});

return response.data;
},

// 게임 강제종료
forceEnd: async (canvasId: number) => {
// POST 요청의 body는 두 번째 인자로 바로 객체를 전달합니다.
const response = await apiClient.post('/admin/force_end', {
canvasId: canvasId,
});
return response.data;
},
};
12 changes: 12 additions & 0 deletions src/api/CanvasAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ export const canvasService = {
}
},

// 시작전 게임 캔버스 목록 조회 (이동 시 사용)
async getGameCanvases(): Promise<Canvas[]> {
try {
const response = await apiClient.get<Canvas[]>(`/game/list`);
console.log(response.data);
return response.data;
} catch (error) {
console.error('Error fetching canvas:', error);
throw new Error('게임 캔버스 정보를 불러오는데 실패했습니다.');
}
},

// 특정 캔버스 조회 (이동 시 사용)
async getCanvas(canvasId: number): Promise<Canvas> {
try {
Expand Down
3 changes: 3 additions & 0 deletions src/auth/AuthCallbackPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export default function AuthCallbackPage() {
console.log(authResult);

if (authResult?.accessToken && authResult?.user) {
// 사용자 정보에 role 설정
setAuth(authResult.accessToken, authResult.user);

// ✨ 1. 객체를 JSON 문자열로 변환합니다.
const authResultString = JSON.stringify(authResult);

Expand Down
28 changes: 23 additions & 5 deletions src/components/SocketIntegration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ interface SocketIntegrationProps {
sourceCanvasRef: React.RefObject<HTMLCanvasElement>;
draw: () => void;
canvas_id: string;
onPixelReceived?: (data: {
pixels: Array<{ x: number; y: number; color: string }>;
}) => void;
onCooldownReceived?: (cooldown: {
cooldown: boolean;
remaining: number;
Expand All @@ -28,23 +31,38 @@ export const usePixelSocket = ({
sourceCanvasRef,
draw,
canvas_id,
onPixelReceived,
onCooldownReceived,
}: SocketIntegrationProps) => {
const handlePixelReceived = useCallback(
(pixel: { x: number; y: number; color: string }) => {
(data: any) => {
// 외부에서 제공된 onPixelReceived 콜백이 있으면 사용
if (onPixelReceived) {
onPixelReceived(data);
return;
}

// 기본 동작: 소스 캔버스에 직접 그리기
const { pixels } = data;
const sourceCtx = sourceCanvasRef.current?.getContext('2d');
if (sourceCtx) {
sourceCtx.fillStyle = pixel.color;
sourceCtx.fillRect(pixel.x, pixel.y, 1, 1);
// 각 픽셀에 대해 그리기
pixels.forEach((pixel: { x: number; y: number; color: string }) => {
// 소스 캔버스에 픽셀 그리기
sourceCtx.fillStyle = pixel.color;
sourceCtx.fillRect(pixel.x, pixel.y, 1, 1);
});

// 캔버스 다시 그리기
draw();
}
},
[sourceCanvasRef, draw]
[sourceCanvasRef, draw, onPixelReceived]
);

const { sendPixel } = useSocket(
handlePixelReceived,
canvas_id,
handlePixelReceived,
onCooldownReceived
);

Expand Down
45 changes: 35 additions & 10 deletions src/components/canvas/CanvasUIMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default function CanvasUIMobile({
openMyPageModal,
openGroupModal,
openHelpModal,
openGameModal,
} = useModalStore();

// 드롭다움 열림, 닫힘 상태
Expand Down Expand Up @@ -335,6 +336,31 @@ export default function CanvasUIMobile({
{isLoggedIn ? '마이페이지' : '로그인'}
</span>
</div>
{/* 그룹 버튼 */}
<div className='group relative'>
<button
onClick={isLoggedIn ? openGroupModal : openLoginModal}
className='flex h-10 w-10 items-center justify-center rounded-full bg-gray-700 text-white shadow-lg transition-transform hover:bg-gray-600 active:scale-95'
>
<svg
className='h-6 w-6'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
strokeWidth={1.5}
stroke='currentColor'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
d='M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m-7.5-2.962a3.75 3.75 0 015.962 0L12 15.75M12 15.75l-2.47-4.772a3.75 3.75 0 015.962 0L12 15.75zM21 12a9 9 0 11-18 0 9 9 0 0118 0z'
/>
</svg>
</button>
<span className='absolute top-1/2 left-full ml-3 -translate-y-1/2 scale-0 rounded bg-gray-900 p-2 text-xs whitespace-nowrap text-white transition-all group-hover:scale-100'>
그룹
</span>
</div>
{/* 캔버스 버튼 */}
<div className='group relative'>
<button
Expand All @@ -360,10 +386,10 @@ export default function CanvasUIMobile({
캔버스
</span>
</div>
{/* 앨범 버튼 */}
{/* 게임 버튼 */}
<div className='group relative'>
<button
onClick={openAlbumModal}
onClick={isLoggedIn ? openGameModal : openLoginModal}
className='flex h-10 w-10 items-center justify-center rounded-full bg-gray-700 text-white shadow-lg transition-transform hover:bg-gray-600 active:scale-95'
>
<svg
Expand All @@ -377,19 +403,19 @@ export default function CanvasUIMobile({
<path
strokeLinecap='round'
strokeLinejoin='round'
d='M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z'
d='M6 12h4m-2-2v4m6-2h.01M13 12h.01M18 12h.01'
/>
<rect x="4" y="8" width="16" height="8" rx="2" strokeWidth={1.5} />
</svg>
</button>
<span className='absolute top-1/2 left-full ml-3 -translate-y-1/2 scale-0 rounded bg-gray-900 p-2 text-xs whitespace-nowrap text-white transition-all group-hover:scale-100'>
앨범
게임
</span>
</div>

{/* 그룹 버튼 */}
{/* 앨범 버튼 */}
<div className='group relative'>
<button
onClick={isLoggedIn ? openGroupModal : openLoginModal}
onClick={openAlbumModal}
className='flex h-10 w-10 items-center justify-center rounded-full bg-gray-700 text-white shadow-lg transition-transform hover:bg-gray-600 active:scale-95'
>
<svg
Expand All @@ -403,15 +429,14 @@ export default function CanvasUIMobile({
<path
strokeLinecap='round'
strokeLinejoin='round'
d='M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m-7.5-2.962a3.75 3.75 0 015.962 0L12 15.75M12 15.75l-2.47-4.772a3.75 3.75 0 015.962 0L12 15.75zM21 12a9 9 0 11-18 0 9 9 0 0118 0z'
d='M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z'
/>
</svg>
</button>
<span className='absolute top-1/2 left-full ml-3 -translate-y-1/2 scale-0 rounded bg-gray-900 p-2 text-xs whitespace-nowrap text-white transition-all group-hover:scale-100'>
그룹
앨범
</span>
</div>

{/* BGM 버튼 */}
<div className='group relative'>
<button
Expand Down
33 changes: 20 additions & 13 deletions src/components/canvas/CanvasUIPC.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default function CanvasUIPC({
openMyPageModal,
openGroupModal,
openHelpModal,
openGameModal,
} = useModalStore();

// 드롭다움 열림, 닫힘 상태
Expand All @@ -89,8 +90,6 @@ export default function CanvasUIPC({
};
}, [isMenuOpen]);

console.log(canvasType);

return (
<>
<ToastContainer />
Expand Down Expand Up @@ -196,6 +195,15 @@ export default function CanvasUIPC({
<span style={{ fontFamily: '"Press Start 2P", cursive' }}>Menu</span>
</button>

{/* 도움말 버튼 - 메뉴 버튼 오른쪽에 배치 */}
<button
onClick={openHelpModal}
className='font-press-start relative ml-2 inline-block bg-[#92CD41] px-4 py-2 text-xs text-white no-underline shadow-[inset_-2px_-2px_0px_0px_#45841B] before:absolute before:-top-[4px] before:left-0 before:box-content before:h-full before:w-full before:border-t-[4px] before:border-b-[4px] before:border-gray-700 before:content-[""] after:absolute after:top-0 after:-left-[4px] after:box-content after:h-full after:w-full after:border-r-[4px] after:border-l-[4px] after:border-gray-700 after:content-[""] hover:bg-[#7CB342] hover:shadow-[inset_-3px_-3px_0px_0px_#366915] active:shadow-[inset_2px_2px_0px_0px_#366915]'
title='게임 가이드'
>
<span style={{ fontFamily: '"Press Start 2P", cursive' }}>?</span>
</button>

{/* isMenuOpen이 true일 때만 드롭다운 메뉴가 보입니다. */}
{isMenuOpen && (
<div className='absolute top-full mt-3 flex w-auto flex-col gap-3'>
Expand All @@ -219,39 +227,38 @@ export default function CanvasUIPC({
</button>
{/* 캔버스 버튼 */}
<button
onClick={openCanvasModal}
onClick={isLoggedIn ? openCanvasModal : openLoginModal}
className='font-press-start relative inline-block w-full bg-[#92CD41] px-4 py-2 text-left text-[10px] text-white no-underline shadow-[inset_-2px_-2px_0px_0px_#45841B] before:absolute before:-top-[4px] before:left-0 before:box-content before:h-full before:w-full before:border-t-[4px] before:border-b-[4px] before:border-gray-700 before:content-[""] after:absolute after:top-0 after:-left-[4px] after:box-content after:h-full after:w-full after:border-r-[4px] after:border-l-[4px] after:border-gray-700 after:content-[""] hover:bg-[#7CB342] hover:shadow-[inset_-3px_-3px_0px_0px_#366915] active:shadow-[inset_2px_2px_0px_0px_#366915]'
>
<span style={{ fontFamily: '"Press Start 2P", cursive' }}>
Canvas
</span>
</button>
{/* 갤러리 버튼 */}
{/* 게임 버튼 */}
<button
onClick={openAlbumModal}
onClick={isLoggedIn ? openGameModal : openLoginModal}
className='font-press-start relative inline-block w-full bg-[#92CD41] px-4 py-2 text-left text-[10px] text-white no-underline shadow-[inset_-2px_-2px_0px_0px_#45841B] before:absolute before:-top-[4px] before:left-0 before:box-content before:h-full before:w-full before:border-t-[4px] before:border-b-[4px] before:border-gray-700 before:content-[""] after:absolute after:top-0 after:-left-[4px] after:box-content after:h-full after:w-full after:border-r-[4px] after:border-l-[4px] after:border-gray-700 after:content-[""] hover:bg-[#7CB342] hover:shadow-[inset_-3px_-3px_0px_0px_#366915] active:shadow-[inset_2px_2px_0px_0px_#366915]'
>
<span style={{ fontFamily: '"Press Start 2P", cursive' }}>
Album
Game
</span>
</button>
{/* BGM 버튼 */}
{/* 갤러리 버튼 */}
<button
onClick={toggleBgm}
onClick={openAlbumModal}
className='font-press-start relative inline-block w-full bg-[#92CD41] px-4 py-2 text-left text-[10px] text-white no-underline shadow-[inset_-2px_-2px_0px_0px_#45841B] before:absolute before:-top-[4px] before:left-0 before:box-content before:h-full before:w-full before:border-t-[4px] before:border-b-[4px] before:border-gray-700 before:content-[""] after:absolute after:top-0 after:-left-[4px] after:box-content after:h-full after:w-full after:border-r-[4px] after:border-l-[4px] after:border-gray-700 after:content-[""] hover:bg-[#7CB342] hover:shadow-[inset_-3px_-3px_0px_0px_#366915] active:shadow-[inset_2px_2px_0px_0px_#366915]'
>
<span style={{ fontFamily: '"Press Start 2P", cursive' }}>
{isBgmPlaying ? 'BGM Off' : 'BGM On'}
Album
</span>
</button>
{/* 도움말 버튼 */}
{/* BGM 버튼 */}
<button
onClick={openHelpModal}
onClick={toggleBgm}
className='font-press-start relative inline-block w-full bg-[#92CD41] px-4 py-2 text-left text-[10px] text-white no-underline shadow-[inset_-2px_-2px_0px_0px_#45841B] before:absolute before:-top-[4px] before:left-0 before:box-content before:h-full before:w-full before:border-t-[4px] before:border-b-[4px] before:border-gray-700 before:content-[""] after:absolute after:top-0 after:-left-[4px] after:box-content after:h-full after:w-full after:border-r-[4px] after:border-l-[4px] after:border-gray-700 after:content-[""] hover:bg-[#7CB342] hover:shadow-[inset_-3px_-3px_0px_0px_#366915] active:shadow-[inset_2px_2px_0px_0px_#366915]'
title='게임 가이드'
>
<span style={{ fontFamily: '"Press Start 2P", cursive' }}>
Help
{isBgmPlaying ? 'BGM Off' : 'BGM On'}
</span>
</button>
</div>
Expand Down
14 changes: 11 additions & 3 deletions src/components/canvas/UserCount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export default function UserCount() {
}, [canvas_id, handleUserCountChange]); // handleUserCountChange가 변경될 때만 재실행

return (
<div className='text pointer-events-none fixed right-[1px] bottom-[1px] z-[9999] rounded-[8px] p-[10px] text-sm text-white'>
<div className='text fixed right-[1px] bottom-[1px] z-[9999] rounded-[8px] p-[10px] text-sm text-white'>
<div className='flex items-center gap-2'>
<div className='flex items-center gap-1'>
<div className='group relative flex items-center gap-1'>
<svg
className='h-4 w-4'
fill='none'
Expand All @@ -59,9 +59,13 @@ export default function UserCount() {
userCount
)}
</span>
{/* 툴팁 - 가로로 표시 */}
<div className='absolute bottom-full left-1/2 mb-2 hidden -translate-x-1/2 translate-y-1 rounded bg-black/80 px-2 py-1 text-xs whitespace-nowrap opacity-0 transition-all group-hover:block group-hover:translate-y-0 group-hover:opacity-100'>
전체 접속자 수
</div>
</div>

<div className='flex items-center gap-1'>
<div className='group relative flex items-center gap-1'>
<svg
className='h-4 w-4'
fill='none'
Expand All @@ -82,6 +86,10 @@ export default function UserCount() {
canvasCount
)}
</span>
{/* 툴팁 - 가로로 표시 */}
<div className='absolute bottom-full left-1/2 mb-2 hidden -translate-x-1/2 translate-x-[-80%] translate-y-1 rounded bg-black/80 px-2 py-1 text-xs whitespace-nowrap opacity-0 transition-all group-hover:block group-hover:translate-y-0 group-hover:opacity-100'>
현재 캔버스 접속자 수
</div>
</div>
</div>
</div>
Expand Down
Loading