diff --git a/assets/example.png b/assets/example.png deleted file mode 100644 index efcd71c..0000000 Binary files a/assets/example.png and /dev/null differ diff --git a/src/renderer/src/assets/video/angel-rini-rest.svg b/src/renderer/src/assets/video/angel-rini-rest.svg new file mode 100644 index 0000000..ce3e190 --- /dev/null +++ b/src/renderer/src/assets/video/angel-rini-rest.svg @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/assets/video/bugi-rest.svg b/src/renderer/src/assets/video/bugi-rest.svg new file mode 100644 index 0000000..062a782 --- /dev/null +++ b/src/renderer/src/assets/video/bugi-rest.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/assets/video/pm-rini-rest.svg b/src/renderer/src/assets/video/pm-rini-rest.svg new file mode 100644 index 0000000..c7d3056 --- /dev/null +++ b/src/renderer/src/assets/video/pm-rini-rest.svg @@ -0,0 +1,283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/assets/video/rini.svg b/src/renderer/src/assets/video/rini.svg new file mode 100644 index 0000000..982ce5d --- /dev/null +++ b/src/renderer/src/assets/video/rini.svg @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/assets/video/stone-bugi-rest.svg b/src/renderer/src/assets/video/stone-bugi-rest.svg new file mode 100644 index 0000000..eb27360 --- /dev/null +++ b/src/renderer/src/assets/video/stone-bugi-rest.svg @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/assets/video/tire-bugi-rest.svg b/src/renderer/src/assets/video/tire-bugi-rest.svg new file mode 100644 index 0000000..613b89f Binary files /dev/null and b/src/renderer/src/assets/video/tire-bugi-rest.svg differ diff --git a/src/renderer/src/pages/Main/components/AttendacePanel.tsx b/src/renderer/src/pages/Main/components/AttendacePanel.tsx index 606553a..e1b3c03 100644 --- a/src/renderer/src/pages/Main/components/AttendacePanel.tsx +++ b/src/renderer/src/pages/Main/components/AttendacePanel.tsx @@ -19,14 +19,32 @@ interface CircleProps { future: boolean; } +// 사용시간 0~1시간: 가장 연한 노란색 +// 1 초과~2 시간 미만: 2단계 노랑 +// 2 시간 이상~3 시간 미만: 3단계 노랑 +// 3 이상~4 미만 : 4단계 노랑 +// 4 이상 ~: 5단계 노랑 const LEVEL_COLORS = [ - 'bg-yellow-500', // 1레벨 - 'bg-yellow-400', // 2레벨 - 'bg-yellow-300', // 3레벨 - 'bg-yellow-200', // 4레벨 - 'bg-yellow-100', // 5레벨 + 'bg-yellow-100', // 1레벨 (0~1시간): 가장 연한 노란색 + 'bg-yellow-200', // 2레벨 (1 초과~2 시간 미만): 2단계 노랑 + 'bg-yellow-300', // 3레벨 (2 시간 이상~3 시간 미만): 3단계 노랑 + 'bg-yellow-400', // 4레벨 (3 이상~4 미만): 4단계 노랑 + 'bg-yellow-500', // 5레벨 (4 이상): 5단계 노랑 (가장 진한 노란색) ] as const; +/** + * 사용 시간(시간 단위)에 따라 레벨(1~5)을 반환합니다. + * @param hours 사용 시간 (시간 단위) + * @returns 레벨 1~5 + */ +const getLevelFromHours = (hours: number): number => { + if (hours <= 1) return 1; // 0~1시간: 레벨 1 + if (hours < 2) return 2; // 1 초과~2 시간 미만: 레벨 2 + if (hours < 3) return 3; // 2 시간 이상~3 시간 미만: 레벨 3 + if (hours < 4) return 4; // 3 이상~4 미만: 레벨 4 + return 5; // 4 이상: 레벨 5 +}; + const Circle = ({ level, today, future }: CircleProps) => { // 미래 날짜 if (future) { @@ -92,13 +110,18 @@ const Calendar = ({ year, month, attendances = {} }: CalendarProps) => { const getLevelForDay = (day: number): number | null => { // 날짜를 YYYY-MM-DD 형식으로 변환 const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; - const level = attendances[dateStr]; + const usageMinutes = attendances[dateStr]; + + // 사용 시간이 없거나 0이면 null (안 사용한 날 - 그레이로 표시) + if (usageMinutes === undefined || usageMinutes === null || usageMinutes === 0) { + return null; + } - // 레벨이 없으면 null (안 사용한 날) - if (level === undefined || level === null) return null; + // 분을 시간으로 변환 + const usageHours = usageMinutes / 60; - // 레벨이 1~5 범위를 벗어나면 클램프 - return Math.min(Math.max(level / 60, 0), LEVEL_COLORS.length); + // 사용 시간에 따라 레벨 결정 (1~5) + return getLevelFromHours(usageHours); }; const isFutureDay = (day: number) => { diff --git a/src/renderer/src/pages/Main/components/RunningPanel.tsx b/src/renderer/src/pages/Main/components/RunningPanel.tsx index 836a50f..de84687 100644 --- a/src/renderer/src/pages/Main/components/RunningPanel.tsx +++ b/src/renderer/src/pages/Main/components/RunningPanel.tsx @@ -6,13 +6,24 @@ import RiniVideo from '@assets/video/rini.webm'; import StoneBugiVideo from '@assets/video/stone-bugi.webm'; import TireBugiVideo from '@assets/video/tire-bugi.webm'; -import { useEffect, useMemo } from 'react'; +import AngelRiniRestSvg from '@assets/video/angel-rini-rest.svg'; +import BugiRestSvg from '@assets/video/bugi-rest.svg'; +import PmRiniRestSvg from '@assets/video/pm-rini-rest.svg'; +import RiniSvg from '@assets/video/rini.svg'; +import StoneBugiRestSvg from '@assets/video/stone-bugi-rest.svg'; +import TireBugiRestSvg from '@assets/video/tire-bugi-rest.svg'; + +import { useEffect, useMemo, useRef } from 'react'; +import { useCameraStore } from '../../../store/useCameraStore'; import { usePostureStore } from '../../../store/usePostureStore'; import { cn } from '../../../utils/cn'; import { getScoreLevel } from '../../../utils/getScoreLevel'; const RunningPanel = () => { const score = usePostureStore((state) => state.score); + const cameraState = useCameraStore((state) => state.cameraState); + const isCameraShow = cameraState === 'show'; + const backgroundVideoRef = useRef(null); // 점수 기반 레벨 계산 const levelInfo = useMemo(() => getScoreLevel(score), [score]); @@ -37,6 +48,26 @@ const RunningPanel = () => { } }, [levelInfo.level]); + // 레벨에 따른 SVG 선택 (카메라 hide 상태일 때 사용) + const levelSvgSrc = useMemo(() => { + switch (levelInfo.level) { + case 1: + return AngelRiniRestSvg; + case 2: + return PmRiniRestSvg; + case 3: + return RiniSvg; + case 4: + return BugiRestSvg; + case 5: + return StoneBugiRestSvg; + case 6: + return TireBugiRestSvg; + default: + return RiniSvg; + } + }, [levelInfo.level]); + // 레벨에 따른 게이지바 비율 (레벨이 낮을수록(좋을수록) 더 많이 채워짐) const gaugeWidth = useMemo(() => { // 레벨 1(가장 좋음): 100%, 레벨 2: 95%, 레벨 3: 70%, 레벨 4: 45%, 레벨 5: 20%, 레벨 6(가장 나쁨): 5% @@ -74,6 +105,20 @@ const RunningPanel = () => { return statusMap[levelInfo.level] || '가는 중'; }, [levelInfo.level]); + // 카메라 상태에 따라 배경 영상 재생/멈춤 제어 + useEffect(() => { + const video = backgroundVideoRef.current; + if (!video) return; + + if (isCameraShow) { + video.play().catch((error) => { + console.warn('배경 영상 재생 실패:', error); + }); + } else { + video.pause(); + } + }, [isCameraShow]); + // 위젯 창 상태 확인 useEffect(() => { const checkWidgetStatus = async () => { @@ -100,6 +145,7 @@ const RunningPanel = () => {
{/* 배경 영상 */}
diff --git a/src/renderer/src/pages/Onboarding/components/ImageDescriptionPanel.tsx b/src/renderer/src/pages/Onboarding/components/ImageDescriptionPanel.tsx index eebfe99..ec72fd7 100644 --- a/src/renderer/src/pages/Onboarding/components/ImageDescriptionPanel.tsx +++ b/src/renderer/src/pages/Onboarding/components/ImageDescriptionPanel.tsx @@ -1,15 +1,15 @@ -import FirstImageDescription from './FirstImageDescription'; -import PrevIcon from '@assets/onboarding/prev_icon.svg?react'; +import firstDarkImage from '@assets/onboarding/first_dark_image.png'; import firstImage from '@assets/onboarding/first_image.png'; -import secondImage from '@assets/onboarding/second_image.png'; -import thirdImage from '@assets/onboarding/third_image.png'; +import fourthDarkImage from '@assets/onboarding/fourth_dark_image.png'; import fourthImage from '@assets/onboarding/fourth_image.png'; -import firstDarkImage from '@assets/onboarding/first_dark_image.png'; +import PrevIcon from '@assets/onboarding/prev_icon.svg?react'; +import RockIcon from '@assets/onboarding/rock_icon.svg?react'; import secondDarkImage from '@assets/onboarding/second_dark_image.png'; +import secondImage from '@assets/onboarding/second_image.png'; import thirdDarkImage from '@assets/onboarding/third_dark_image.png'; -import fourthDarkImage from '@assets/onboarding/fourth_dark_image.png'; -import RockIcon from '@assets/onboarding/rock_icon.svg?react'; -import { useState, useEffect } from 'react'; +import thirdImage from '@assets/onboarding/third_image.png'; +import { useEffect, useState } from 'react'; +import FirstImageDescription from './FirstImageDescription'; /* 단계별 이미지 (1단계는 null, 2~5단계는 이미지) */ const STEP_IMAGES_LIGHT = [ @@ -103,7 +103,7 @@ const ImageDescriptionPannel = ({ )} -

+

영상은 사용자의 PC에서만 처리되며, 어디에도 저장되거나 전송되지 diff --git a/src/renderer/src/utils/getScoreLevel.ts b/src/renderer/src/utils/getScoreLevel.ts index 376a303..306a3a9 100644 --- a/src/renderer/src/utils/getScoreLevel.ts +++ b/src/renderer/src/utils/getScoreLevel.ts @@ -110,3 +110,4 @@ export function getAllLevelDefinitions(): ScoreLevelInfo[] { } +