From ecbd81d091dce7dc519fac09dfd628c957fb90cc Mon Sep 17 00:00:00 2001 From: hbyang Date: Wed, 24 Dec 2025 13:09:05 +0900 Subject: [PATCH] =?UTF-8?q?css=20=EC=A0=95=EB=A6=AC=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.css | 3319 +++++++++++++++++ src/App.tsx | 29 +- src/components/Header.tsx | 15 +- src/components/Sidebar.tsx | 77 +- src/pages/Analysis/AnalysisResultSection.tsx | 172 +- src/pages/Analysis/LoadingSection.tsx | 18 +- .../Dashboard/AssetManagementContent.tsx | 79 +- .../Dashboard/BusinessSettingsContent.tsx | 28 +- src/pages/Dashboard/CompletionContent.tsx | 114 +- src/pages/Dashboard/DashboardContent.tsx | 58 +- src/pages/Dashboard/GenerationFlow.tsx | 36 +- src/pages/Dashboard/SoundStudioContent.tsx | 151 +- src/pages/Landing/DisplaySection.tsx | 49 +- src/pages/Landing/HeroSection.tsx | 48 +- src/pages/Landing/WelcomeSection.tsx | 50 +- src/pages/Login/LoginSection.tsx | 20 +- 16 files changed, 3778 insertions(+), 485 deletions(-) create mode 100644 index.css diff --git a/index.css b/index.css new file mode 100644 index 0000000..6e0f971 --- /dev/null +++ b/index.css @@ -0,0 +1,3319 @@ +/* ===================================================== + CASTAD Design System - CSS Variables & Component Classes + ===================================================== */ + +/* ===================================================== + CSS Variables - Design Tokens + ===================================================== */ +:root { + /* Primary Colors */ + --color-mint: #a6ffea; + --color-mint-hover: #8affda; + --color-mint-glow: rgba(166, 255, 234, 0.6); + --color-mint-10: rgba(166, 255, 234, 0.1); + --color-mint-20: rgba(166, 255, 234, 0.2); + --color-mint-30: rgba(166, 255, 234, 0.3); + --color-mint-50: rgba(166, 255, 234, 0.5); + + --color-purple: #a682ff; + --color-purple-hover: #9570f0; + --color-purple-glow: rgba(166, 130, 255, 0.2); + --color-purple-80: rgba(166, 130, 255, 0.8); + + /* Background Colors */ + --color-bg-dark: #121a1d; + --color-bg-darker: #0d1416; + --color-bg-card: #1c2a2e; + --color-bg-card-inner: #2a3a3e; + + /* Text Colors */ + --color-text-white: #ffffff; + --color-text-gray-300: #d1d5db; + --color-text-gray-400: #9ca3af; + --color-text-gray-500: #6b7280; + + /* Border Colors */ + --color-border-white-5: rgba(255, 255, 255, 0.05); + --color-border-white-10: rgba(255, 255, 255, 0.1); + --color-border-gray-600: #4b5563; + --color-border-gray-700: #374151; + + /* Shadows */ + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-purple: 0 10px 15px -3px rgba(166, 130, 255, 0.2); + --shadow-mint-glow: 0 0 10px rgba(166, 255, 234, 0.6); + + /* Spacing */ + --spacing-page: 1.5rem; + --spacing-page-md: 2rem; + + /* Border Radius */ + --radius-sm: 0.5rem; + --radius-md: 0.75rem; + --radius-lg: 1rem; + --radius-xl: 1.25rem; + --radius-2xl: 1rem; + --radius-3xl: 1.5rem; + --radius-full: 9999px; + + /* Font Sizes */ + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + --text-3xl: 1.875rem; + --text-4xl: 2.25rem; + + /* Transitions */ + --transition-fast: 150ms; + --transition-normal: 200ms; + --transition-slow: 300ms; +} + +/* ===================================================== + Layout Components + ===================================================== */ + +/* Page Container */ +.page-container { + display: flex; + flex-direction: column; + height: 100%; + padding: var(--spacing-page); + overflow: hidden; +} + +@media (min-width: 768px) { + .page-container { + padding: var(--spacing-page-md); + } +} + +/* Page Container - Full Height with Background */ +.page-container-full { + width: 100%; + min-height: 100dvh; + color: var(--color-text-white); + display: flex; + flex-direction: column; + padding: var(--spacing-page); + background-color: var(--color-bg-dark); +} + +@media (min-width: 768px) { + .page-container-full { + padding: var(--spacing-page-md); + } +} + +@media (min-width: 1024px) { + .page-container-full { + padding: 2.5rem; + } +} + +/* Main Content Area */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 1.5rem; + max-width: 72rem; + margin-left: auto; + margin-right: auto; + width: 100%; + min-height: 0; + overflow: hidden; +} + +@media (min-width: 1024px) { + .main-content { + flex-direction: row; + } +} + +/* Dashboard Layout */ +.dashboard-layout { + display: flex; + width: 100%; + height: 100dvh; + background-color: var(--color-bg-darker); + color: var(--color-text-white); + overflow: hidden; +} + +.dashboard-content { + flex: 1; + height: 100%; + position: relative; + overflow: hidden; + padding-left: 0; +} + +/* ===================================================== + Card Components + ===================================================== */ + +/* Base Card */ +.card { + background-color: var(--color-bg-card); + border-radius: var(--radius-3xl); + padding: var(--spacing-page); + border: 1px solid var(--color-border-white-5); + box-shadow: var(--shadow-xl); +} + +@media (min-width: 768px) { + .card { + padding: var(--spacing-page-md); + } +} + +/* Card Variants */ +.card-flex { + display: flex; + flex-direction: column; +} + +.card-flex-1 { + flex: 1; +} + +.card-flex-2 { + flex: 2; +} + +.card-overflow-hidden { + overflow: hidden; +} + +/* Card Inner (Darker Background) */ +.card-inner { + background-color: rgba(18, 26, 29, 0.5); + border-radius: var(--radius-2xl); + border: 1px solid var(--color-border-white-5); + padding: 1rem; +} + +/* ===================================================== + Header Components + ===================================================== */ + +/* Page Header */ +.page-header { + text-align: center; + margin-bottom: 2rem; + flex-shrink: 0; +} + +/* Page Title */ +.page-title { + font-size: var(--text-3xl); + font-weight: 700; + margin-bottom: 0.5rem; + letter-spacing: -0.025em; +} + +@media (min-width: 768px) { + .page-title { + font-size: var(--text-4xl); + } +} + +/* Page Subtitle */ +.page-subtitle { + color: var(--color-text-gray-400); + font-size: var(--text-base); +} + +@media (min-width: 768px) { + .page-subtitle { + font-size: var(--text-lg); + } +} + +/* Section Title (Mint color uppercase) */ +.section-title { + color: var(--color-mint); + font-size: var(--text-sm); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1rem; + flex-shrink: 0; +} + +/* Section Title Purple */ +.section-title-purple { + color: var(--color-purple); + font-size: var(--text-sm); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1.25rem; + display: block; +} + +/* ===================================================== + Button Components + ===================================================== */ + +/* Primary Button (Purple) */ +.btn-primary { + background-color: var(--color-purple); + color: var(--color-text-white); + font-weight: 700; + padding: 1rem 4rem; + border-radius: var(--radius-full); + transition: all var(--transition-normal); + transform: scale(1); + box-shadow: var(--shadow-purple); + font-size: var(--text-lg); + border: none; + cursor: pointer; +} + +.btn-primary:hover { + background-color: var(--color-purple-hover); +} + +.btn-primary:active { + transform: scale(0.95); +} + +.btn-primary:disabled { + background-color: #4b5563; + color: var(--color-text-gray-400); + cursor: not-allowed; + box-shadow: none; +} + +/* Secondary Button (Mint) */ +.btn-secondary { + background-color: var(--color-mint); + color: var(--color-bg-dark); + font-weight: 700; + padding: 1rem; + border-radius: var(--radius-xl); + transition: all var(--transition-normal); + transform: scale(1); + font-size: var(--text-base); + border: none; + cursor: pointer; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.btn-secondary:hover { + background-color: var(--color-mint-hover); +} + +.btn-secondary:active { + transform: scale(0.98); +} + +.btn-secondary:disabled { + background-color: rgba(166, 255, 234, 0.5); + color: rgba(18, 26, 29, 0.5); + cursor: not-allowed; +} + +/* Outline Button */ +.btn-outline { + background-color: transparent; + border: 1px solid var(--color-border-gray-600); + color: var(--color-text-white); + font-weight: 700; + padding: 1rem 4rem; + border-radius: var(--radius-full); + transition: all var(--transition-normal); + font-size: var(--text-base); + cursor: pointer; +} + +.btn-outline:hover { + border-color: var(--color-text-gray-500); +} + +/* Back Button */ +.btn-back { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1.25rem; + border-radius: var(--radius-full); + border: 1px solid var(--color-border-gray-600); + background-color: transparent; + color: var(--color-text-gray-300); + font-size: var(--text-base); + transition: background-color var(--transition-normal); + cursor: pointer; +} + +.btn-back:hover { + background-color: rgba(31, 41, 55, 1); +} + +/* Regenerate Button (Mint Outline) */ +.btn-regenerate { + margin-top: 1rem; + width: 100%; + padding: 0.75rem; + background-color: var(--color-mint-20); + color: var(--color-mint); + border: 1px solid var(--color-mint-30); + font-weight: 700; + border-radius: var(--radius-xl); + transition: background-color var(--transition-normal); + font-size: var(--text-base); + flex-shrink: 0; + cursor: pointer; +} + +.btn-regenerate:hover { + background-color: var(--color-mint-30); +} + +.btn-regenerate:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Selection Button */ +.btn-select { + padding: 0.75rem; + border-radius: var(--radius-xl); + border: 1px solid var(--color-border-gray-700); + background-color: rgba(18, 26, 29, 0.4); + color: var(--color-text-gray-400); + font-size: var(--text-sm); + font-weight: 700; + transition: all var(--transition-normal); + cursor: pointer; +} + +.btn-select:hover { + border-color: var(--color-border-gray-600); +} + +.btn-select.active { + border-color: var(--color-mint); + background-color: var(--color-bg-dark); + color: var(--color-mint); +} + +.btn-select:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* ===================================================== + Form Components + ===================================================== */ + +/* Select Input */ +.select-input { + width: 100%; + padding: 0.75rem 1rem; + border-radius: var(--radius-xl); + background-color: var(--color-bg-dark); + border: 1px solid var(--color-border-gray-700); + font-size: var(--text-base); + color: var(--color-text-gray-300); +} + +.select-input:focus { + outline: none; + border-color: var(--color-mint); +} + +.select-input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Text Input */ +.text-input { + width: 100%; + padding: 0.75rem 1rem; + border-radius: var(--radius-xl); + background-color: var(--color-bg-dark); + border: 1px solid var(--color-border-gray-700); + font-size: var(--text-base); + color: var(--color-text-gray-300); +} + +.text-input:focus { + outline: none; + border-color: var(--color-mint); +} + +/* Textarea - Lyrics */ +.textarea-lyrics { + flex: 1; + overflow-y: auto; + font-size: var(--text-sm); + color: var(--color-text-gray-400); + background-color: transparent; + resize: none; + border: none; + padding-right: 0.5rem; + line-height: 1.625; + min-height: 0; +} + +.textarea-lyrics:focus { + outline: none; + color: var(--color-text-gray-300); +} + +/* ===================================================== + Navigation Components + ===================================================== */ + +/* Back Button Container */ +.back-button-container { + display: flex; + justify-content: flex-start; + margin-bottom: 1.5rem; + margin-left: 2.5rem; + flex-shrink: 0; +} + +@media (min-width: 768px) { + .back-button-container { + margin-left: 0; + } +} + +/* Bottom Button Container */ +.bottom-button-container { + display: flex; + justify-content: center; + padding: 1.5rem; + flex-shrink: 0; +} + +/* ===================================================== + Sidebar Components + ===================================================== */ + +/* Sidebar Container */ +.sidebar { + position: fixed; + height: 100vh; + display: flex; + flex-direction: column; + background-color: var(--color-bg-dark); + border-right: 1px solid var(--color-border-white-5); + transition: all var(--transition-slow); + z-index: 50; +} + +@media (min-width: 768px) { + .sidebar { + position: relative; + } +} + +.sidebar.expanded { + width: 15rem; +} + +.sidebar.collapsed { + width: 5rem; +} + +.sidebar.mobile-open { + transform: translateX(0); +} + +.sidebar.mobile-closed { + transform: translateX(-100%); +} + +@media (min-width: 768px) { + .sidebar.mobile-closed { + transform: translateX(0); + } +} + +/* Sidebar Header */ +.sidebar-header { + padding: 1.25rem; + display: flex; + align-items: center; + justify-content: space-between; +} + +.sidebar-header.collapsed { + flex-direction: column; + gap: 1rem; +} + +/* Sidebar Logo */ +.sidebar-logo { + font-family: 'Playfair Display', serif; + font-style: italic; + font-size: var(--text-2xl); + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text-white); + cursor: pointer; + transition: color var(--transition-normal); +} + +.sidebar-logo:hover { + color: var(--color-mint); +} + +/* Sidebar Menu */ +.sidebar-menu { + flex: 1; + padding: 0 0.75rem; + margin-top: 1rem; + overflow-y: auto; +} + +/* Sidebar Menu Item */ +.sidebar-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + border-radius: var(--radius-xl); + transition: all var(--transition-normal); + cursor: pointer; + margin-bottom: 0.25rem; + position: relative; + overflow: hidden; +} + +.sidebar-item.collapsed { + justify-content: center; + width: 3rem; + height: 3rem; + margin-left: auto; + margin-right: auto; + padding: 0; +} + +.sidebar-item.active { + background-color: var(--color-mint); + color: var(--color-bg-dark); +} + +.sidebar-item:not(.active) { + color: var(--color-text-gray-400); +} + +.sidebar-item:not(.active):hover { + background-color: rgba(255, 255, 255, 0.05); + color: var(--color-text-white); +} + +.sidebar-item-icon { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: color var(--transition-normal); +} + +.sidebar-item.active .sidebar-item-icon { + color: var(--color-bg-dark); +} + +.sidebar-item-label { + font-size: var(--text-sm); + font-weight: 700; + white-space: nowrap; +} + +/* Sidebar Footer */ +.sidebar-footer { + padding: 1rem; + margin-top: auto; + display: flex; + flex-direction: column; + gap: 1rem; +} + +/* Credit Card */ +.credit-card { + background-color: var(--color-bg-card); + border-radius: var(--radius-2xl); + padding: 1rem; + border: 1px solid var(--color-border-white-5); +} + +.credit-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.credit-label { + color: var(--color-text-gray-400); + font-size: var(--text-sm); + font-weight: 500; +} + +.credit-value { + color: var(--color-text-white); + font-size: var(--text-sm); + font-weight: 700; +} + +.credit-bar { + width: 100%; + height: 0.375rem; + background-color: #1f2937; + border-radius: var(--radius-full); + overflow: hidden; + margin-bottom: 0.75rem; +} + +.credit-bar-fill { + height: 100%; + background-color: var(--color-mint); +} + +.credit-upgrade-btn { + width: 100%; + padding: 0.625rem; + background-color: var(--color-bg-dark); + color: var(--color-text-white); + font-size: var(--text-sm); + font-weight: 700; + border-radius: var(--radius-lg); + border: 1px solid var(--color-border-gray-700); + transition: background-color var(--transition-normal); + cursor: pointer; +} + +.credit-upgrade-btn:hover { + background-color: #1f2937; +} + +/* Profile Section */ +.profile-section { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0 0.5rem; +} + +.profile-section.collapsed { + flex-direction: column; +} + +.profile-avatar { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-full); + border: 1px solid var(--color-border-gray-700); +} + +.profile-name { + color: var(--color-text-white); + font-size: var(--text-sm); + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Logout Button */ +.logout-btn { + width: 100%; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + color: var(--color-text-gray-400); + background: none; + border: none; + transition: color var(--transition-normal); + cursor: pointer; +} + +.logout-btn:hover { + color: var(--color-text-white); +} + +.logout-btn.collapsed { + justify-content: center; +} + +.logout-btn-label { + font-size: var(--text-sm); + font-weight: 700; +} + +/* Mobile Menu Button */ +.mobile-menu-btn { + position: fixed; + top: 1rem; + left: 1rem; + z-index: 40; + padding: 0.625rem; + background-color: var(--color-bg-card); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border-white-10); + color: var(--color-text-gray-400); + cursor: pointer; +} + +.mobile-menu-btn:hover { + color: var(--color-text-white); +} + +@media (min-width: 768px) { + .mobile-menu-btn { + display: none; + } +} + +/* Mobile Overlay */ +.mobile-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 40; +} + +@media (min-width: 768px) { + .mobile-overlay { + display: none; + } +} + +/* ===================================================== + Player Components + ===================================================== */ + +/* Player Bar */ +.player-bar { + background-color: rgba(0, 0, 0, 0.4); + border-radius: var(--radius-full); + padding: 0.75rem; + display: flex; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; +} + +/* Play Button */ +.play-btn { + color: var(--color-mint); + flex-shrink: 0; + transition: transform var(--transition-normal); + background: none; + border: none; + cursor: pointer; + padding: 0; +} + +.play-btn:hover { + transform: scale(1.1); +} + +.play-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Progress Bar */ +.progress-bar-container { + flex: 1; + height: 0.375rem; + background-color: #1f2937; + border-radius: var(--radius-full); + position: relative; + cursor: pointer; +} + +.progress-bar-container:disabled, +.progress-bar-container.disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.progress-bar-fill { + position: absolute; + left: 0; + top: 0; + height: 100%; + background-color: var(--color-mint); + border-radius: var(--radius-full); +} + +.progress-bar-thumb { + position: absolute; + top: 50%; + transform: translateY(-50%) translateX(-50%); + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; +} + +.progress-bar-thumb-glow { + position: absolute; + width: 1rem; + height: 1rem; + background-color: var(--color-mint); + border-radius: var(--radius-full); + opacity: 0.2; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +.progress-bar-thumb-dot { + width: 0.75rem; + height: 0.75rem; + background-color: var(--color-mint); + border-radius: var(--radius-full); + box-shadow: var(--shadow-mint-glow); + border: 1px solid rgba(18, 26, 29, 0.2); +} + +/* Time Display */ +.time-display { + font-size: var(--text-sm); + color: var(--color-text-gray-500); + font-family: monospace; + text-align: right; + white-space: nowrap; +} + +/* ===================================================== + Image Grid Components + ===================================================== */ + +/* Image Grid */ +.image-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.75rem; +} + +.image-grid-4 { + grid-template-columns: repeat(4, 1fr); + gap: 0.5rem; +} + +/* Image Item */ +.image-item { + aspect-ratio: 1; + background-color: rgba(18, 26, 29, 0.5); + border-radius: var(--radius-xl); + border: 1px solid var(--color-border-white-5); + position: relative; + overflow: hidden; +} + +.image-item img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.image-item-badge { + position: absolute; + top: 0.5rem; + left: 0.5rem; + padding: 0.25rem 0.5rem; + background-color: var(--color-purple-80); + border-radius: 0.25rem; + font-size: var(--text-xs); + color: var(--color-text-white); + font-weight: 500; +} + +.image-item-remove { + position: absolute; + top: 0.5rem; + right: 0.5rem; + width: 1.75rem; + height: 1.75rem; + background-color: rgba(0, 0, 0, 0.6); + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.7); + border: none; + cursor: pointer; + transition: all var(--transition-normal); +} + +.image-item-remove:hover { + background-color: rgba(239, 68, 68, 0.8); + color: var(--color-text-white); +} + +/* ===================================================== + Upload Components + ===================================================== */ + +/* Upload Zone */ +.upload-zone { + flex: 1; + border: 2px dashed var(--color-border-gray-600); + border-radius: var(--radius-2xl); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.5rem; + text-align: center; + cursor: pointer; + transition: border-color var(--transition-normal); +} + +.upload-zone:hover { + border-color: rgba(166, 255, 234, 0.5); +} + +.upload-zone-icon { + margin-bottom: 1rem; + color: var(--color-text-gray-500); + transition: color var(--transition-normal); +} + +.upload-zone:hover .upload-zone-icon { + color: var(--color-mint); +} + +.upload-zone-title { + color: var(--color-text-gray-300); + font-size: var(--text-base); + font-weight: 500; + line-height: 1.625; + margin-bottom: 0.5rem; +} + +.upload-zone-subtitle { + color: var(--color-text-gray-500); + font-size: var(--text-sm); +} + +/* ===================================================== + Tag Components + ===================================================== */ + +/* Tag */ +.tag { + padding: 0.625rem 1.25rem; + background-color: var(--color-bg-card-inner); + border-radius: var(--radius-full); + font-size: var(--text-base); + color: #e5e7eb; + border: 1px solid var(--color-border-white-10); +} + +/* Tag with Dot */ +.tag-dot { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: var(--text-sm); + color: var(--color-text-gray-400); +} + +.tag-dot::before { + content: ''; + width: 0.375rem; + height: 0.375rem; + border-radius: var(--radius-full); + background-color: var(--color-mint); +} + +/* ===================================================== + Social Card Components + ===================================================== */ + +/* Social Card */ +.social-card { + display: flex; + align-items: center; + gap: 1rem; + background-color: var(--color-bg-dark); + padding: 1rem; + border-radius: var(--radius-2xl); + border: 1px solid var(--color-border-white-5); + transition: all var(--transition-normal); + cursor: pointer; +} + +.social-card:hover { + border-color: var(--color-border-white-10); +} + +.social-card.selected { + border-color: var(--color-mint); + box-shadow: 0 0 15px rgba(166, 255, 234, 0.15); + background-color: var(--color-bg-card); +} + +.social-icon { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-xl); + display: flex; + align-items: center; + justify-content: center; + position: relative; + transition: transform var(--transition-slow); +} + +.social-icon-inner { + width: 1.5rem; + height: 1.5rem; + color: var(--color-text-white); +} + +.social-check { + position: absolute; + top: -0.25rem; + right: -0.25rem; + width: 1.25rem; + height: 1.25rem; + background-color: var(--color-mint); + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + color: var(--color-bg-dark); + border: 2px solid var(--color-bg-dark); +} + +.social-name { + font-size: var(--text-base); + font-weight: 700; + transition: color var(--transition-normal); +} + +.social-card.selected .social-name { + color: var(--color-text-white); +} + +.social-card:not(.selected) .social-name { + color: var(--color-text-gray-300); +} + +.social-email { + margin-left: auto; + font-size: var(--text-sm); + color: var(--color-purple); +} + +/* ===================================================== + Video Preview Components + ===================================================== */ + +/* Video Container */ +.video-container { + flex: 1; + background-color: #000; + border-radius: var(--radius-2xl); + position: relative; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + min-height: 200px; +} + +.video-pattern { + position: absolute; + inset: 0; + opacity: 0.1; + background-image: + linear-gradient(45deg, var(--color-bg-dark) 25%, transparent 25%), + linear-gradient(-45deg, var(--color-bg-dark) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, var(--color-bg-dark) 75%), + linear-gradient(-45deg, transparent 75%, var(--color-bg-dark) 75%); + background-size: 40px 40px; + background-position: 0 0, 0 20px, 20px -20px, -20px 0px; +} + +.video-controls { + position: absolute; + bottom: 1rem; + left: 1rem; + right: 1rem; + z-index: 10; +} + +.video-controls-inner { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.video-play-btn { + color: var(--color-text-white); + background: none; + border: none; + cursor: pointer; + transition: color var(--transition-normal); +} + +.video-play-btn:hover { + color: var(--color-mint); +} + +.video-progress { + flex: 1; + height: 0.375rem; + background-color: rgba(255, 255, 255, 0.2); + border-radius: var(--radius-full); + overflow: hidden; +} + +.video-progress-fill { + height: 100%; + background-color: var(--color-mint); +} + +/* ===================================================== + Tags Container + ===================================================== */ + +.tags-container { + margin-top: 1rem; + background-color: rgba(18, 26, 29, 0.6); + border-radius: var(--radius-xl); + padding: 0.75rem 1rem; + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + border: 1px solid var(--color-border-white-5); + flex-shrink: 0; +} + +/* ===================================================== + Status Messages + ===================================================== */ + +/* Error Message */ +.error-message { + margin-top: 1rem; + padding: 0.75rem; + background-color: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: var(--radius-xl); + color: #f87171; + font-size: var(--text-sm); + flex-shrink: 0; +} + +/* Status Message */ +.status-message { + margin-top: 1rem; + padding: 0.75rem; + background-color: var(--color-mint-10); + border: 1px solid var(--color-mint-30); + border-radius: var(--radius-xl); + color: var(--color-mint); + font-size: var(--text-sm); + display: flex; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; +} + +/* ===================================================== + Utility Classes + ===================================================== */ + +/* Flex utilities */ +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-col { + display: flex; + flex-direction: column; +} + +.flex-1 { + flex: 1; +} + +.flex-2 { + flex: 2; +} + +.shrink-0 { + flex-shrink: 0; +} + +.min-h-0 { + min-height: 0; +} + +/* Gap utilities */ +.gap-2 { gap: 0.5rem; } +.gap-3 { gap: 0.75rem; } +.gap-4 { gap: 1rem; } +.gap-6 { gap: 1.5rem; } + +/* Margin utilities */ +.mb-1 { margin-bottom: 0.25rem; } +.mb-2 { margin-bottom: 0.5rem; } +.mb-3 { margin-bottom: 0.75rem; } +.mb-4 { margin-bottom: 1rem; } +.mb-6 { margin-bottom: 1.5rem; } +.mt-4 { margin-top: 1rem; } +.mt-6 { margin-top: 1.5rem; } + +/* Text utilities */ +.text-center { text-align: center; } +.text-right { text-align: right; } +.font-bold { font-weight: 700; } +.font-medium { font-weight: 500; } + +/* Overflow utilities */ +.overflow-hidden { overflow: hidden; } +.overflow-y-auto { overflow-y: auto; } + +/* Width utilities */ +.w-full { width: 100%; } + +/* Animation */ +@keyframes pulse { + 0%, 100% { opacity: 0.2; } + 50% { opacity: 0.4; } +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +/* Grid utilities */ +.grid-cols-3 { + display: grid; + grid-template-columns: repeat(3, 1fr); +} + +/* Space utilities */ +.space-y-4 > * + * { + margin-top: 1rem; +} + +.space-y-5 > * + * { + margin-top: 1.25rem; +} + +/* Min width */ +.min-w-280 { + min-width: 280px; +} + +/* Scrollbar utility */ +.scrollbar-hide { + scrollbar-width: none; + -ms-overflow-style: none; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +/* ===================================================== + Completion Page Components + ===================================================== */ + +/* Completion Page Container */ +.completion-container { + display: flex; + flex-direction: column; + height: 100%; + padding: var(--spacing-page); + overflow: hidden; +} + +@media (min-width: 768px) { + .completion-container { + padding: var(--spacing-page-md); + } +} + +/* Completion Content Layout */ +.completion-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 1.5rem; + max-width: 72rem; + margin-left: auto; + margin-right: auto; + width: 100%; + min-height: 0; + overflow: hidden; +} + +@media (min-width: 1024px) { + .completion-content { + flex-direction: row; + } +} + +/* Video Preview Card */ +.video-preview-card { + flex: 2; + background-color: var(--color-bg-card); + border-radius: var(--radius-3xl); + padding: var(--spacing-page); + border: 1px solid var(--color-border-white-5); + box-shadow: var(--shadow-xl); + display: flex; + flex-direction: column; + overflow: hidden; +} + +@media (min-width: 768px) { + .video-preview-card { + padding: var(--spacing-page-md); + } +} + +/* Sharing Card */ +.sharing-card { + flex: 1; + background-color: var(--color-bg-card); + border-radius: var(--radius-3xl); + padding: var(--spacing-page); + border: 1px solid var(--color-border-white-5); + box-shadow: var(--shadow-xl); + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 280px; +} + +@media (min-width: 768px) { + .sharing-card { + padding: var(--spacing-page-md); + } +} + +/* Social List */ +.social-list { + flex: 1; + min-height: 0; + overflow-y: auto; +} + +.social-list > * + * { + margin-top: 1rem; +} + +/* Deploy Button */ +.btn-deploy { + margin-top: 1.5rem; + width: 100%; + padding: 1rem; + font-weight: 700; + border-radius: var(--radius-full); + transition: all var(--transition-normal); + transform: scale(1); + font-size: var(--text-base); + box-shadow: var(--shadow-purple); + flex-shrink: 0; + background-color: var(--color-purple); + color: var(--color-text-white); + border: none; + cursor: pointer; +} + +.btn-deploy:hover { + background-color: var(--color-purple-hover); +} + +.btn-deploy:active { + transform: scale(0.95); +} + +.btn-deploy:disabled { + opacity: 1; + cursor: pointer; +} + +/* Download Button */ +.btn-download { + margin-top: 0.75rem; + width: 100%; + padding: 1rem; + background-color: transparent; + border: 1px solid var(--color-border-gray-600); + color: var(--color-text-white); + font-weight: 700; + border-radius: var(--radius-full); + transition: all var(--transition-normal); + font-size: var(--text-base); + flex-shrink: 0; + cursor: pointer; +} + +.btn-download:hover { + border-color: var(--color-text-gray-500); +} + +/* ===================================================== + Analysis Result Page Components + ===================================================== */ + +/* Analysis Container */ +.analysis-container { + width: 100%; + min-height: 100dvh; + color: var(--color-text-white); + display: flex; + flex-direction: column; + padding: var(--spacing-page); + background-color: var(--color-bg-dark); + overflow-y: auto; +} + +@media (min-width: 768px) { + .analysis-container { + padding: var(--spacing-page-md); + } +} + +@media (min-width: 1024px) { + .analysis-container { + padding: 2.5rem; + } +} + +/* Analysis Header */ +.analysis-header { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + margin-bottom: 1.5rem; + flex-shrink: 0; +} + +@media (min-width: 768px) { + .analysis-header { + margin-bottom: 2rem; + } +} + +.analysis-icon { + color: var(--color-purple); + margin-bottom: 0.75rem; +} + +/* Analysis Grid */ +.analysis-grid { + max-width: 72rem; + margin-left: auto; + margin-right: auto; + width: 100%; + display: flex; + flex-direction: column; + gap: 1.5rem; + flex: 0 0 auto; +} + +@media (min-width: 1024px) { + .analysis-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + } +} + +/* Brand Identity Card */ +.brand-identity-card { + background-color: var(--color-bg-card); + border-radius: var(--radius-3xl); + padding: var(--spacing-page); + display: flex; + flex-direction: column; + border: 1px solid var(--color-border-white-5); + box-shadow: var(--shadow-xl); +} + +@media (min-width: 768px) { + .brand-identity-card { + padding: var(--spacing-page-md); + } +} + +.brand-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1rem; +} + +.brand-name { + font-size: var(--text-3xl); + font-weight: 700; + margin-bottom: 0.5rem; +} + +@media (min-width: 768px) { + .brand-name { + font-size: var(--text-4xl); + } +} + +.brand-location { + color: var(--color-text-gray-400); + font-size: var(--text-base); + margin-bottom: 1.5rem; +} + +/* Report Section */ +.report-toggle { + font-size: var(--text-sm); + color: var(--color-text-gray-400); + background: none; + border: none; + cursor: pointer; + transition: color var(--transition-normal); +} + +.report-toggle:hover { + color: var(--color-purple); +} + +.report-content { + color: var(--color-text-gray-300); + font-size: var(--text-base); + line-height: 1.625; + overflow-y: auto; + max-height: 300px; +} + +.report-section-title { + color: var(--color-mint); + font-size: var(--text-lg); + font-weight: 600; + margin-bottom: 0.5rem; +} + +/* Image Preview */ +.image-preview-section { + margin-top: 1.5rem; + padding-top: 1.5rem; + border-top: 1px solid var(--color-border-white-10); +} + +.image-preview-title { + color: var(--color-text-gray-400); + font-size: var(--text-sm); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.75rem; + display: block; +} + +.image-preview-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.5rem; +} + +.image-preview-item { + aspect-ratio: 1; + border-radius: var(--radius-lg); + overflow: hidden; + background-color: var(--color-bg-dark); +} + +.image-preview-item img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-normal); +} + +.image-preview-item:hover img { + transform: scale(1.05); +} + +.image-preview-more { + color: var(--color-text-gray-500); + font-size: var(--text-sm); + margin-top: 0.5rem; + text-align: center; +} + +/* Right Side Cards Container */ +.analysis-cards-column { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* Feature Card (Selling Points & Keywords) */ +.feature-card { + background-color: var(--color-bg-card); + border-radius: var(--radius-3xl); + padding: var(--spacing-page); + border: 1px solid var(--color-border-white-5); + box-shadow: var(--shadow-xl); +} + +@media (min-width: 768px) { + .feature-card { + padding: var(--spacing-page-md); + } +} + +/* Tags Wrapper */ +.tags-wrapper { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +/* Feature Tag */ +.feature-tag { + padding: 0.625rem 1.25rem; + background-color: var(--color-bg-card-inner); + border-radius: var(--radius-full); + font-size: var(--text-base); + color: #e5e7eb; + border: 1px solid var(--color-border-white-10); +} + +/* Analysis Bottom Button */ +.analysis-bottom { + margin-top: 2rem; + padding-bottom: 1rem; + display: flex; + justify-content: center; + flex-shrink: 0; +} + +@media (min-width: 768px) { + .analysis-bottom { + padding-bottom: 1.5rem; + } +} + +/* ===================================================== + Landing Page Components + ===================================================== */ + +/* Hero Section */ +.hero-section { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: var(--color-bg-dark); + color: var(--color-text-white); + padding: 0 1rem; + position: relative; +} + +.hero-title { + font-family: 'Playfair Display', serif; + font-style: italic; + font-size: 3.75rem; + font-weight: 700; + margin-bottom: 1rem; + letter-spacing: -0.025em; + text-align: center; + line-height: 1; +} + +@media (min-width: 640px) { + .hero-title { + font-size: 4.5rem; + } +} + +@media (min-width: 768px) { + .hero-title { + font-size: 6rem; + } +} + +@media (min-width: 1024px) { + .hero-title { + font-size: 8rem; + } +} + +.hero-subtitle { + font-size: var(--text-sm); + font-weight: 300; + margin-bottom: 3rem; + opacity: 0.8; + text-align: center; + max-width: 32rem; +} + +@media (min-width: 640px) { + .hero-subtitle { + font-size: var(--text-base); + } +} + +@media (min-width: 768px) { + .hero-subtitle { + font-size: var(--text-xl); + } +} + +/* Hero Form */ +.hero-form { + width: 100%; + max-width: 24rem; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.hero-input { + width: 100%; + padding: 0.75rem 1.5rem; + border-radius: var(--radius-full); + background-color: var(--color-text-white); + color: #1f2937; + text-align: center; + font-weight: 500; + font-size: var(--text-sm); + border: none; +} + +@media (min-width: 640px) { + .hero-input { + padding: 1rem 1.5rem; + font-size: var(--text-base); + } +} + +.hero-input:focus { + outline: none; +} + +.hero-input::placeholder { + color: #9ca3af; +} + +.hero-input.error { + box-shadow: 0 0 0 2px #ef4444; +} + +.hero-error { + color: #f87171; + font-size: var(--text-xs); + text-align: center; +} + +.hero-button { + width: 100%; + padding: 0.75rem 1.5rem; + border-radius: var(--radius-full); + background-color: var(--color-purple); + color: var(--color-text-white); + font-weight: 700; + font-size: var(--text-sm); + box-shadow: var(--shadow-purple); + transition: all var(--transition-normal); + border: none; + cursor: pointer; +} + +@media (min-width: 640px) { + .hero-button { + padding: 1rem 1.5rem; + font-size: var(--text-base); + } +} + +.hero-button:hover { + background-color: var(--color-purple-hover); +} + +.hero-button:active { + transform: scale(0.95); +} + +/* Scroll Indicator */ +.scroll-indicator { + position: absolute; + bottom: 1.5rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + opacity: 0.4; + transition: opacity var(--transition-normal); + cursor: pointer; + animation: bounce 1s infinite; + background: none; + border: none; + color: inherit; +} + +@media (min-width: 640px) { + .scroll-indicator { + bottom: 2.5rem; + } +} + +.scroll-indicator:hover { + opacity: 1; +} + +.scroll-indicator-text { + font-size: 10px; + font-weight: 300; +} + +@media (min-width: 640px) { + .scroll-indicator-text { + font-size: var(--text-xs); + } +} + +.scroll-indicator-icon { + width: 2rem; + height: 2rem; + border: 1px solid var(--color-text-white); + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; +} + +@media (min-width: 640px) { + .scroll-indicator-icon { + width: 2.5rem; + height: 2.5rem; + } +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } +} + +/* ===================================================== + Welcome Section Components + ===================================================== */ + +/* Welcome Section */ +.welcome-section { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: var(--color-bg-dark); + color: var(--color-text-white); + overflow: hidden; +} + +.welcome-header { + margin-bottom: 1.5rem; + text-align: center; +} + +@media (min-width: 768px) { + .welcome-header { + margin-bottom: 2.5rem; + } +} + +.welcome-icon { + display: inline-block; + margin-bottom: 0.5rem; + color: var(--color-purple); +} + +.welcome-title { + font-size: var(--text-xl); + font-weight: 700; + margin-bottom: 0.5rem; +} + +@media (min-width: 640px) { + .welcome-title { + font-size: 1.5rem; + } +} + +@media (min-width: 768px) { + .welcome-title { + font-size: var(--text-3xl); + } +} + +@media (min-width: 1024px) { + .welcome-title { + font-size: var(--text-4xl); + } +} + +.welcome-subtitle { + color: var(--color-text-gray-400); + font-size: var(--text-xs); +} + +@media (min-width: 640px) { + .welcome-subtitle { + font-size: var(--text-sm); + } +} + +@media (min-width: 768px) { + .welcome-subtitle { + font-size: var(--text-base); + } +} + +/* Feature Cards Grid */ +.feature-grid { + display: grid; + grid-template-columns: 1fr; + gap: 0.75rem; + width: 100%; + max-width: 64rem; + margin-bottom: 2rem; + overflow-y: auto; + padding-right: 0.25rem; +} + +@media (min-width: 640px) { + .feature-grid { + gap: 1rem; + } +} + +@media (min-width: 768px) { + .feature-grid { + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; + overflow: visible; + } +} + +/* Feature Card */ +.feature-item { + position: relative; + background-color: var(--color-bg-card); + border-radius: var(--radius-2xl); + padding: 1rem; + display: flex; + flex-direction: row; + align-items: center; + text-align: left; + transition: all var(--transition-normal); + border: 1px solid transparent; +} + +@media (min-width: 640px) { + .feature-item { + padding: 1.5rem; + } +} + +@media (min-width: 768px) { + .feature-item { + flex-direction: column; + align-items: center; + text-align: center; + padding: 2rem; + border-radius: var(--radius-3xl); + } +} + +.feature-number { + width: 1.5rem; + height: 1.5rem; + flex-shrink: 0; + border-radius: var(--radius-full); + border: 1px solid var(--color-border-gray-600); + display: flex; + align-items: center; + justify-content: center; + margin-right: 1rem; + font-size: 10px; + font-weight: 700; + color: var(--color-text-gray-400); +} + +@media (min-width: 640px) { + .feature-number { + width: 2rem; + height: 2rem; + font-size: var(--text-xs); + } +} + +@media (min-width: 768px) { + .feature-number { + margin-right: 0; + margin-bottom: 1.5rem; + } +} + +.feature-content { + display: flex; + flex-direction: column; +} + +@media (min-width: 768px) { + .feature-content { + align-items: center; + } +} + +.feature-title { + font-size: var(--text-sm); + font-weight: 700; + margin-bottom: 0.25rem; +} + +@media (min-width: 640px) { + .feature-title { + font-size: var(--text-base); + } +} + +@media (min-width: 768px) { + .feature-title { + font-size: var(--text-lg); + margin-bottom: 1rem; + } +} + +.feature-description { + color: var(--color-text-gray-400); + font-size: 10px; + line-height: 1.25; +} + +@media (min-width: 640px) { + .feature-description { + font-size: var(--text-xs); + } +} + +@media (min-width: 768px) { + .feature-description { + font-size: var(--text-sm); + line-height: 1.625; + } +} + +/* Welcome Buttons */ +.welcome-buttons { + display: flex; + gap: 1rem; +} + +.btn-ghost { + padding: 0.75rem 2rem; + border-radius: var(--radius-full); + border: 1px solid var(--color-border-gray-700); + background-color: transparent; + color: var(--color-text-white); + font-weight: 700; + font-size: var(--text-xs); + transition: all var(--transition-normal); + cursor: pointer; + margin-bottom: 1rem; +} + +@media (min-width: 640px) { + .btn-ghost { + font-size: var(--text-sm); + } +} + +.btn-ghost:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.btn-cta { + padding: 0.75rem 2.5rem; + border-radius: var(--radius-full); + background-color: var(--color-purple); + color: var(--color-text-white); + font-weight: 700; + font-size: var(--text-xs); + box-shadow: var(--shadow-purple); + transition: all var(--transition-normal); + border: none; + cursor: pointer; + margin-bottom: 1rem; +} + +@media (min-width: 640px) { + .btn-cta { + font-size: var(--text-sm); + } +} + +@media (min-width: 768px) { + .btn-cta { + padding: 0.75rem 3.5rem; + } +} + +.btn-cta:hover { + background-color: var(--color-purple-hover); +} + +.btn-cta:active { + transform: scale(0.95); +} + +/* ===================================================== + Display Section Components + ===================================================== */ + +/* Display Section */ +.display-section { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: var(--color-bg-dark); + color: var(--color-text-white); + position: relative; + overflow: hidden; +} + +/* Video Frames Container */ +.video-frames { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 0.5rem; + margin-bottom: 2rem; + width: 100%; + max-width: 72rem; +} + +@media (min-width: 640px) { + .video-frames { + gap: 1.5rem; + } +} + +@media (min-width: 768px) { + .video-frames { + gap: 2.5rem; + margin-bottom: 4rem; + } +} + +@media (min-width: 1024px) { + .video-frames { + gap: 3.5rem; + } +} + +/* Phone Frame */ +.phone-frame { + display: flex; + width: 135px; + aspect-ratio: 9/16; + border-radius: 24px; + background-color: #000; + border: 1px solid rgba(255, 255, 255, 0.1); + overflow: hidden; + position: relative; + box-shadow: 0 30px 80px rgba(0, 0, 0, 0.9); + align-items: center; + justify-content: center; + transition: all var(--transition-normal); +} + +@media (min-width: 640px) { + .phone-frame { + width: 190px; + border-radius: 40px; + } +} + +@media (min-width: 768px) { + .phone-frame { + width: 230px; + border-radius: 48px; + } +} + +@media (min-width: 1024px) { + .phone-frame { + width: 280px; + } +} + +.phone-frame.hidden-mobile { + display: none; +} + +@media (min-width: 768px) { + .phone-frame.hidden-mobile { + display: flex; + } +} + +.phone-video-container { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; + overflow: hidden; +} + +.phone-video-container iframe { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 180%; + height: 100%; + object-fit: cover; +} + +.phone-bezel { + position: absolute; + inset: 0; + border: 6px solid #20282b; + border-radius: 24px; + pointer-events: none; + z-index: 20; +} + +@media (min-width: 640px) { + .phone-bezel { + border-width: 10px; + border-radius: 40px; + } +} + +@media (min-width: 768px) { + .phone-bezel { + border-radius: 48px; + } +} + +.phone-notch { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 25%; + height: 12px; + background-color: #20282b; + border-bottom-left-radius: 16px; + border-bottom-right-radius: 16px; + z-index: 30; + opacity: 0.8; +} + +@media (min-width: 640px) { + .phone-notch { + height: 20px; + } +} + +/* Display Button */ +.display-button { + padding: 0.75rem 3.5rem; + border-radius: var(--radius-full); + background-color: var(--color-purple); + color: var(--color-text-white); + font-weight: 700; + font-size: var(--text-xs); + letter-spacing: 0.025em; + box-shadow: 0 20px 25px -5px rgba(166, 130, 255, 0.27); + transition: all var(--transition-normal); + border: none; + cursor: pointer; + margin-bottom: 2rem; +} + +@media (min-width: 640px) { + .display-button { + padding: 0.875rem 4rem; + font-size: var(--text-base); + } +} + +.display-button:hover { + background-color: var(--color-purple-hover); +} + +.display-button:active { + transform: scale(0.95); +} + +/* ===================================================== + Login Section Components + ===================================================== */ + +/* Login Container */ +.login-container { + width: 100%; + height: 100dvh; + background-color: var(--color-bg-dark); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: var(--color-text-white); + padding: 1rem; + position: relative; + overflow: hidden; +} + +@media (min-width: 640px) { + .login-container { + padding: 1.5rem; + } +} + +/* Login Back Button */ +.login-back-btn { + position: absolute; + top: 0.75rem; + left: 0.75rem; + display: flex; + align-items: center; + gap: 0.375rem; + padding: 0.25rem 0.75rem; + border-radius: var(--radius-full); + border: 1px solid var(--color-border-gray-700); + background-color: transparent; + color: var(--color-text-gray-300); + font-size: 9px; + transition: background-color var(--transition-normal); + cursor: pointer; +} + +@media (min-width: 640px) { + .login-back-btn { + top: 1rem; + left: 1rem; + padding: 0.375rem 1rem; + font-size: 10px; + } +} + +@media (min-width: 768px) { + .login-back-btn { + font-size: var(--text-xs); + } +} + +.login-back-btn:hover { + background-color: rgba(31, 41, 55, 1); +} + +/* Login Content */ +.login-content { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + max-width: 42rem; +} + +.login-title { + font-family: 'Playfair Display', serif; + font-style: italic; + font-size: var(--text-3xl); + font-weight: 700; + margin-bottom: 0.5rem; + letter-spacing: -0.025em; + line-height: 1; + color: var(--color-text-white); +} + +@media (min-width: 640px) { + .login-title { + font-size: var(--text-4xl); + margin-bottom: 0.75rem; + } +} + +@media (min-width: 768px) { + .login-title { + font-size: 3rem; + } +} + +@media (min-width: 1024px) { + .login-title { + font-size: 3.75rem; + } +} + +.login-subtitle { + font-size: 9px; + font-weight: 300; + margin-bottom: 1.5rem; + opacity: 0.8; +} + +@media (min-width: 640px) { + .login-subtitle { + font-size: 10px; + margin-bottom: 2rem; + } +} + +@media (min-width: 768px) { + .login-subtitle { + font-size: var(--text-xs); + margin-bottom: 2.5rem; + } +} + +@media (min-width: 1024px) { + .login-subtitle { + font-size: var(--text-sm); + } +} + +/* Kakao Button */ +.btn-kakao { + width: 100%; + max-width: 200px; + padding: 0.625rem 1rem; + border-radius: var(--radius-full); + background-color: #fae100; + color: var(--color-bg-dark); + font-weight: 700; + font-size: 10px; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2); + transition: all var(--transition-normal); + border: none; + cursor: pointer; +} + +@media (min-width: 640px) { + .btn-kakao { + max-width: 240px; + padding: 0.75rem 1rem; + font-size: var(--text-xs); + } +} + +.btn-kakao:hover { + background-color: #ebd500; +} + +.btn-kakao:active { + transform: scale(0.95); +} + +/* ===================================================== + Loading Section Components + ===================================================== */ + +/* Loading Container */ +.loading-container { + width: 100%; + height: 100vh; + background-color: var(--color-bg-dark); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: var(--color-text-white); + padding: 0 1.5rem; +} + +/* Loading Spinner */ +.loading-spinner { + position: relative; + margin-bottom: 2rem; +} + +.loading-ring { + width: 4rem; + height: 4rem; + border: 4px solid rgba(166, 130, 255, 0.2); + border-top-color: var(--color-purple); + border-radius: var(--radius-full); + animation: spin 1s linear infinite; +} + +.loading-dot { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.loading-dot-inner { + width: 1rem; + height: 1rem; + background-color: var(--color-purple); + border-radius: var(--radius-full); + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + box-shadow: 0 0 15px var(--color-purple); +} + +/* Loading Text */ +.loading-text { + text-align: center; +} + +.loading-text > * + * { + margin-top: 0.5rem; +} + +.loading-title { + font-size: var(--text-xl); + font-weight: 700; + letter-spacing: -0.025em; +} + +@media (min-width: 640px) { + .loading-title { + font-size: 1.5rem; + } +} + +.loading-description { + color: var(--color-text-gray-400); + font-size: var(--text-sm); + font-weight: 300; + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@media (min-width: 640px) { + .loading-description { + font-size: var(--text-base); + } +} + +/* ===================================================== + Dashboard Content Components + ===================================================== */ + +/* Dashboard Container */ +.dashboard-container { + width: 100%; + height: 100%; + padding: 0.75rem; + display: flex; + flex-direction: column; + background-color: var(--color-bg-dark); + color: var(--color-text-white); + overflow: hidden; +} + +@media (min-width: 640px) { + .dashboard-container { + padding: 1rem; + } +} + +@media (min-width: 768px) { + .dashboard-container { + padding: 1.5rem; + } +} + +/* Dashboard Header */ +.dashboard-header { + flex-shrink: 0; + margin-bottom: 0.5rem; + margin-left: 2.5rem; +} + +@media (min-width: 640px) { + .dashboard-header { + margin-bottom: 0.75rem; + } +} + +@media (min-width: 768px) { + .dashboard-header { + margin-bottom: 1rem; + margin-left: 0; + } +} + +.dashboard-title { + font-size: var(--text-lg); + 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: 9px; + color: var(--color-text-gray-500); + margin-top: 0.125rem; +} + +@media (min-width: 640px) { + .dashboard-description { + font-size: 10px; + } +} + +/* Stats Grid */ +.stats-grid { + flex-shrink: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +@media (min-width: 640px) { + .stats-grid { + gap: 0.75rem; + margin-bottom: 0.75rem; + } +} + +@media (min-width: 768px) { + .stats-grid { + margin-bottom: 1rem; + } +} + +/* Stat Card */ +.stat-card { + background-color: var(--color-bg-card); + border-radius: var(--radius-xl); + padding: 0.5rem; + border: 1px solid var(--color-border-white-5); + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.125rem; + box-shadow: var(--shadow-xl); + transition: transform var(--transition-normal); +} + +@media (min-width: 640px) { + .stat-card { + padding: 0.75rem; + border-radius: var(--radius-2xl); + } +} + +@media (min-width: 768px) { + .stat-card { + padding: 1rem; + } +} + +.stat-card:hover { + transform: scale(1.02); +} + +.stat-label { + font-size: 8px; + 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-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 { + font-size: 8px; + color: var(--color-mint); + font-weight: 500; +} + +@media (min-width: 640px) { + .stat-trend { + font-size: 9px; + } +} + +/* Chart Card */ +.chart-card { + flex: 1; + min-height: 0; + background-color: var(--color-bg-card); + border-radius: var(--radius-xl); + padding: 0.75rem; + border: 1px solid var(--color-border-white-5); + display: flex; + flex-direction: column; + position: relative; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + overflow: hidden; +} + +@media (min-width: 640px) { + .chart-card { + padding: 1rem; + border-radius: var(--radius-2xl); + } +} + +@media (min-width: 768px) { + .chart-card { + padding: 1.5rem; + border-radius: var(--radius-3xl); + } +} + +.chart-header { + flex-shrink: 0; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +@media (min-width: 640px) { + .chart-header { + margin-bottom: 0.75rem; + } +} + +@media (min-width: 768px) { + .chart-header { + margin-bottom: 1rem; + } +} + +.chart-title { + font-size: 8px; + font-weight: 700; + color: var(--color-text-gray-400); + text-transform: uppercase; + 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; + align-items: center; +} + +.chart-legend-dot { + width: 0.375rem; + height: 0.375rem; + border-radius: var(--radius-full); + background-color: var(--color-mint); +} + +.chart-legend-text { + font-size: 8px; + color: var(--color-text-gray-400); +} + +@media (min-width: 640px) { + .chart-legend-text { + font-size: 9px; + } +} + +.chart-container { + flex: 1; + position: relative; + min-height: 0; +} + +.chart-badge { + position: absolute; + top: 45%; + left: 55%; + transform: translate(-50%, -100%); + margin-bottom: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + pointer-events: none; +} + +.chart-badge-value { + background-color: var(--color-mint); + color: var(--color-bg-dark); + padding: 0.125rem 0.5rem; + border-radius: var(--radius-md); + font-size: 9px; + font-weight: 700; + 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; + background-color: rgba(166, 255, 234, 0.5); + margin-top: 0.125rem; +} + +@media (min-width: 640px) { + .chart-badge-line { + height: 0.75rem; + } +} + +.chart-xaxis { + flex-shrink: 0; + display: flex; + justify-content: space-between; + margin-top: 0.5rem; + padding: 0 0.5rem; + font-size: 8px; + color: var(--color-text-gray-500); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + border-top: 1px solid var(--color-border-white-5); + padding-top: 0.5rem; +} + +@media (min-width: 640px) { + .chart-xaxis { + margin-top: 0.75rem; + padding: 0 1rem; + font-size: 9px; + padding-top: 0.75rem; + } +} + +@media (min-width: 768px) { + .chart-xaxis { + margin-top: 1rem; + padding: 0 1.5rem; + } +} + +/* ===================================================== + Business Settings Components + ===================================================== */ + +/* Settings Container */ +.settings-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 0.75rem; + background-color: var(--color-bg-dark); + color: var(--color-text-white); + overflow: hidden; +} + +@media (min-width: 640px) { + .settings-container { + padding: 1rem; + } +} + +@media (min-width: 768px) { + .settings-container { + padding: 1.5rem; + } +} + +/* Settings Header */ +.settings-header { + text-align: center; + margin-bottom: 1rem; + max-width: 42rem; + margin-left: 2.5rem; +} + +@media (min-width: 640px) { + .settings-header { + margin-bottom: 1.5rem; + } +} + +@media (min-width: 768px) { + .settings-header { + margin-bottom: 2rem; + margin-left: 0; + } +} + +.settings-title { + font-size: var(--text-lg); + font-weight: 700; + margin-bottom: 0.25rem; +} + +@media (min-width: 640px) { + .settings-title { + font-size: var(--text-xl); + margin-bottom: 0.5rem; + } +} + +@media (min-width: 768px) { + .settings-title { + font-size: 1.5rem; + } +} + +@media (min-width: 1024px) { + .settings-title { + font-size: var(--text-3xl); + } +} + +.settings-description { + color: var(--color-text-gray-400); + font-size: 9px; + font-weight: 300; + line-height: 1.625; + opacity: 0.8; +} + +@media (min-width: 640px) { + .settings-description { + font-size: 10px; + } +} + +@media (min-width: 768px) { + .settings-description { + font-size: var(--text-xs); + } +} + +/* Settings Card */ +.settings-card { + width: 100%; + max-width: 36rem; +} + +.settings-card-inner { + background-color: var(--color-bg-card); + border-radius: var(--radius-xl); + padding: 0.75rem; + border: 1px solid var(--color-border-white-5); + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); +} + +@media (min-width: 640px) { + .settings-card-inner { + padding: 1rem; + border-radius: var(--radius-2xl); + } +} + +@media (min-width: 768px) { + .settings-card-inner { + padding: 1.5rem; + border-radius: var(--radius-3xl); + } +} + +.settings-card-title { + color: var(--color-mint); + font-size: 9px; + font-weight: 700; + margin-bottom: 0.5rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +@media (min-width: 640px) { + .settings-card-title { + font-size: 10px; + margin-bottom: 0.75rem; + } +} + +@media (min-width: 768px) { + .settings-card-title { + font-size: var(--text-xs); + margin-bottom: 1rem; + } +} + +/* Social Items */ +.social-items { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +@media (min-width: 640px) { + .social-items { + gap: 0.75rem; + } +} + +.social-item { + background-color: var(--color-bg-dark); + padding: 0.5rem; + border-radius: var(--radius-lg); + border: 1px solid var(--color-border-white-5); + display: flex; + align-items: center; + justify-content: space-between; + transition: border-color var(--transition-normal); +} + +@media (min-width: 640px) { + .social-item { + padding: 0.75rem; + border-radius: var(--radius-xl); + } +} + +.social-item:hover { + border-color: var(--color-border-white-10); +} + +.social-item-left { + display: flex; + align-items: center; + gap: 0.5rem; +} + +@media (min-width: 640px) { + .social-item-left { + gap: 0.75rem; + } +} + +.social-item-icon { + width: 1.5rem; + height: 1.5rem; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; +} + +@media (min-width: 640px) { + .social-item-icon { + width: 2rem; + height: 2rem; + } +} + +.social-item-icon-inner { + width: 0.75rem; + height: 0.75rem; +} + +@media (min-width: 640px) { + .social-item-icon-inner { + width: 1rem; + height: 1rem; + } +} + +.social-item-name { + font-size: 10px; + font-weight: 700; + color: var(--color-text-gray-300); +} + +@media (min-width: 640px) { + .social-item-name { + font-size: var(--text-xs); + } +} + +.social-item-connect { + color: var(--color-mint); + font-size: 9px; + font-weight: 700; + background: none; + border: none; + cursor: pointer; +} + +@media (min-width: 640px) { + .social-item-connect { + font-size: 10px; + } +} + +.social-item-connect:hover { + text-decoration: underline; +} + +/* ===================================================== + Header Component + ===================================================== */ + +/* Landing Header */ +.landing-header { + width: 100%; + padding: 1rem 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + position: absolute; + top: 0; + left: 0; + z-index: 20; +} + +@media (min-width: 640px) { + .landing-header { + padding: 1.5rem 2rem; + } +} + +.header-logo { + font-family: 'Playfair Display', serif; + font-style: italic; + font-size: var(--text-xl); + font-weight: 700; + letter-spacing: -0.025em; + color: var(--color-text-white); +} + +@media (min-width: 640px) { + .header-logo { + font-size: 1.5rem; + } +} + +.header-avatar { + width: 2rem; + height: 2rem; + border-radius: var(--radius-full); + overflow: hidden; + border: 1px solid var(--color-border-gray-700); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +@media (min-width: 640px) { + .header-avatar { + width: 2.5rem; + height: 2.5rem; + } +} + +.header-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* ===================================================== + Content Safe Area (for Landing Pages) + ===================================================== */ +.content-safe-area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 0 1rem; +} diff --git a/src/App.tsx b/src/App.tsx index 6c6d534..80ffe06 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import HeroSection from './pages/Landing/HeroSection'; import WelcomeSection from './pages/Landing/WelcomeSection'; import DisplaySection from './pages/Landing/DisplaySection'; @@ -12,13 +12,30 @@ import { CrawlingResponse } from './types/api'; type ViewMode = 'landing' | 'loading' | 'analysis' | 'login' | 'generation_flow'; +const VIEW_MODE_KEY = 'castad_view_mode'; +const ANALYSIS_DATA_KEY = 'castad_analysis_data'; + const App: React.FC = () => { const containerRef = useRef(null); - const [viewMode, setViewMode] = useState('landing'); + + // localStorage에서 저장된 상태 복원 + const savedViewMode = localStorage.getItem(VIEW_MODE_KEY) as ViewMode | null; + const savedAnalysisData = localStorage.getItem(ANALYSIS_DATA_KEY); + + const [viewMode, setViewMode] = useState( + savedViewMode === 'generation_flow' ? 'generation_flow' : 'landing' + ); const [initialTab, setInitialTab] = useState('새 프로젝트 만들기'); - const [analysisData, setAnalysisData] = useState(null); + const [analysisData, setAnalysisData] = useState( + savedAnalysisData ? JSON.parse(savedAnalysisData) : null + ); const [error, setError] = useState(null); + // viewMode 변경 시 localStorage에 저장 + useEffect(() => { + localStorage.setItem(VIEW_MODE_KEY, viewMode); + }, [viewMode]); + const scrollToSection = (index: number) => { if (containerRef.current) { const h = containerRef.current.clientHeight; @@ -38,6 +55,7 @@ const App: React.FC = () => { try { const data = await crawlUrl(url); setAnalysisData(data); + localStorage.setItem(ANALYSIS_DATA_KEY, JSON.stringify(data)); setViewMode('analysis'); } catch (err) { console.error('Crawling failed:', err); @@ -56,6 +74,11 @@ const App: React.FC = () => { }; const handleGoBack = () => { + // localStorage 정리 + localStorage.removeItem(VIEW_MODE_KEY); + localStorage.removeItem(ANALYSIS_DATA_KEY); + localStorage.removeItem('castad_wizard_step'); + localStorage.removeItem('castad_active_item'); setViewMode('landing'); }; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index c3749e6..aad3b9f 100755 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -3,15 +3,12 @@ import React from 'react'; const Header: React.FC = () => { return ( -
-
- CASTAD -
-
- User Profile +
CASTAD
+
+ User Profile
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 175cbd3..c670356 100755 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -10,20 +10,16 @@ interface SidebarItemProps { } const SidebarItem: React.FC = ({ icon, label, isActive, isCollapsed, onClick }) => { - const baseClasses = "flex items-center gap-3 px-3 py-2.5 sm:py-3 rounded-xl transition-all cursor-pointer group mb-1 relative overflow-hidden"; - const activeClasses = "bg-[#a6ffea] text-[#121a1d]"; - const inactiveClasses = "text-gray-400 hover:bg-white/5 hover:text-white"; - return (
-
+
{icon}
- {!isCollapsed && {label}} + {!isCollapsed && {label}}
); }; @@ -31,13 +27,13 @@ const SidebarItem: React.FC = ({ icon, label, isActive, isColl interface SidebarProps { activeItem: string; onNavigate: (id: string) => void; + onHome?: () => void; } -const Sidebar: React.FC = ({ activeItem, onNavigate }) => { +const Sidebar: React.FC = ({ activeItem, onNavigate, onHome }) => { const [isCollapsed, setIsCollapsed] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false); - // 모바일에서는 기본적으로 사이드바 숨기기 useEffect(() => { const handleResize = () => { if (window.innerWidth < 768) { @@ -50,7 +46,6 @@ const Sidebar: React.FC = ({ activeItem, onNavigate }) => { return () => window.removeEventListener('resize', handleResize); }, []); - // 네비게이션 시 모바일에서 사이드바 닫기 const handleNavigate = (id: string) => { onNavigate(id); if (window.innerWidth < 768) { @@ -59,43 +54,43 @@ const Sidebar: React.FC = ({ activeItem, onNavigate }) => { }; const menuItems = [ + { id: '대시보드', label: '대시보드', icon: }, { id: '새 프로젝트 만들기', label: '새 프로젝트 만들기', icon: }, { id: '내 보관함', label: '내 보관함', icon: }, { id: '에셋 관리', label: '에셋 관리', icon: }, { id: '내 펜션', label: '내 펜션', icon: }, { id: '계정 설정', label: '계정 설정', icon: }, - { id: '대시보드', label: '대시보드', icon: }, { id: '비즈니스 설정', label: '비즈니스 설정', icon: }, ]; return ( <> - {/* 모바일 햄버거 버튼 */} + {/* Mobile Menu Button */} - {/* 모바일 오버레이 */} + {/* Mobile Overlay */} {isMobileOpen && (
setIsMobileOpen(false)} /> )} - {/* 사이드바 */} -
-
- {!isCollapsed && CASTAD} + {/* Sidebar */} +
+
+ {!isCollapsed && ( + + CASTAD + + )}
-
+
{menuItems.map(item => ( = ({ activeItem, onNavigate }) => { ))}
-
+
{!isCollapsed && ( -
-
- Credit - 850 / 1000 +
+
+ Credit + 850 / 1000
-
-
+
+
-
)} -
+
Profile {!isCollapsed && (
-

username1234

+

username1234

)}
-
diff --git a/src/pages/Analysis/AnalysisResultSection.tsx b/src/pages/Analysis/AnalysisResultSection.tsx index e8210ea..f3bee1a 100755 --- a/src/pages/Analysis/AnalysisResultSection.tsx +++ b/src/pages/Analysis/AnalysisResultSection.tsx @@ -1,5 +1,5 @@ -import React from 'react'; +import React, { useState } from 'react'; import { CrawlingResponse } from '../../types/api'; interface AnalysisResultSectionProps { @@ -8,119 +8,153 @@ interface AnalysisResultSectionProps { data: CrawlingResponse; } +// 마크다운 report를 섹션별로 파싱 +const parseReport = (report: string) => { + const sections: { title: string; content: string }[] = []; + const lines = report.split('\n'); + let currentTitle = ''; + let currentContent: string[] = []; + + lines.forEach((line) => { + if (line.startsWith('## ')) { + if (currentTitle || currentContent.length > 0) { + sections.push({ title: currentTitle, content: currentContent.join('\n').trim() }); + } + currentTitle = line.replace('## ', '').trim(); + currentContent = []; + } else if (!line.startsWith('# ')) { + currentContent.push(line); + } + }); + + if (currentTitle || currentContent.length > 0) { + sections.push({ title: currentTitle, content: currentContent.join('\n').trim() }); + } + + return sections.filter(s => s.title && s.content && !s.title.includes('JSON')); +}; + const AnalysisResultSection: React.FC = ({ onBack, onGenerate, data }) => { const { processed_info, marketing_analysis, image_list } = data; const tags = marketing_analysis.tags || []; const facilities = marketing_analysis.facilities || []; + const [showFullReport, setShowFullReport] = useState(false); + const reportSections = parseReport(marketing_analysis.report); return ( -
- {/* 뒤로가기 버튼 */} -
-
- {/* 헤더 - 높이가 작을 때 숨김 */} -
-
- + {/* Header */} +
+
+
-

브랜드 분석

-

+

브랜드 분석

+

쉽고 빠르게, 브랜드 소셜 미디어 캠페인을 만드세요.

- {/* 메인 콘텐츠 그리드 */} -
- {/* 브랜드 정체성 */} -
- 브랜드 정체성 -

{processed_info.customer_name}

-

{processed_info.region} · {processed_info.detail_region_info}

+ {/* Main Content Grid */} +
+ {/* Brand Identity */} +
+
+ 브랜드 정체성 + AI 마케팅 분석 요약 +
- {/* 이미지 미리보기 */} +

{processed_info.customer_name}

+

{processed_info.region} · {processed_info.detail_region_info}

+ + {/* Marketing Analysis Summary */} +
+
+ +
+
+ {showFullReport ? ( +
+ {reportSections.map((section, idx) => ( +
+

{section.title}

+

{section.content}

+
+ ))} +
+ ) : ( +

+ {reportSections[0]?.content.slice(0, 150)}... +

+ )} +
+
+ + {/* Image Preview */} {image_list.length > 0 && ( -
- 수집된 이미지 ({image_list.length}장) -
+
+ 수집된 이미지 ({image_list.length}장) +
{image_list.slice(0, 8).map((img, idx) => ( -
- {`이미지 +
+ {`이미지
))}
{image_list.length > 8 && ( -

+{image_list.length - 8}장 더 있음

+

+{image_list.length - 8}장 더 있음

)}
)}
- {/* 오른쪽 카드들 */} -
- {/* 시설 정보 */} -
- 주요 시설 -
- {facilities.slice(0, 6).map((facility, idx) => ( - + {/* Right Cards */} +
+ {/* Main Selling Points (Facilities) */} +
+ 주요 셀링 포인트 +
+ {facilities.map((facility, idx) => ( + {facility} ))} - {facilities.length > 6 && ( - - +{facilities.length - 6} - - )}
- {/* 추천 태그 */} -
- 추천 마케팅 태그 -
- {tags.slice(0, 6).map((tag, idx) => ( - + {/* Recommended Target Keywords (Tags) */} +
+ 추천 타겟 키워드 +
+ {tags.map((tag, idx) => ( + {tag} ))} - {tags.length > 6 && ( - - +{tags.length - 6} - - )} -
-
- - {/* 마케팅 분석 요약 */} -
- 마케팅 분석 -
- {marketing_analysis.report.split('\n').slice(0, 3).map((line, idx) => ( -

{line.replace(/^#+\s*/, '')}

- ))}
- {/* 하단 버튼 */} -
-
diff --git a/src/pages/Analysis/LoadingSection.tsx b/src/pages/Analysis/LoadingSection.tsx index 9828f66..016d9e2 100755 --- a/src/pages/Analysis/LoadingSection.tsx +++ b/src/pages/Analysis/LoadingSection.tsx @@ -3,19 +3,19 @@ import React from 'react'; const LoadingSection: React.FC = () => { return ( -
-
+
+
{/* Spinning Outer Ring */} -
+
{/* Pulsing center icon */} -
-
+
+
- -
-

비즈니스 분석 중

-

+ +

+

비즈니스 분석 중

+

AI가 입력하신 정보를 바탕으로 마케팅 핵심 가치를 추출하고 있습니다...

diff --git a/src/pages/Dashboard/AssetManagementContent.tsx b/src/pages/Dashboard/AssetManagementContent.tsx index c1a4119..18c9b2e 100755 --- a/src/pages/Dashboard/AssetManagementContent.tsx +++ b/src/pages/Dashboard/AssetManagementContent.tsx @@ -20,7 +20,6 @@ const AssetManagementContent: React.FC = ({ const fileInputRef = useRef(null); const imageListRef = useRef(null); - // 이미지 아이템의 표시용 URL 가져오기 const getImageSrc = (item: ImageItem): string => { return item.type === 'url' ? item.url : item.preview; }; @@ -37,7 +36,6 @@ const AssetManagementContent: React.FC = ({ const atTop = scrollTop <= 0; const atBottom = scrollTop + clientHeight >= scrollHeight - 1; - // 스크롤 가능한 영역 내에서 스크롤 중이면 이벤트 전파 중지 if ((e.deltaY < 0 && !atTop) || (e.deltaY > 0 && !atBottom)) { e.stopPropagation(); } @@ -69,38 +67,37 @@ const AssetManagementContent: React.FC = ({ const files = e.target.files; if (files && files.length > 0) { onAddImages(Array.from(files)); - // input 초기화 (같은 파일 다시 선택 가능하도록) e.target.value = ''; } }; return ( -
-
-
-
-

브랜드 에셋

-

+ {/* Header */} +

+

브랜드 에셋

+

쉽고 빠르게, 브랜드 소셜 미디어 캠페인을 만드세요.

-
- {/* 선택된 이미지 */} -
-
-

선택된 이미지

- {imageList.length}장 + {/* Main Content */} +
+ {/* Selected Images Card */} +
+
+

선택된 이미지

+ {imageList.length}장
= ({ style={{ overscrollBehavior: 'contain' }} > {imageList.length > 0 ? ( -
+
{imageList.map((item, i) => ( -
+
{`이미지 - {/* 업로드된 파일 표시 배지 */} {item.type === 'file' && ( -
- NEW -
+
NEW
)}
) : ( -
+
이미지가 없습니다
)}
- {/* 이미지 업로드 */} -
-

이미지 업로드

+ {/* Upload Card */} +
+

이미지 업로드

-
- +
+
-

+

이미지를 드래그하여
업로드

-

+

또는 클릭하여 파일 선택

@@ -180,15 +170,12 @@ const AssetManagementContent: React.FC = ({
-
+ {/* Bottom Button */} +
diff --git a/src/pages/Dashboard/BusinessSettingsContent.tsx b/src/pages/Dashboard/BusinessSettingsContent.tsx index f01cdd0..d172ec3 100755 --- a/src/pages/Dashboard/BusinessSettingsContent.tsx +++ b/src/pages/Dashboard/BusinessSettingsContent.tsx @@ -2,32 +2,32 @@ import React from 'react'; const SocialItem: React.FC<{ platform: string; icon: React.ReactNode; color: string }> = ({ platform, icon, color }) => ( -
-
-
-
{icon}
+
+
+
+
{icon}
- {platform} + {platform}
- +
); const BusinessSettingsContent: React.FC = () => { return ( -
-
-

비즈니스 설정

-

+

+
+

비즈니스 설정

+

펜션 정보와 YouTube 채널을 설정하여 자동 업로드를 활성화하세요

-
-
-

공유

+
+
+

공유

-
+
= ({ onBack }) => { const [selectedSocials, setSelectedSocials] = useState([]); const toggleSocial = (id: string) => { - setSelectedSocials(prev => - prev.includes(id) - ? prev.filter(s => s !== id) + setSelectedSocials(prev => + prev.includes(id) + ? prev.filter(s => s !== id) : [...prev, id] ); }; const socials = [ - { id: 'Youtube', color: '#ff0000', icon: }, - { id: 'Instagram', color: '#e4405f', icon: }, - { id: 'Facebook', color: '#1877f2', icon: } + { id: 'Youtube', email: 'o2ocorp@o2o.kr', color: '#ff0000', icon: }, + { id: 'Instagram', email: 'o2ocorp@o2o.kr', color: '#e4405f', icon: }, ]; return ( -
-
-
-
-

콘텐츠 제작 완료

-

+ {/* Header */} +

+

콘텐츠 제작 완료

+

인스타그램 릴스 및 틱톡에 최적화된 고성능 영상을 제작했습니다.

-
+ {/* Main Content */} +
{/* Left: Video Preview */} -
-

이미지 및 영상

+
+

이미지 및 영상

-
-
+
+
-
-
-
-
-
- -
-
+ {/* Video Player Controls */} +
+
+ +
+
+
+
+
-
- {['AI 최적화', '색상 보정', '자막', '비트 싱크', 'SEO'].map(tag => ( - - + {/* Tags */} +
+ {['AI 최적화', '색상 보정', '다이나믹 자막', '비트 싱크', 'SEO 메타 태그'].map(tag => ( + {tag} ))} @@ -74,33 +74,33 @@ const CompletionContent: React.FC = ({ onBack }) => {
{/* Right: Sharing */} -
-

공유 채널 선택

+
+

공유

-
+
{socials.map(social => { const isSelected = selectedSocials.includes(social.id); return (
toggleSocial(social.id)} - className={`flex items-center gap-2 sm:gap-3 bg-[#121a1d] p-2 sm:p-2.5 rounded-lg sm:rounded-xl border transition-all cursor-pointer ${ - isSelected - ? 'border-[#a6ffea] shadow-[0_0_15px_rgba(166,255,234,0.15)] bg-[#1c2a2e]' - : 'border-white/5 hover:border-white/10 grayscale hover:grayscale-0 opacity-60 hover:opacity-100' - }`} + className={`social-card ${isSelected ? 'selected' : ''}`} > -
-
{social.icon}
+
+
{social.icon}
{isSelected && ( -
- +
+
)}
- + {social.id} + {social.email}
); })} @@ -108,22 +108,16 @@ const CompletionContent: React.FC = ({ onBack }) => { + +
- -
- -
); }; diff --git a/src/pages/Dashboard/DashboardContent.tsx b/src/pages/Dashboard/DashboardContent.tsx index a3bc755..ff1cd77 100755 --- a/src/pages/Dashboard/DashboardContent.tsx +++ b/src/pages/Dashboard/DashboardContent.tsx @@ -2,40 +2,40 @@ import React from 'react'; const StatCard: React.FC<{ label: string; value: string; trend: string }> = ({ label, value, trend }) => ( -
- {label} -

{value}

- {trend} +
+ {label} +

{value}

+ {trend}
); const DashboardContent: React.FC = () => { return ( -
-
-

대시보드

-

실시간 마케팅 퍼포먼스를 확인하세요.

+
+
+

대시보드

+

실시간 마케팅 퍼포먼스를 확인하세요.

- {/* Top Stats Grid - Fixed height based on content */} -
+ {/* Top Stats Grid */} +
- {/* Engagement Chart Section - Flex-1 to fill remaining space */} -
-
-

Engagement Overview

-
- - Weekly Active + {/* Engagement Chart Section */} +
+
+

Engagement Overview

+
+ + Weekly Active
-
- {/* Chart SVG - Responsive viewBox and preserveAspectRatio */} +
+ {/* Chart SVG */} { fill="url(#chartGradient)" /> - {/* Pulsing Dot at X=550, Y=180 */} + {/* Pulsing Dot */} - + - {/* Floating Data Badge - Positioning relative to SVG percentage */} -
-
- 1,234 -
-
+ {/* Floating Data Badge */} +
+
1,234
+
- {/* X Axis Labels - Fixed bottom */} -
+ {/* X Axis Labels */} +
Mon Tue Wed diff --git a/src/pages/Dashboard/GenerationFlow.tsx b/src/pages/Dashboard/GenerationFlow.tsx index 86f9f5b..0b49053 100755 --- a/src/pages/Dashboard/GenerationFlow.tsx +++ b/src/pages/Dashboard/GenerationFlow.tsx @@ -8,6 +8,9 @@ import DashboardContent from './DashboardContent'; import BusinessSettingsContent from './BusinessSettingsContent'; import { ImageItem } from '../../types/api'; +const WIZARD_STEP_KEY = 'castad_wizard_step'; +const ACTIVE_ITEM_KEY = 'castad_active_item'; + interface BusinessInfo { customer_name: string; region: string; @@ -23,8 +26,13 @@ interface GenerationFlowProps { const GenerationFlow: React.FC = ({ onHome, initialActiveItem = '대시보드', initialImageList = [], businessInfo }) => { const scrollContainerRef = useRef(null); - const [activeItem, setActiveItem] = useState(initialActiveItem); - const [maxWizardIndex, setMaxWizardIndex] = useState(0); + + // localStorage에서 저장된 상태 복원 + const savedActiveItem = localStorage.getItem(ACTIVE_ITEM_KEY); + const savedWizardStep = localStorage.getItem(WIZARD_STEP_KEY); + + const [activeItem, setActiveItem] = useState(savedActiveItem || initialActiveItem); + const [maxWizardIndex, setMaxWizardIndex] = useState(savedWizardStep ? parseInt(savedWizardStep, 10) : 0); // URL 이미지를 ImageItem 형태로 변환하여 초기화 const [imageList, setImageList] = useState( @@ -56,11 +64,33 @@ const GenerationFlow: React.FC = ({ onHome, initialActiveIt const sections = scrollContainerRef.current.querySelectorAll('.flow-section'); if (sections[index]) { setMaxWizardIndex(prev => Math.max(prev, index)); + localStorage.setItem(WIZARD_STEP_KEY, index.toString()); sections[index].scrollIntoView({ behavior: 'smooth', block: 'start' }); } } }; + // activeItem 변경 시 localStorage에 저장 + useEffect(() => { + localStorage.setItem(ACTIVE_ITEM_KEY, activeItem); + }, [activeItem]); + + // 새로고침 시 저장된 위저드 단계로 스크롤 + useEffect(() => { + if (activeItem === '새 프로젝트 만들기' && savedWizardStep) { + const stepIndex = parseInt(savedWizardStep, 10); + // 약간의 딜레이 후 스크롤 (DOM이 준비될 때까지) + setTimeout(() => { + if (scrollContainerRef.current) { + const sections = scrollContainerRef.current.querySelectorAll('.flow-section'); + if (sections[stepIndex]) { + sections[stepIndex].scrollIntoView({ behavior: 'auto', block: 'start' }); + } + } + }, 100); + } + }, []); + useEffect(() => { const container = scrollContainerRef.current; if (!container || activeItem !== '새 프로젝트 만들기') return; @@ -150,7 +180,7 @@ const GenerationFlow: React.FC = ({ onHome, initialActiveIt return (
- +
{renderContent()}
diff --git a/src/pages/Dashboard/SoundStudioContent.tsx b/src/pages/Dashboard/SoundStudioContent.tsx index e3d8edc..6d8f395 100755 --- a/src/pages/Dashboard/SoundStudioContent.tsx +++ b/src/pages/Dashboard/SoundStudioContent.tsx @@ -17,7 +17,6 @@ interface SoundStudioContentProps { type GenerationStatus = 'idle' | 'generating_lyric' | 'generating_song' | 'polling' | 'complete' | 'error'; -// localStorage에 저장할 데이터 구조 interface SavedGenerationState { taskId: string; lyrics: string; @@ -26,8 +25,8 @@ interface SavedGenerationState { } const STORAGE_KEY = 'castad_song_generation'; -const STORAGE_EXPIRY = 30 * 60 * 1000; // 30분 -const MAX_RETRY_COUNT = 3; // 최대 재시도 횟수 +const STORAGE_EXPIRY = 30 * 60 * 1000; +const MAX_RETRY_COUNT = 3; const SoundStudioContent: React.FC = ({ onBack, onNext, businessInfo }) => { const [selectedType, setSelectedType] = useState('보컬'); @@ -49,7 +48,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, const progressBarRef = useRef(null); const audioRef = useRef(null); - // localStorage에 상태 저장 const saveToStorage = (taskId: string, currentLyrics: string, currentStatus: GenerationStatus) => { const data: SavedGenerationState = { taskId, @@ -60,12 +58,10 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); }; - // localStorage에서 상태 제거 const clearStorage = () => { localStorage.removeItem(STORAGE_KEY); }; - // localStorage에서 상태 복구 const loadFromStorage = (): SavedGenerationState | null => { try { const saved = localStorage.getItem(STORAGE_KEY); @@ -73,7 +69,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, const data: SavedGenerationState = JSON.parse(saved); - // 만료된 데이터인지 확인 (30분) if (Date.now() - data.timestamp > STORAGE_EXPIRY) { clearStorage(); return null; @@ -86,26 +81,19 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, } }; - // 컴포넌트 마운트 시 저장된 상태 복구 useEffect(() => { const savedState = loadFromStorage(); if (savedState && (savedState.status === 'polling' || savedState.status === 'generating_song')) { - // 저장된 가사가 있으면 표시 if (savedState.lyrics) { setLyrics(savedState.lyrics); setShowLyrics(true); } - - // 폴링 상태 복구 setStatus('polling'); setStatusMessage('음악을 처리하고 있습니다... (새로고침 후 복구됨)'); - - // 폴링 재개 (저장된 가사와 함께) resumePolling(savedState.taskId, savedState.lyrics, 0); } }, []); - // 폴링 재개 함수 (타임아웃 시 재생성) const resumePolling = async (taskId: string, currentLyrics: string, currentRetryCount: number = 0) => { try { const downloadResponse = await waitForSongComplete( @@ -132,14 +120,11 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, } catch (error) { console.error('Polling failed:', error); - // 타임아웃인 경우 재생성 시도 if (error instanceof Error && error.message === 'TIMEOUT') { if (currentRetryCount < MAX_RETRY_COUNT) { const newRetryCount = currentRetryCount + 1; setRetryCount(newRetryCount); setStatusMessage(`시간 초과로 재생성 중... (${newRetryCount}/${MAX_RETRY_COUNT})`); - - // 새로운 노래 생성 요청 await regenerateSongOnly(currentLyrics, newRetryCount); } else { setStatus('error'); @@ -156,7 +141,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, } }; - // 노래만 재생성 (가사는 유지) const regenerateSongOnly = async (currentLyrics: string, currentRetryCount: number) => { if (!businessInfo) return; @@ -179,10 +163,7 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, throw new Error(songResponse.error_message || '음악 생성 요청에 실패했습니다.'); } - // 새 task_id로 저장 saveToStorage(songResponse.task_id, currentLyrics, 'polling'); - - // 폴링 재개 await resumePolling(songResponse.task_id, currentLyrics, currentRetryCount); } catch (error) { @@ -288,7 +269,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, setStatusMessage('가사를 생성하고 있습니다...'); try { - // Step 1: Generate lyrics const language = LANGUAGE_MAP[selectedLang] || 'Korean'; let lyricResponse = await generateLyric({ customer_name: businessInfo.customer_name, @@ -297,7 +277,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, region: businessInfo.region, }); - // Retry if the response contains error message let retryCount = 0; while (lyricResponse.lyric.includes("I'm sorry") && retryCount < 3) { retryCount++; @@ -314,7 +293,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, throw new Error(lyricResponse.error_message || '가사 생성에 실패했습니다.'); } - // 3번 재시도 후에도 여전히 에러 메시지가 포함되어 있으면 가사 카드를 표시하지 않고 에러 처리 if (lyricResponse.lyric.includes("I'm sorry")) { throw new Error('가사 생성에 실패했습니다. 다시 시도해주세요.'); } @@ -322,7 +300,6 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, setLyrics(lyricResponse.lyric); setShowLyrics(true); - // Step 2: Generate song setStatus('generating_song'); setStatusMessage('음악을 생성하고 있습니다...'); @@ -343,12 +320,10 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, throw new Error(songResponse.error_message || '음악 생성 요청에 실패했습니다.'); } - // Step 3: Poll for completion - 상태 저장 setStatus('polling'); setStatusMessage('음악을 처리하고 있습니다...'); saveToStorage(songResponse.task_id, lyricResponse.lyric, 'polling'); - // 폴링 시작 (타임아웃 시 자동 재생성) await resumePolling(songResponse.task_id, lyricResponse.lyric, 0); } catch (error) { @@ -376,53 +351,50 @@ const SoundStudioContent: React.FC = ({ onBack, onNext, const isGenerating = status === 'generating_lyric' || status === 'generating_song' || status === 'polling'; return ( -
- {/* Hidden audio element */} +
{audioUrl && (