From 9fe03a66cbf0cb20c3a48cb8588937db237682d3 Mon Sep 17 00:00:00 2001 From: hbyang Date: Mon, 9 Mar 2026 13:10:55 +0900 Subject: [PATCH 1/8] =?UTF-8?q?url=20=EB=B6=99=ED=98=80=EB=84=A3=EA=B8=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Dashboard/UrlInputContent.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pages/Dashboard/UrlInputContent.tsx b/src/pages/Dashboard/UrlInputContent.tsx index 2329fdc..12dec9c 100644 --- a/src/pages/Dashboard/UrlInputContent.tsx +++ b/src/pages/Dashboard/UrlInputContent.tsx @@ -124,6 +124,17 @@ const UrlInputContent: React.FC = ({ onAnalyze, onAutocomp } }; + // 붙여넣기 시 URL만 추출 + const handlePaste = (e: React.ClipboardEvent) => { + if (searchType !== 'url') return; + const pasted = e.clipboardData.getData('text'); + const urlMatch = pasted.match(/https?:\/\/[^\s]+/); + if (urlMatch) { + e.preventDefault(); + setInputValue(urlMatch[0]); + } + }; + // 폼 제출 처리 const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -212,6 +223,7 @@ const UrlInputContent: React.FC = ({ onAnalyze, onAutocomp }, 300); } }} + onPaste={handlePaste} onFocus={() => { if (searchType === 'name' && autocompleteResults.length > 0) { setShowAutocomplete(true); From 937ef7a57614ca153cc4a81f5f608287e2674b4a Mon Sep 17 00:00:00 2001 From: hbyang Date: Mon, 9 Mar 2026 13:46:54 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20loca?= =?UTF-8?q?l=20storage=20=20=EC=A0=95=EB=A6=AC=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/api.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 11f313a..2c980a4 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -596,14 +596,28 @@ export async function refreshAccessToken(): Promise { return data; } +// 로컬 스토리지 전체 정리 +function clearAllLocalData() { + clearTokens(); + localStorage.removeItem('castad_view_mode'); + localStorage.removeItem('castad_analysis_data'); + localStorage.removeItem('castad_wizard_step'); + localStorage.removeItem('castad_active_item'); + localStorage.removeItem('castad_song_task_id'); + localStorage.removeItem('castad_image_task_id'); + localStorage.removeItem('castad_song_generation'); + localStorage.removeItem('castad_video_generation'); + localStorage.removeItem('castad_video_ratio'); +} + // 로그아웃 export async function logout(): Promise { const response = await authenticatedFetch(`${API_URL}/user/auth/logout`, { method: 'POST', }); - // 응답과 관계없이 로컬 토큰 삭제 - clearTokens(); + // 응답과 관계없이 로컬 데이터 전체 삭제 + clearAllLocalData(); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); @@ -616,8 +630,8 @@ export async function logoutAll(): Promise { method: 'POST', }); - // 응답과 관계없이 로컬 토큰 삭제 - clearTokens(); + // 응답과 관계없이 로컬 데이터 전체 삭제 + clearAllLocalData(); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); From dc3d128b222bb3990ee6aea3168c4f1fb5919bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Mon, 9 Mar 2026 15:09:51 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EC=84=9D=20=EB=B0=8F=20=EA=B8=B0=ED=83=80=20UI=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.css | 160 ++++++++--- public/assets/images/star-icon(old).svg | 3 + public/assets/images/star-icon.svg | 22 +- src/pages/Analysis/AnalysisResultSection.tsx | 229 +--------------- src/pages/Analysis/GeometricChart.tsx | 263 +++++++++++-------- src/pages/Dashboard/SoundStudioContent.tsx | 42 ++- 6 files changed, 329 insertions(+), 390 deletions(-) create mode 100644 public/assets/images/star-icon(old).svg diff --git a/index.css b/index.css index dbea805..227d64b 100644 --- a/index.css +++ b/index.css @@ -2049,19 +2049,23 @@ display: flex; align-items: center; gap: 4px; - padding: 8px 20px 8px 8px; - background: #462E64; + background-color: #462E64; + color: #CFABFB; + font-size: 0.875rem; + font-weight: 600; + padding: 0 1.25rem 0 0.5rem; + height: 36px; border: 1px solid #694596; border-radius: 999px; - color: #CFABFB; - font-size: 14px; - font-weight: 600; cursor: pointer; - transition: opacity 0.2s; + white-space: nowrap; + transition: background-color 0.2s; + line-height: 1.19; + letter-spacing: -0.006em; } .comp2-back-btn:hover { - opacity: 0.85; + background-color: #5a3a80; } .comp2-container { @@ -3270,18 +3274,40 @@ .bi2-page { min-height: 100vh; - background-color: #002224; + background: linear-gradient(to bottom, #002224, #01191a); color: #E5F1F2; + padding-top: 64px; padding-bottom: 160px; font-family: 'Pretendard', sans-serif; + overflow-x: auto; } /* 헤더 */ .bi2-header { + position: fixed; + top: 0; + left: 0; + right: 0; display: flex; align-items: center; - padding: 8px 32px; + justify-content: flex-start; + padding: 8px 1rem; height: 64px; + z-index: 30; +} + +@media (min-width: 768px) { + .bi2-header { + padding: 8px 2rem; + } + + body:has(.sidebar.expanded) .bi2-header { + left: 15rem; + } + + body:has(.sidebar.collapsed) .bi2-header { + left: 5rem; + } } .bi2-back-btn { @@ -3316,13 +3342,14 @@ } .bi2-title-icon { - width: 40px; - height: 40px; + width: 80px; + height: 80px; } .bi2-star-icon { - width: 40px; - height: 40px; + width: 80px; + height: 80px; + aspect-ratio: 1/1; animation: twinkle 2.5s ease-in-out infinite; } @@ -3334,7 +3361,7 @@ } .bi2-main-title { - font-size: 40px; + font-size: 48px; font-weight: 600; color: #FFFFFF; letter-spacing: -0.006em; @@ -3354,6 +3381,7 @@ /* 메인 컨테이너 */ .bi2-main-container { max-width: 1440px; + min-width: 1000px; margin: 0 auto; background: #013032; border: 1px solid #034A4D; @@ -3404,11 +3432,13 @@ /* 브랜드 정체성 카드 */ .bi2-identity-card { background: #034245; + border: 1px solid #94FBE0; border-radius: 20px; padding: 22px 24px; display: flex; flex-direction: column; gap: 22px; + box-shadow: 0 0 40px rgba(148, 251, 224, 0.1); } .bi2-identity-top { @@ -3433,7 +3463,7 @@ .bi2-core-value { font-size: 24px; font-weight: 700; - color: #FFFFFF; + color: #F4FFFC; letter-spacing: -0.006em; line-height: 1.3; } @@ -3464,10 +3494,10 @@ } .bi2-body-text { - font-size: 16px; + font-size: 18px; font-weight: 400; color: #CEE5E6; - line-height: 1.625; + line-height: 1.6; letter-spacing: -0.006em; } @@ -3476,7 +3506,7 @@ display: flex; align-items: center; justify-content: center; - gap: 160px; + gap: clamp(20px, 4vw, 80px); background: #034245; border: 1px solid #034A4D; border-radius: 20px; @@ -3493,6 +3523,17 @@ align-items: center; } +@media (max-width: 768px) { + .bi2-radar-container svg { + width: min(95vw, 420px) !important; + height: min(95vw, 420px) !important; + } + + .bi2-radar-label-text { + display: none; + } +} + .bi2-selling-list { display: flex; flex-direction: column; @@ -3532,19 +3573,19 @@ } .bi2-selling-name { - font-size: 16px; - font-weight: 400; + font-size: 20px; + font-weight: 600; color: #E5F1F2; letter-spacing: -0.006em; line-height: 1.375; } .bi2-selling-desc { - font-size: 12px; - font-weight: 500; + font-size: 14px; + font-weight: 600; color: #9BCACC; letter-spacing: -0.006em; - line-height: 1; + line-height: 1.29; } /* 고객 유형 카드 */ @@ -3629,9 +3670,9 @@ .bi2-persona-detail-text { font-size: 16px; - font-weight: 400; + font-weight: 600; color: #E5F1F2; - line-height: 1.625; + line-height: 26px; letter-spacing: -0.006em; white-space: pre-line; text-align: right; @@ -3665,18 +3706,67 @@ align-items: center; justify-content: center; padding: 8px 16px; - background: #034245; + background: #01393B; border-radius: 999px; - font-size: 16px; - font-weight: 400; + font-size: 20px; + font-weight: 600; color: #E5F1F2; letter-spacing: -0.006em; line-height: 1.375; } -/* 반응형 */ -@media (max-width: 1024px) { +/* 하단 고정 버튼 */ +.bi2-bottom-button-container { + position: fixed; + bottom: 32px; + left: 0; + right: 0; + display: flex; + justify-content: center; + z-index: 50; + pointer-events: none; +} + +@media (min-width: 768px) { + body:has(.sidebar.expanded) .bi2-bottom-button-container { + left: 15rem; + } + + body:has(.sidebar.collapsed) .bi2-bottom-button-container { + left: 5rem; + } +} + +.bi2-generate-btn { + pointer-events: auto; + display: flex; + align-items: center; + gap: 8px; + padding: 12px 48px; + background: #A65EFF; + color: #FFFFFF; + font-size: 16px; + font-weight: 700; + border: none; + border-radius: 999px; + cursor: pointer; + box-shadow: 0 4px 24px rgba(174, 114, 249, 0.4); + transition: background-color 0.2s, transform 0.2s; +} + +.bi2-generate-btn:hover { + background: #8B3FE8; + transform: scale(1.05); +} + +/* 반응형 – 태블릿/모바일 (≤768px) */ +@media (max-width: 768px) { + .bi2-page { + padding-top: 64px; + } + .bi2-main-container { + min-width: unset; margin: 0 16px; padding: 24px 20px 80px; gap: 48px; @@ -3704,6 +3794,14 @@ .bi2-store-name { font-size: 24px; } + + .bi2-selling-name { + font-size: 16px; + } + + .bi2-keyword-pill { + font-size: 16px; + } } /* ===================================================== @@ -7857,7 +7955,7 @@ .lyrics-textarea { width: 100%; - height: 200px; + min-height: 200px; background: transparent; border: none; color: var(--color-text-white); diff --git a/public/assets/images/star-icon(old).svg b/public/assets/images/star-icon(old).svg new file mode 100644 index 0000000..a40bfa9 --- /dev/null +++ b/public/assets/images/star-icon(old).svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/images/star-icon.svg b/public/assets/images/star-icon.svg index a40bfa9..9f104a3 100644 --- a/public/assets/images/star-icon.svg +++ b/public/assets/images/star-icon.svg @@ -1,3 +1,21 @@ - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Analysis/AnalysisResultSection.tsx b/src/pages/Analysis/AnalysisResultSection.tsx index 5ce9ea0..3e63ded 100755 --- a/src/pages/Analysis/AnalysisResultSection.tsx +++ b/src/pages/Analysis/AnalysisResultSection.tsx @@ -1,7 +1,8 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { CrawlingResponse, TargetPersona, SellingPoint } from '../../types/api'; +import { CrawlingResponse, TargetPersona } from '../../types/api'; +import { GeometricChart } from './GeometricChart'; interface AnalysisResultSectionProps { onBack: () => void; @@ -9,200 +10,9 @@ interface AnalysisResultSectionProps { data: CrawlingResponse; } -// 레이더 차트 컴포넌트 -interface RadarChartProps { - data: SellingPoint[]; - size?: number; -} - -const RadarChart: React.FC = ({ data, size = 360 }) => { - const [animatedScores, setAnimatedScores] = useState(data.map(() => 0)); - const [isAnimating, setIsAnimating] = useState(true); - - useEffect(() => { - const targetScores = data.map(item => item.score); - const duration = 1500; - const startTime = Date.now(); - - const animate = () => { - const elapsed = Date.now() - startTime; - const progress = Math.min(elapsed / duration, 1); - const easeProgress = 1 - Math.pow(1 - progress, 3); - - const newScores = targetScores.map(target => target * easeProgress); - setAnimatedScores(newScores); - - if (progress < 1) { - requestAnimationFrame(animate); - } else { - setIsAnimating(false); - } - }; - - requestAnimationFrame(animate); - }, [data]); - - const padding = 80; - const center = size / 2 + padding; - const maxRadius = size / 2 - 20; - const levels = 5; - const angleStep = (2 * Math.PI) / data.length; - - const getPoint = (index: number, score: number) => { - const angle = angleStep * index - Math.PI / 2; - const radius = (score / 100) * maxRadius; - return { - x: center + radius * Math.cos(angle), - y: center + radius * Math.sin(angle), - }; - }; - - const dataPoints = animatedScores.map((score, i) => getPoint(i, score)); - const dataPath = dataPoints.map((p, i) => (i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`)).join(' ') + ' Z'; - - const levelPaths = Array.from({ length: levels }, (_, levelIndex) => { - const levelRadius = ((levelIndex + 1) / levels) * maxRadius; - const points = data.map((_, i) => { - const angle = angleStep * i - Math.PI / 2; - return { - x: center + levelRadius * Math.cos(angle), - y: center + levelRadius * Math.sin(angle), - }; - }); - return points.map((p, i) => (i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`)).join(' ') + ' Z'; - }); - - const axisLines = data.map((_, i) => { - const angle = angleStep * i - Math.PI / 2; - const endX = center + maxRadius * Math.cos(angle); - const endY = center + maxRadius * Math.sin(angle); - return `M ${center} ${center} L ${endX} ${endY}`; - }); - - const svgSize = size + padding * 2; - - // 순위별로 정렬하여 라벨 위치에 순위 번호 배치 - const sortedIndices = [...data] - .map((item, i) => ({ index: i, score: item.score })) - .sort((a, b) => b.score - a.score) - .map((item, rank) => ({ ...item, rank: rank + 1 })); - - const rankMap = new Map(); - sortedIndices.forEach(item => rankMap.set(item.index, item.rank)); - - return ( -
- - {levelPaths.map((path, i) => ( - - ))} - - {axisLines.map((path, i) => ( - - ))} - - - - {dataPoints.map((point, i) => ( - - ))} - - {data.map((item, i) => { - const angle = angleStep * i - Math.PI / 2; - const labelRadius = maxRadius + 40; - const pos = { - x: center + labelRadius * Math.cos(angle), - y: center + labelRadius * Math.sin(angle), - }; - const rank = rankMap.get(i) || i + 1; - const isTopThree = rank <= 3; - - return ( - - - - - {rank} - - - {item.korean_category} - - - - ); - })} - -
- ); -}; - const AnalysisResultSection: React.FC = ({ onBack, onGenerate, data }) => { const { t } = useTranslation(); const { processed_info, marketing_analysis } = data; - const containerRef = useRef(null); - const [buttonPosition, setButtonPosition] = useState({ left: 0, width: 0 }); const brandIdentity = marketing_analysis?.brand_identity; const marketPositioning = marketing_analysis?.market_positioning; @@ -213,26 +23,6 @@ const AnalysisResultSection: React.FC = ({ onBack, o // 셀링 포인트를 score 내림차순으로 정렬 const sortedSellingPoints = [...sellingPoints].sort((a, b) => b.score - a.score); - useEffect(() => { - const updateButtonPosition = () => { - if (containerRef.current) { - const rect = containerRef.current.getBoundingClientRect(); - setButtonPosition({ left: rect.left, width: rect.width }); - } - }; - - updateButtonPosition(); - window.addEventListener('resize', updateButtonPosition); - - const observer = new MutationObserver(updateButtonPosition); - observer.observe(document.body, { attributes: true, subtree: true, attributeFilter: ['class'] }); - - return () => { - window.removeEventListener('resize', updateButtonPosition); - observer.disconnect(); - }; - }, []); - return (
{/* Header */} @@ -253,13 +43,13 @@ const AnalysisResultSection: React.FC = ({ onBack, o

{t('analysis.pageTitle')}

- {t('analysis.pageDescHighlight')}{t('analysis.pageDescBefore')}{processed_info?.customer_name || t('analysis.defaultBrandName')}{t('analysis.pageDescAfter')} + {t('analysis.pageDescHighlight')}{t('analysis.pageDescBefore')}{processed_info?.customer_name || t('analysis.defaultBrandName')}{t('analysis.pageDescAfter')}

{/* Main Content Container */} -
+
{/* 매장명 & 주소 */}

{processed_info?.customer_name || t('analysis.brandNameFallback')}

@@ -298,7 +88,7 @@ const AnalysisResultSection: React.FC = ({ onBack, o {/* 레이더 차트 */}
{sellingPoints.length > 0 && ( - + )}
{/* 셀링 포인트 리스트 */} @@ -369,13 +159,10 @@ const AnalysisResultSection: React.FC = ({ onBack, o
{/* 콘텐츠 생성 버튼 */} -
+
- - {errorMessage && ( -
- {errorMessage} -
- )} - - {isGenerating && statusMessage && ( + {/* Generate Button / Status Message (교체) */} + {isGenerating && statusMessage ? (
@@ -551,6 +527,20 @@ const SoundStudioContent: React.FC = ({ {statusMessage}
+ ) : ( + + )} + + {errorMessage && ( +
+ {errorMessage} +
)}
From 1d868627e47ba0c3ee2d8263b52d511c51c91e39 Mon Sep 17 00:00:00 2001 From: hbyang Date: Mon, 9 Mar 2026 15:44:40 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=EA=B2=80=EC=83=89=EC=B0=BD=20url=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 23 ++++++----------------- package.json | 2 +- src/pages/Dashboard/UrlInputContent.tsx | 11 ++++++++++- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ec8865..b35197e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "react-i18next": "^16.5.4", - "recharts": "^3.7.0" + "recharts": "^3.8.0" }, "devDependencies": { "@types/node": "^22.14.0", @@ -52,7 +52,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1289,7 +1288,6 @@ "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1351,7 +1349,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1694,7 +1691,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -1815,7 +1811,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1857,7 +1852,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1867,7 +1861,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -1914,7 +1907,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -1944,15 +1936,15 @@ } }, "node_modules/recharts": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", - "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.0.tgz", + "integrity": "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==", "license": "MIT", "workspaces": [ "www" ], "dependencies": { - "@reduxjs/toolkit": "1.x.x || 2.x.x", + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", @@ -1977,8 +1969,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -2092,7 +2083,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2176,7 +2166,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index 90260e3..bdf154e 100755 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "react-i18next": "^16.5.4", - "recharts": "^3.7.0" + "recharts": "^3.8.0" }, "devDependencies": { "@types/node": "^22.14.0", diff --git a/src/pages/Dashboard/UrlInputContent.tsx b/src/pages/Dashboard/UrlInputContent.tsx index 12dec9c..525f44d 100644 --- a/src/pages/Dashboard/UrlInputContent.tsx +++ b/src/pages/Dashboard/UrlInputContent.tsx @@ -210,7 +210,16 @@ const UrlInputContent: React.FC = ({ onAnalyze, onAutocomp type={searchType === 'url' ? 'url' : 'text'} value={inputValue} onChange={(e) => { - const value = e.target.value; + let value = e.target.value; + + // URL 모드일 때 앞에 붙은 텍스트 제거 (예: "[네이버 지도] https://...") + if (searchType === 'url') { + const urlMatch = value.match(/https?:\/\/.+/); + if (urlMatch && urlMatch[0] !== value) { + value = urlMatch[0]; + } + } + setInputValue(value); // 업체명 검색일 때 자동완성 검색 (디바운스) From 388351e6d4c6e636b73d65e47ac61a6c7d08d016 Mon Sep 17 00:00:00 2001 From: hbyang Date: Mon, 9 Mar 2026 15:59:18 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=EB=9E=9C=EB=94=A9=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B2=80=EC=83=89=EC=B0=BD=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Dashboard/UrlInputContent.tsx | 6 +++--- src/pages/Landing/HeroSection.tsx | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/pages/Dashboard/UrlInputContent.tsx b/src/pages/Dashboard/UrlInputContent.tsx index 525f44d..6bfc84c 100644 --- a/src/pages/Dashboard/UrlInputContent.tsx +++ b/src/pages/Dashboard/UrlInputContent.tsx @@ -207,14 +207,14 @@ const UrlInputContent: React.FC = ({ onAnalyze, onAutocomp {/* 입력 필드 */}
{ let value = e.target.value; - // URL 모드일 때 앞에 붙은 텍스트 제거 (예: "[네이버 지도] https://...") + // URL 모드일 때 URL만 추출 (예: "[네이버 지도] https://...") if (searchType === 'url') { - const urlMatch = value.match(/https?:\/\/.+/); + const urlMatch = value.match(/https?:\/\/\S+/); if (urlMatch && urlMatch[0] !== value) { value = urlMatch[0]; } diff --git a/src/pages/Landing/HeroSection.tsx b/src/pages/Landing/HeroSection.tsx index 6320fe0..4318e86 100755 --- a/src/pages/Landing/HeroSection.tsx +++ b/src/pages/Landing/HeroSection.tsx @@ -376,7 +376,16 @@ const HeroSection: React.FC = ({ onAnalyze, onAutocomplete, on type="text" value={inputValue} onChange={(e) => { - const value = e.target.value; + let value = e.target.value; + + // URL 모드일 때 URL만 추출 (예: "[네이버 지도] https://...") + if (searchType === 'url') { + const urlMatch = value.match(/https?:\/\/\S+/); + if (urlMatch && urlMatch[0] !== value) { + value = urlMatch[0]; + } + } + setInputValue(value); setHighlightedIndex(-1); // 입력 시 하이라이트 초기화 if (localError) setLocalError(''); @@ -391,6 +400,16 @@ const HeroSection: React.FC = ({ onAnalyze, onAutocomplete, on }, 300); } }} + onPaste={(e: React.ClipboardEvent) => { + if (searchType !== 'url') return; + const pasted = e.clipboardData.getData('text'); + const urlMatch = pasted.match(/https?:\/\/[^\s]+/); + if (urlMatch) { + e.preventDefault(); + setInputValue(urlMatch[0]); + if (localError) setLocalError(''); + } + }} onFocus={() => { setIsFocused(true); if (searchType === 'name' && autocompleteResults.length > 0) { From 65ea09ffd5bcbeadf04cd25bb65141e948acf55a Mon Sep 17 00:00:00 2001 From: hbyang Date: Wed, 11 Mar 2026 13:33:20 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Dashboard/GenerationFlow.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/Dashboard/GenerationFlow.tsx b/src/pages/Dashboard/GenerationFlow.tsx index 4bb72a4..f39633c 100755 --- a/src/pages/Dashboard/GenerationFlow.tsx +++ b/src/pages/Dashboard/GenerationFlow.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Sidebar from '../../components/Sidebar'; import AssetManagementContent from './AssetManagementContent'; @@ -148,13 +148,18 @@ const GenerationFlow: React.FC = ({ }; const [imageList, setImageList] = useState(getInitialImageList()); + const prevAnalysisMIdRef = useRef(analysisData?.m_id); - // analysisData 변경 시 imageList 업데이트 + // analysisData 변경 시 m_id가 바뀐 경우(새로운 분석)에만 imageList 업데이트 + // m_id가 동일하면 언어 변경 등 기타 업데이트이므로 사용자가 수정한 이미지 목록 유지 useEffect(() => { console.log('[GenerationFlow] analysisData updated, m_id:', analysisData?.m_id); if (analysisData?.image_list && analysisData.image_list.length > 0) { - setImageList(analysisData.image_list.map(url => ({ type: 'url', url }))); + if (prevAnalysisMIdRef.current !== analysisData.m_id) { + setImageList(analysisData.image_list.map(url => ({ type: 'url', url }))); + } } + prevAnalysisMIdRef.current = analysisData?.m_id; }, [analysisData]); const handleRemoveImage = (index: number) => { From 7aedf072031714e0ef07c1a25016e3090a72bc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Fri, 13 Mar 2026 10:13:26 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=ED=8F=B0=ED=8A=B8=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?UI=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.css | 486 +++++++++---------- src/components/Footer.tsx | 3 +- src/components/SocialPostingModal.tsx | 14 +- src/locales/en.json | 3 +- src/locales/ko.json | 9 +- src/pages/Analysis/AnalysisResultSection.tsx | 10 +- src/pages/Analysis/GeometricChart.tsx | 34 +- src/pages/Dashboard/CompletionContent.tsx | 2 +- src/pages/Dashboard/DashboardContent.tsx | 91 ++-- 9 files changed, 313 insertions(+), 339 deletions(-) diff --git a/index.css b/index.css index 227d64b..d3fb75d 100644 --- a/index.css +++ b/index.css @@ -670,7 +670,7 @@ } .sidebar-item-label { - font-size: var(--text-sm); + font-size: 16px; font-weight: 700; white-space: nowrap; } @@ -802,7 +802,7 @@ position: relative; z-index: 1; padding: 4px 0; - font-size: 11px; + font-size: 14px; font-weight: 600; letter-spacing: 0.03em; color: rgba(255, 255, 255, 0.4); @@ -2083,7 +2083,7 @@ } .comp2-title-row { - padding: 0 0 16px; + padding: 16px; text-align: center; flex-shrink: 0; } @@ -2098,7 +2098,7 @@ } .comp2-page-subtitle { - font-size: 14px; + font-size: 16px; font-weight: 400; color: #9BCACC; line-height: 1.29; @@ -2313,7 +2313,7 @@ } .comp2-info-label { - font-size: 12px; + font-size: 14px; font-weight: 400; color: #9BCACC; line-height: 1.29; @@ -2362,7 +2362,7 @@ } .comp2-filesize { - font-size: 12px; + font-size: 14px; font-weight: 400; color: #9BCACC; line-height: 1.29; @@ -2386,9 +2386,9 @@ } .comp2-meta-label { - font-size: 12px; + font-size: 16px; font-weight: 400; - color: #E5F1F2; + color: #9BCACC; line-height: 1.29; } @@ -2406,7 +2406,7 @@ } .comp2-lyrics-text { - font-size: 12px; + font-size: 16px; font-weight: 400; color: #E5F1F2; line-height: 1.625; @@ -2500,7 +2500,7 @@ flex: 1; height: 40px; border-radius: 8px; - font-size: 12px; + font-size: 14px; font-weight: 600; letter-spacing: -0.006em; line-height: 1.19; @@ -3269,15 +3269,19 @@ } /* ===================================================== - Brand Intelligence v2 (피그마 디자인 기반) + Brand Intelligence v2 ===================================================== */ .bi2-page { + display: flex; + width: 100%; min-height: 100vh; - background: linear-gradient(to bottom, #002224, #01191a); - color: #E5F1F2; padding-top: 64px; padding-bottom: 160px; + flex-direction: column; + align-items: center; + background: linear-gradient(to bottom, #002224, #01191a); + color: #E5F1F2; font-family: 'Pretendard', sans-serif; overflow-x: auto; } @@ -3333,11 +3337,22 @@ /* 타이틀 영역 */ .bi2-page-title-section { display: flex; + max-width: 1440px; + padding: 68px 16px 50px 16px; flex-direction: column; + justify-content: center; align-items: center; gap: 24px; - padding: 8px 0 40px; + align-self: stretch; + margin: 0 auto; +} + +.bi2-page-divider { + height: 0; + width: calc(100% - 32px); max-width: 1440px; + border: none; + border-top: 1px solid var(--Color-teal-500, #067C80); margin: 0 auto; } @@ -3357,39 +3372,44 @@ display: flex; flex-direction: column; align-items: center; - gap: 8px; + gap: 20px; + align-self: stretch; } .bi2-main-title { - font-size: 48px; - font-weight: 600; - color: #FFFFFF; - letter-spacing: -0.006em; - line-height: 1.19; + color: var(--Color-white, #FFF); text-align: center; + font-family: Pretendard; + font-size: 48px; + font-style: normal; + font-weight: 600; + line-height: normal; + letter-spacing: -0.288px; + align-self: stretch; } .bi2-subtitle { - font-size: 14px; + font-size: 21px; font-weight: 400; color: #E5F1F2; letter-spacing: -0.006em; line-height: 1.19; text-align: center; + align-self: stretch; } /* 메인 컨테이너 */ .bi2-main-container { + display: flex; max-width: 1440px; min-width: 1000px; - margin: 0 auto; - background: #013032; - border: 1px solid #034A4D; - border-radius: 40px; - padding: 32px 32px 112px; - display: flex; + padding: 68px 32px 112px 32px; flex-direction: column; + justify-content: center; + align-items: flex-start; gap: 88px; + align-self: stretch; + margin: 0 auto; } /* 매장 헤더 */ @@ -3400,33 +3420,42 @@ } .bi2-store-name { - font-size: 30px; - font-weight: 700; - color: #FFFFFF; - letter-spacing: -0.006em; - line-height: 1.3; + color: var(--Color-white, #FFF); + font-family: Pretendard; + font-size: 40px; + font-style: normal; + font-weight: 600; + line-height: 130%; /* 52px */ + letter-spacing: -0.24px; } .bi2-store-address { - font-size: 14px; - font-weight: 400; - color: #9BCACC; - line-height: 1.29; + color: var(--Color-teal-200, #9BCACC); + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 22px; /* 137.5% */ + letter-spacing: -0.096px; } /* 섹션 공통 */ .bi2-section { display: flex; flex-direction: column; - gap: 16px; + align-items: flex-start; + gap: 20px; + align-self: stretch; } .bi2-section-title { - font-size: 20px; - font-weight: 700; - color: #E5F1F2; - letter-spacing: -0.006em; - line-height: 1.3; + color: var(--Color-white, #FFF); + font-family: Pretendard; + font-size: 28px; + font-style: normal; + font-weight: 600; + line-height: 130%; /* 36.4px */ + letter-spacing: -0.168px; } /* 브랜드 정체성 카드 */ @@ -3454,25 +3483,34 @@ } .bi2-label-sm { - font-size: 14px; - font-weight: 400; - color: #E5F1F2; - line-height: 1.29; + color: var(--Color-teal-50, #E5F1F2); + /* Body_600 */ + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 22px; /* 137.5% */ + letter-spacing: -0.096px; } .bi2-core-value { + color: var(--Color-mint-50, #F4FFFC); + font-family: Pretendard; font-size: 24px; + font-style: normal; font-weight: 700; - color: #F4FFFC; - letter-spacing: -0.006em; - line-height: 1.3; + line-height: 130%; /* 31.2px */ + letter-spacing: -0.144px; } .bi2-category-text { - font-size: 14px; - font-weight: 400; - color: #E5F1F2; - line-height: 1.29; + color: var(--Color-teal-50, #E5F1F2); + font-family: Pretendard; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: 22px; /* 122.222% */ + letter-spacing: -0.108px; } .bi2-identity-divider { @@ -3494,23 +3532,26 @@ } .bi2-body-text { + color: var(--Color-teal-100, #CEE5E6); + font-family: Pretendard; font-size: 18px; + font-style: normal; font-weight: 400; - color: #CEE5E6; - line-height: 1.6; - letter-spacing: -0.006em; + line-height: 160%; /* 28.8px */ + letter-spacing: -0.108px; } /* 셀링 포인트 카드 */ .bi2-selling-card { display: flex; - align-items: center; + padding: 16px 20px; justify-content: center; - gap: clamp(20px, 4vw, 80px); - background: #034245; - border: 1px solid #034A4D; + align-items: center; + gap: 100px; + align-self: stretch; border-radius: 20px; - padding: 16px 32px; + border: 1px solid var(--Color-teal-700, #034A4D); + background: var(--Color-teal-750, #01393B); } .bi2-selling-chart { @@ -3525,8 +3566,8 @@ @media (max-width: 768px) { .bi2-radar-container svg { - width: min(95vw, 420px) !important; - height: min(95vw, 420px) !important; + width: min(95vw, 440px) !important; + height: min(95vw, 360px) !important; } .bi2-radar-label-text { @@ -3593,24 +3634,29 @@ display: flex; align-items: stretch; gap: 16px; + width: 100%; } .bi2-persona-card { - flex: 1; - background: #034245; - border: 1px solid #034A4D; - border-radius: 20px; - padding: 20px; display: flex; + padding: 20px; flex-direction: column; + align-items: stretch; gap: 16px; + flex: 1 0 0; + align-self: stretch; + border-radius: 20px; + border: 1px solid var(--Color-teal-700, #034A4D); + background: var(--Color-teal-750, #01393B); } .bi2-persona-header { display: flex; - flex-direction: column; - gap: 8px; padding-bottom: 8px; + flex-direction: column; + align-items: flex-start; + gap: 4px; + align-self: stretch; } .bi2-persona-info { @@ -3620,27 +3666,33 @@ } .bi2-persona-name { - font-size: 18px; + color: var(--Color-teal-50, #E5F1F2); + font-family: Pretendard; + font-size: 20px; + font-style: normal; font-weight: 700; - color: #E5F1F2; - letter-spacing: -0.006em; - line-height: 1.3; + line-height: 130%; /* 26px */ + letter-spacing: -0.12px; } .bi2-persona-age { + color: var(--Color-teal-200, #9BCACC); + /* 14_600 */ + font-family: Pretendard; font-size: 14px; - font-weight: 400; - color: #9BCACC; - line-height: 1.29; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 128.571% */ } .bi2-persona-desc { + color: var(--Color-teal-100, #CEE5E6); + /* 14_600 */ + font-family: Pretendard; font-size: 14px; - font-weight: 400; - color: #CEE5E6; - line-height: 1.29; - word-break: keep-all; - overflow-wrap: break-word; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 128.571% */ } .bi2-persona-divider { @@ -3656,29 +3708,33 @@ } .bi2-persona-detail.grow { - flex: 1; + display: flex; + padding: 0 4px; + align-items: flex-start; + gap: 4px; + align-self: stretch; } .bi2-label-xs { + color: var(--Color-teal-100, #CEE5E6); + font-family: Pretendard; font-size: 14px; - font-weight: 400; - color: #CEE5E6; - line-height: 1.29; - white-space: nowrap; - flex-shrink: 0; + font-style: normal; + font-weight: 600; + line-height: 18px; /* 128.571% */ } .bi2-persona-detail-text { - font-size: 16px; - font-weight: 600; - color: #E5F1F2; - line-height: 26px; - letter-spacing: -0.006em; - white-space: pre-line; - text-align: right; - word-break: keep-all; - overflow-wrap: break-word; flex: 1; + color: var(--Color-teal-50, #E5F1F2); + text-align: right; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 26px; + letter-spacing: -0.096px; + white-space: pre-line; } /* 추천 타겟 키워드 */ @@ -3689,16 +3745,23 @@ } .bi2-keyword-subtitle { - font-size: 14px; - font-weight: 400; - color: #9BCACC; - line-height: 1.29; + color: var(--Color-teal-200, #9BCACC); + /* Body_600 */ + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 22px; /* 137.5% */ + letter-spacing: -0.096px; } .bi2-keyword-tags { display: flex; - flex-wrap: wrap; + align-items: flex-start; + align-content: flex-start; gap: 8px; + align-self: stretch; + flex-wrap: wrap; } .bi2-keyword-pill { @@ -3768,8 +3831,8 @@ .bi2-main-container { min-width: unset; margin: 0 16px; - padding: 24px 20px 80px; - gap: 48px; + padding: 24px 0px 80px; + gap: 64px; border-radius: 24px; } @@ -3779,8 +3842,12 @@ } .bi2-persona-grid { + display: flex; flex-direction: column; - } + align-items: flex-start; + gap: 20px; + align-self: stretch; + } .bi2-identity-bottom { flex-direction: column; @@ -3788,11 +3855,18 @@ } .bi2-main-title { - font-size: 28px; - } + color: var(--Color-white, #FFF); + text-align: center; + font-family: Pretendard; + font-size: 45px; + font-style: normal; + font-weight: 600; + line-height: normal; + letter-spacing: -0.288px; + } .bi2-store-name { - font-size: 24px; + font-size: 32px; } .bi2-selling-name { @@ -5664,6 +5738,7 @@ /* Dashboard Container */ .dashboard-container { width: 100%; + min-width: 375px; padding: 0.75rem; display: flex; flex-direction: column; @@ -5704,35 +5779,17 @@ } .dashboard-title { - font-size: var(--text-lg); + font-size: 32px; font-weight: 700; letter-spacing: -0.025em; } -@media (min-width: 640px) { - .dashboard-title { - font-size: var(--text-xl); - } -} - -@media (min-width: 768px) { - .dashboard-title { - font-size: 1.5rem; - } -} - .dashboard-description { - font-size: 15px; + font-size: 16px; color: var(--color-text-gray-500); margin-top: 0.125rem; } -@media (min-width: 640px) { - .dashboard-description { - font-size: 15px; - } -} - /* Stats Grid */ .stats-grid { flex-shrink: 0; @@ -5787,65 +5844,29 @@ } .stat-label { - font-size: 8px; + font-size: 14px; font-weight: 700; color: var(--color-text-gray-400); text-transform: uppercase; letter-spacing: 0.1em; } -@media (min-width: 640px) { - .stat-label { - font-size: 9px; - } -} - -@media (min-width: 768px) { - .stat-label { - font-size: 10px; - } -} - .stat-value { - font-size: var(--text-lg); + font-size: 24px; font-weight: 700; color: var(--color-text-white); line-height: 1.25; } -@media (min-width: 640px) { - .stat-value { - font-size: var(--text-xl); - } -} - -@media (min-width: 768px) { - .stat-value { - font-size: 1.5rem; - } -} - -@media (min-width: 1024px) { - .stat-value { - font-size: var(--text-3xl); - } -} - .stat-trend { display: inline-flex; align-items: center; gap: 2px; - font-size: 8px; + font-size: 12px; color: var(--color-mint); font-weight: 500; } -@media (min-width: 640px) { - .stat-trend { - font-size: 9px; - } -} - /* Chart Card */ .chart-card { flex: 1; @@ -5903,18 +5924,6 @@ letter-spacing: 0.1em; } -@media (min-width: 640px) { - .chart-title { - font-size: 9px; - } -} - -@media (min-width: 768px) { - .chart-title { - font-size: 10px; - } -} - .chart-legend { display: flex; gap: 0.375rem; @@ -5929,16 +5938,10 @@ } .chart-legend-text { - font-size: 8px; + font-size: 14px; color: var(--color-text-gray-400); } -@media (min-width: 640px) { - .chart-legend-text { - font-size: 9px; - } -} - .chart-container { flex: 1; position: relative; @@ -5967,12 +5970,6 @@ box-shadow: 0 20px 25px -5px rgba(166, 255, 234, 0.2); } -@media (min-width: 640px) { - .chart-badge-value { - font-size: 10px; - } -} - .chart-badge-line { width: 2px; height: 0.5rem; @@ -6034,15 +6031,15 @@ } .dashboard-last-updated { - font-size: 8px; + font-size: 12px; color: var(--color-text-gray-500); display: none; } -@media (min-width: 640px) { +@media (min-width: 768px) { .dashboard-last-updated { display: block; - font-size: 9px; + font-size: 12px; } } @@ -6112,19 +6109,12 @@ } .dashboard-section-title { - font-size: var(--text-sm); + font-size: 24px; font-weight: 600; color: var(--color-text-white); margin-bottom: 0.5rem; } -@media (min-width: 768px) { - .dashboard-section-title { - font-size: var(--text-base); - margin-bottom: 0.75rem; - } -} - /* Chart Legend Dual (for YoY comparison) */ .chart-legend-dual { display: flex; @@ -6189,7 +6179,7 @@ @media (min-width: 640px) { .platform-tab { padding: 0.5rem 1rem; - font-size: var(--text-sm); + /* font-size: var(--text-sm); */ gap: 0.5rem; } } @@ -6231,7 +6221,7 @@ padding: 0.375rem 0.875rem; background-color: transparent; color: var(--color-text-gray-400); - font-size: var(--text-sm); + font-size: 16px; white-space: nowrap; font-weight: 600; cursor: pointer; @@ -6334,30 +6324,12 @@ gap: 0.25rem; } -@media (min-width: 640px) { - .metric-card-value { - font-size: var(--text-2xl); - } -} - -@media (min-width: 768px) { - .metric-card-value { - font-size: 1.75rem; - } -} - .metric-card-unit { font-size: var(--text-xs); color: var(--color-text-gray-500); font-weight: 400; } -@media (min-width: 640px) { - .metric-card-unit { - font-size: var(--text-sm); - } -} - .metric-card-trend { display: flex; align-items: center; @@ -6366,12 +6338,6 @@ font-weight: 500; } -@media (min-width: 640px) { - .metric-card-trend { - font-size: var(--text-xs); - } -} - .metric-card-trend.up { color: var(--color-mint); } @@ -6394,9 +6360,10 @@ /* YoY Chart */ .yoy-chart-card { + min-width: 351px; background-color: var(--color-bg-card); border-radius: var(--radius-xl); - padding: 0.75rem; + padding: 0.5rem; border: 1px solid var(--color-border-white-5); box-shadow: var(--shadow-xl); margin-bottom: 0.75rem; @@ -6695,14 +6662,14 @@ @media (min-width: 768px) { .top-content-card { - padding: 1.5rem; + padding: 1rem; } } .top-content-list { display: flex; flex-direction: column; - gap: 0.75rem; + gap: 0.5rem; } .top-content-item { @@ -6768,7 +6735,8 @@ } .top-content-title { - font-size: var(--text-xs); + max-width: 215px; + font-size: 14px; font-weight: 600; color: var(--color-text-white); white-space: nowrap; @@ -6776,12 +6744,6 @@ text-overflow: ellipsis; } -@media (min-width: 640px) { - .top-content-title { - font-size: var(--text-sm); - } -} - .top-content-stats { display: flex; gap: 0.75rem; @@ -6791,31 +6753,19 @@ display: flex; align-items: center; gap: 0.25rem; - font-size: 10px; + font-size: 12px; color: var(--color-text-gray-400); } -@media (min-width: 640px) { - .top-content-stat { - font-size: var(--text-xs); - } -} - .top-content-stat svg { color: var(--color-text-gray-500); } .top-content-date { - font-size: 9px; + font-size: 12px; color: var(--color-text-gray-500); } -@media (min-width: 640px) { - .top-content-date { - font-size: 10px; - } -} - /* Audience Section */ .audience-section { margin-bottom: 0.75rem; @@ -6856,7 +6806,7 @@ } .audience-card-title { - font-size: var(--text-sm); + font-size: 20px; font-weight: 600; color: var(--color-text-gray-300); margin-bottom: 1rem; @@ -6876,7 +6826,7 @@ } .audience-bar-label { - font-size: var(--text-xs); + font-size: 14px; color: var(--color-text-gray-400); width: 60px; flex-shrink: 0; @@ -6898,7 +6848,7 @@ } .audience-bar-value { - font-size: var(--text-xs); + font-size: 14px; color: var(--color-text-white); width: 36px; text-align: right; @@ -6949,7 +6899,7 @@ display: flex; align-items: center; gap: 0.375rem; - font-size: var(--text-xs); + font-size: 14px; color: var(--color-text-gray-400); } @@ -7620,6 +7570,7 @@ align-items: center; gap: 1.25rem; flex-shrink: 0; + font-size: 14px; } .sound-column > *:not(.btn-generate-sound):not(.error-message-new):not(.status-message-new) { @@ -7674,7 +7625,7 @@ border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 8px; color: var(--color-text-white); - font-size: var(--text-sm); + font-size: 14px; font-weight: 600; cursor: pointer; transition: all var(--transition-normal); @@ -7959,7 +7910,7 @@ background: transparent; border: none; color: var(--color-text-white); - font-size: var(--text-sm); + font-size: 16px; font-family: inherit; resize: none; outline: none; @@ -8097,7 +8048,7 @@ } /* Responsive Styles for Sound Studio */ -@media (max-width: 639px) { +@media (min-width: 768px) { .progress-indicator { width: 100%; } @@ -8108,7 +8059,7 @@ .sound-type-btn { padding: 0.75rem 0.5rem; - font-size: var(--text-xs); + font-size: 14px; } .genre-row { @@ -8117,7 +8068,7 @@ .genre-btn { padding: 0.5rem 0.75rem; - font-size: var(--text-xs); + font-size: 14px; } } @@ -8143,6 +8094,7 @@ flex: 1; width: auto; min-height: 0; + font-size: 14px; } .lyrics-column { @@ -9593,7 +9545,7 @@ } .social-posting-title { - font-size: 1.125rem; + font-size: 24px; font-weight: 600; color: #FFFFFF; margin: 0; @@ -9726,7 +9678,7 @@ } .social-posting-video-specs { - font-size: 0.75rem; + font-size: 14px; color: rgba(255, 255, 255, 0.5); margin: 0; } @@ -9739,7 +9691,7 @@ } .social-posting-label { - font-size: 0.8rem; + font-size: 16px; font-weight: 500; color: rgba(255, 255, 255, 0.8); } @@ -9757,7 +9709,7 @@ border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; color: #FFFFFF; - font-size: 0.875rem; + font-size: 14px; font-family: 'Pretendard', sans-serif; transition: border-color 0.2s; } @@ -9836,7 +9788,7 @@ } .social-posting-radio .radio-label { - font-size: 0.85rem; + font-size: 14px; color: rgba(255, 255, 255, 0.8); } diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 93ea975..d0ae582 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -8,10 +8,11 @@ const Footer: React.FC = () => {
- CASTAD + ADO2

Copyright ⓒ O2O Inc. All rights reserved

+

{t('footer.company')}

{t('footer.businessNumber')}

{t('footer.headquarters')}

{t('footer.researchCenter')}

diff --git a/src/components/SocialPostingModal.tsx b/src/components/SocialPostingModal.tsx index a0f56c1..d47c43f 100644 --- a/src/components/SocialPostingModal.tsx +++ b/src/components/SocialPostingModal.tsx @@ -132,6 +132,7 @@ const SocialPostingModal: React.FC = ({ const [isPrivacyDropdownOpen, setIsPrivacyDropdownOpen] = useState(false); const [isLoadingAutoDescription, setIsLoadingAutoDescription] = useState(false); const [isHorizontalVideo, setIsHorizontalVideo] = useState(false); + const [videoMeta, setVideoMeta] = useState<{ width: number; height: number; duration: number } | null>(null); const channelDropdownRef = useRef(null); const privacyDropdownRef = useRef(null); @@ -175,12 +176,6 @@ const SocialPostingModal: React.FC = ({ if (isOpen) { loadSocialAccounts(); loadAutocomplete(); - // // 비디오 정보로 기본 제목 설정 - // if (video) { - // const date = new Date(video.created_at); - // const formattedDate = `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`; - // setTitle(`${video.store_name} ${formattedDate}`); - // } } }, [isOpen, video]); @@ -436,6 +431,7 @@ const SocialPostingModal: React.FC = ({ onLoadedMetadata={(e) => { const v = e.currentTarget; setIsHorizontalVideo(v.videoWidth > v.videoHeight); + setVideoMeta({ width: v.videoWidth, height: v.videoHeight, duration: v.duration }); }} />
@@ -453,7 +449,11 @@ const SocialPostingModal: React.FC = ({

{video.store_name} {new Date(video.created_at).toLocaleString('ko-KR')}

-

{t('social.videoSpecs')}

+

+ {videoMeta + ? `${videoMeta.width}×${videoMeta.height} · ${Math.floor(videoMeta.duration / 60)}:${String(Math.floor(videoMeta.duration % 60)).padStart(2, '0')}` + : t('social.videoSpecs')} +

{/* Channel Selector - Custom Dropdown */} diff --git a/src/locales/en.json b/src/locales/en.json index 8f88625..d561a5d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -16,6 +16,7 @@ "logout": "Log Out" }, "footer": { + "company":"O2O Inc.", "businessNumber": "Business Registration No. : 620-87-00810 | CEO : Ahn Sungmin", "headquarters": "HQ : 41593 Unicorn Lab Daegu A05, 5F, 111 Oksan-ro, Buk-gu, Daegu, Korea", "researchCenter": "R&D : 13453 Rooms 504-505 (East), KT Pangyo Bldg, 32 Geumto-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Korea", @@ -25,7 +26,7 @@ "social": { "title": "Social Media Posting", "postNumber": "Post 1", - "videoSpecs": "1080x1920 · 10 sec", + "videoSpecs": "720x1280 · 1:01", "channelLabel": "Post Channel", "loadingAccounts": "Loading accounts...", "noAccounts": "No connected social accounts.", diff --git a/src/locales/ko.json b/src/locales/ko.json index 45e8b59..07716a4 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -16,6 +16,7 @@ "logout": "로그아웃" }, "footer": { + "company":"㈜에이아이오투오", "businessNumber": "사업자 등록번호 : 620-87-00810 | 대표 : 안성민", "headquarters": "본사 : 41593 대구광역시 북구 옥산로 111, 5층 유니콘랩 대구 A05호", "researchCenter": "연구소 : 13453 경기 성남시 수정구 금토로 32 (금토동) (주)KT 판교빌딩 504호~505호 (East)", @@ -25,7 +26,7 @@ "social": { "title": "소셜 미디어 포스팅", "postNumber": "게시물 1", - "videoSpecs": "1080x1920 · 10초", + "videoSpecs": "720x1280 · 1:01", "channelLabel": "게시 채널", "loadingAccounts": "계정 로딩 중...", "noAccounts": "연결된 소셜 계정이 없습니다.", @@ -345,11 +346,11 @@ "conceptScalability": "컨셉 확장성", "noInfo": "정보 없음", "marketPositioning": "시장 포지셔닝", - "coreValue": "핵심 가치 (Core Value)", + "coreValue": "핵심 가치", "categoryDefinition": "카테고리 정의", - "targetPersona": "타겟 페르소나", + "targetPersona": "주요 고객 유형", "ageSuffix": "세", - "sellingPoints": "주요 셀링 포인트 (USP)", + "sellingPoints": "주요 셀링 포인트", "recommendedKeywords": "추천 타겟 키워드", "generateContent": "콘텐츠 생성", "pageDescBefore": "을 통해 도출된 ", diff --git a/src/pages/Analysis/AnalysisResultSection.tsx b/src/pages/Analysis/AnalysisResultSection.tsx index 3e63ded..88a49f7 100755 --- a/src/pages/Analysis/AnalysisResultSection.tsx +++ b/src/pages/Analysis/AnalysisResultSection.tsx @@ -48,6 +48,8 @@ const AnalysisResultSection: React.FC = ({ onBack, o
+
+ {/* Main Content Container */}
{/* 매장명 & 주소 */} @@ -130,9 +132,11 @@ const AnalysisResultSection: React.FC = ({ onBack, o
{t('analysis.favorKeywords', { defaultValue: '선호 키워드' })} -

- {persona.favor_target.join('\n')} -

+
+ {persona.favor_target.map((item, i) => ( +

{item}

+ ))} +
diff --git a/src/pages/Analysis/GeometricChart.tsx b/src/pages/Analysis/GeometricChart.tsx index b406cfa..d4b92b1 100644 --- a/src/pages/Analysis/GeometricChart.tsx +++ b/src/pages/Analysis/GeometricChart.tsx @@ -76,11 +76,27 @@ export const GeometricChart: React.FC = ({ data }) => { style={{ overflow: 'visible', width: '440px', height: '440px' }} > - - + {/* 레이더 폴리곤 fill: radial gradient */} + + + + + {/* 레이더 폴리곤 filter: outer drop-shadow + inset glow */} + + {/* Outer drop shadow */} + + {/* Inset glow: invert alpha → blur → composite inside */} + + + + + + + - + + @@ -118,11 +134,11 @@ export const GeometricChart: React.FC = ({ data }) => { {/* 데이터 폴리곤 */} {/* 데이터 포인트 */} @@ -132,8 +148,8 @@ export const GeometricChart: React.FC = ({ data }) => { cx={point.x} cy={point.y} r="4" - fill="#2DD4BF" - stroke="#1A1A2E" + fill="#ffffff" + stroke="#ffffff" strokeWidth="2" /> ))} @@ -145,7 +161,7 @@ export const GeometricChart: React.FC = ({ data }) => { const rank = rankMap.get(i) || i + 1; const isTopThree = rank <= 3; const isLeftSide = Math.cos(angle) < -0.1; - const textX = isLeftSide ? -14 : 14; + const textX = isLeftSide ? -20 : 20; const textAnchor: 'start' | 'end' = isLeftSide ? 'end' : 'start'; return ( diff --git a/src/pages/Dashboard/CompletionContent.tsx b/src/pages/Dashboard/CompletionContent.tsx index b9208e7..4da046e 100755 --- a/src/pages/Dashboard/CompletionContent.tsx +++ b/src/pages/Dashboard/CompletionContent.tsx @@ -385,7 +385,7 @@ const CompletionContent: React.FC = ({

{getFileName()}

-

19.6MB

+ {/*

19.6MB

*/}
diff --git a/src/pages/Dashboard/DashboardContent.tsx b/src/pages/Dashboard/DashboardContent.tsx index 027e118..91c5ed5 100755 --- a/src/pages/Dashboard/DashboardContent.tsx +++ b/src/pages/Dashboard/DashboardContent.tsx @@ -618,21 +618,22 @@ const DashboardContent: React.FC = ({ onNavigate }) => { // 블러 조건: 1)계정 미연결 2)업로드 영상 없음 3)데이터 없음 const isBlurred = accounts.length === 0 || isEmptyState || !dashboardData; - // API 데이터 우선 사용, 없거나 영상 없음(isEmptyState) 시 Mock 데이터로 폴백 - const contentMetrics = (!isEmptyState && dashboardData?.contentMetrics?.length) ? dashboardData.contentMetrics : MOCK_CONTENT_METRICS; - const topContent = (!isEmptyState && dashboardData?.topContent?.length) ? dashboardData.topContent : MOCK_TOP_CONTENT; - const hasRealAgeGroups = !isEmptyState && !!dashboardData?.audienceData?.ageGroups?.some(g => g.percentage > 0); - const hasRealGender = !isEmptyState && ((dashboardData?.audienceData?.gender?.male ?? 0) + (dashboardData?.audienceData?.gender?.female ?? 0)) > 0; - const hasRealTopRegions = !isEmptyState && !!dashboardData?.audienceData?.topRegions?.some(r => r.percentage > 0); + // showMockData=true면 전체 mock 강제, 아니면 API 우선 / isEmptyState 시 mock 폴백 + const useReal = !showMockData && !isEmptyState; + const contentMetrics = (useReal && dashboardData?.contentMetrics?.length) ? dashboardData.contentMetrics : MOCK_CONTENT_METRICS; + const topContent = (useReal && dashboardData?.topContent?.length) ? dashboardData.topContent : MOCK_TOP_CONTENT; + const hasRealAgeGroups = useReal && !!dashboardData?.audienceData?.ageGroups?.some(g => g.percentage > 0); + const hasRealGender = useReal && ((dashboardData?.audienceData?.gender?.male ?? 0) + (dashboardData?.audienceData?.gender?.female ?? 0)) > 0; + const hasRealTopRegions = useReal && !!dashboardData?.audienceData?.topRegions?.some(r => r.percentage > 0); const hasRealAudienceData = hasRealAgeGroups && hasRealGender && hasRealTopRegions; const audienceData = hasRealAudienceData ? dashboardData!.audienceData : MOCK_AUDIENCE_DATA; // mode별 차트 데이터를 ChartDataPoint 통합 형식으로 변환 const chartData: ChartDataPoint[] = mode === 'month' - ? ((!isEmptyState && dashboardData?.monthlyData?.length) + ? ((useReal && dashboardData?.monthlyData?.length) ? dashboardData.monthlyData.map((d: MonthlyData) => ({ label: d.month, current: d.thisYear, previous: d.lastYear })) : MOCK_MONTHLY_DATA.map((d: MonthlyData) => ({ label: d.month, current: d.thisYear, previous: d.lastYear }))) - : ((!isEmptyState && dashboardData?.dailyData?.length) + : ((useReal && dashboardData?.dailyData?.length) ? dashboardData.dailyData.map((d: DailyData) => ({ label: d.date, current: d.thisPeriod, previous: d.lastPeriod })) : MOCK_DAILY_DATA.map((d: DailyData) => ({ label: d.date, current: d.thisPeriod, previous: d.lastPeriod }))); @@ -657,8 +658,8 @@ const DashboardContent: React.FC = ({ onNavigate }) => {
-

아직 업로드된 영상이 없습니다.

-

ADO2에서 영상을 업로드하면 실제 통계가 표시됩니다.

+

아직 업로드된 영상이 없습니다.

+

ADO2에서 영상을 업로드하면 실제 통계가 표시됩니다.

@@ -786,7 +787,7 @@ const DashboardContent: React.FC = ({ onNavigate }) => {

{t('dashboard.contentPerformance')}

-

ADO2에서 업로드한 최근 30개의 영상 통계가 표시됩니다.

+

ADO2에서 업로드한 최근 30개의 영상 통계가 표시됩니다.

- )} + {/* mock 데이터 보기 버튼 */} +
); }; From 3f3f8bb159047cadb1c9b1d699b95306f3a081ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Thu, 19 Mar 2026 15:05:12 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=ED=8F=B0=ED=8A=B8=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20UI=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.css | 16 +++++++--------- src/components/Sidebar.tsx | 2 +- src/components/SocialPostingModal.tsx | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/index.css b/index.css index d3fb75d..2f1ca21 100644 --- a/index.css +++ b/index.css @@ -2071,7 +2071,7 @@ .comp2-container { width: calc(100% - 64px); max-width: 1440px; - margin: 16px; + margin: 16px auto; background: #01393B; border-radius: 24px; padding: 24px; @@ -2313,7 +2313,7 @@ } .comp2-info-label { - font-size: 14px; + font-size: 16px; font-weight: 400; color: #9BCACC; line-height: 1.29; @@ -2354,7 +2354,7 @@ } .comp2-filename { - font-size: 16px; + font-size: 20px; font-weight: 700; color: #FFFFFF; letter-spacing: -0.006em; @@ -9634,7 +9634,7 @@ gap: 1.25rem; } -.social-posting-video-info { +/* .social-posting-video-info { display: flex; align-items: center; gap: 0.5rem; @@ -9662,16 +9662,14 @@ color: rgba(255, 255, 255, 0.4); font-size: 1rem; cursor: pointer; -} +} */ .social-posting-video-meta { - padding: 0.75rem 1rem; - background: rgba(255, 255, 255, 0.03); - border-radius: 8px; + padding: 0.5rem; } .social-posting-video-title { - font-size: 0.875rem; + font-size: 18px; font-weight: 500; color: #FFFFFF; margin: 0 0 0.25rem 0; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index acbd509..f322c56 100755 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -82,7 +82,7 @@ const Sidebar: React.FC = ({ activeItem, onNavigate, onHome, userI { id: '대시보드', label: t('sidebar.dashboard'), disabled: false, icon: }, { id: '새 프로젝트 만들기', label: t('sidebar.newProject'), disabled: false, icon: }, { id: 'ADO2 콘텐츠', label: t('sidebar.ado2Contents'), disabled: false, icon: }, - { id: '내 콘텐츠', label: t('sidebar.myContents'), disabled: true, icon: }, + // { id: '내 콘텐츠', label: t('sidebar.myContents'), disabled: true, icon: }, { id: '콘텐츠 캘린더', label: '콘텐츠 캘린더', disabled: false, icon: }, { id: '내 정보', label: t('sidebar.myInfo'), disabled: false, icon: }, ]; diff --git a/src/components/SocialPostingModal.tsx b/src/components/SocialPostingModal.tsx index d47c43f..5fe77d5 100644 --- a/src/components/SocialPostingModal.tsx +++ b/src/components/SocialPostingModal.tsx @@ -440,10 +440,10 @@ const SocialPostingModal: React.FC = ({ {/* Right: Form */}
{/* Video Info */} -
+ {/*
{t('social.postNumber')} + -
+
*/}