0113 수정 사항 반영

main
hbyang 2026-01-13 17:51:48 +09:00
parent 57377c7d1f
commit 1351370ce8
3 changed files with 104 additions and 84 deletions

135
index.css
View File

@ -630,11 +630,17 @@
color: var(--color-text-gray-400);
}
.sidebar-item:not(.active):hover {
.sidebar-item:not(.active):not(.disabled):hover {
background-color: rgba(255, 255, 255, 0.05);
color: var(--color-text-white);
}
.sidebar-item.disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.sidebar-item-icon {
flex-shrink: 0;
display: flex;
@ -1436,9 +1442,11 @@
width: calc(100% - 4rem);
display: flex;
flex-direction: column;
gap: 1rem;
gap: 1.5rem;
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
@media (min-width: 1024px) {
@ -1446,6 +1454,7 @@
flex-direction: row;
gap: 2rem;
max-width: 1400px;
overflow: hidden;
}
}
@ -1458,6 +1467,7 @@
@media (max-width: 480px) {
.completion-container {
width: calc(100% - 2rem);
gap: 1.25rem;
}
}
@ -1474,8 +1484,9 @@
}
.completion-column-left {
flex: 1;
width: 100%;
min-width: 0;
flex-shrink: 0;
}
.completion-column-right {
@ -1484,6 +1495,11 @@
}
@media (min-width: 1024px) {
.completion-column-left {
flex: 1;
width: auto;
}
.completion-column-right {
width: 400px;
}
@ -1507,6 +1523,7 @@
display: flex;
flex-direction: column;
gap: 1.25rem;
flex-shrink: 0;
}
@media (min-width: 768px) {
@ -1517,6 +1534,12 @@
}
@media (min-width: 1024px) {
.video-preview-card {
flex-shrink: 1;
flex: 1;
min-height: 0;
}
.video-preview-card,
.sharing-card {
padding: 2.5rem;
@ -1525,12 +1548,17 @@
/* Completion Video Wrapper */
.completion-video-wrapper {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
@media (min-width: 1024px) {
.completion-video-wrapper {
flex: 1;
min-height: 0;
}
}
/* Video Container for Completion - always 16:9 aspect ratio (horizontal letterbox) */
.video-preview-card .video-container {
position: relative;
@ -4412,29 +4440,30 @@
/* Sound Studio Columns */
.sound-studio-columns {
display: flex;
gap: 2.5rem;
flex-direction: column;
gap: 1.5rem;
flex: 1;
min-height: 0;
overflow: hidden;
overflow-y: auto;
overflow-x: hidden;
}
/* Sound Column */
.sound-column {
flex: 1;
width: 100%;
display: flex;
flex-direction: column;
gap: 1.25rem;
min-height: 0;
overflow-y: auto;
flex-shrink: 0;
}
/* Lyrics Column */
.lyrics-column {
flex: 1;
width: 100%;
display: flex;
flex-direction: column;
gap: 1.25rem;
min-height: 0;
flex-shrink: 0;
}
/* Column Title */
@ -4822,10 +4851,10 @@
/* Video Generate Button */
.btn-video-generate {
padding: 0.625rem 2.5rem;
background-color: #462E64;
background-color: #AE72F9;
border: none;
border-radius: var(--radius-full);
color: #8B5BC7;
color: #FFFFFF;
font-size: var(--text-sm);
font-weight: 600;
cursor: pointer;
@ -4840,11 +4869,13 @@
}
.btn-video-generate:hover:not(.disabled):not(.generating) {
background-color: #694596;
background-color: #9B5DE5;
}
.btn-video-generate.disabled {
opacity: 0.5;
background-color: #462E64;
color: #8B5BC7;
opacity: 0.7;
cursor: not-allowed;
}
@ -4927,15 +4958,6 @@
width: calc(100% - 2rem);
}
.sound-studio-columns {
flex-direction: column;
gap: 1.5rem;
}
.sound-column {
overflow-y: visible;
}
.sound-type-grid {
gap: 0.5rem;
}
@ -4976,24 +4998,14 @@
}
.sound-studio-columns {
flex-direction: column;
gap: 2rem;
}
.sound-column {
overflow-y: visible;
}
}
@media (min-width: 768px) and (max-width: 1023px) {
.sound-studio-columns {
flex-direction: column;
gap: 2rem;
}
.sound-column {
overflow-y: visible;
}
}
@media (min-width: 1024px) {
@ -5002,7 +5014,22 @@
}
.sound-studio-columns {
flex-direction: row;
gap: 3rem;
overflow: hidden;
}
.sound-column {
flex: 1;
width: auto;
min-height: 0;
overflow-y: auto;
}
.lyrics-column {
flex: 1;
width: auto;
min-height: 0;
}
.column-title {
@ -5114,14 +5141,17 @@
width: calc(100% - 4rem);
display: flex;
flex-direction: column;
gap: 1rem;
gap: 1.5rem;
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
@media (min-width: 1024px) {
.asset-container {
flex-direction: row;
overflow: hidden;
}
}
@ -5133,10 +5163,9 @@
}
.asset-column-left {
flex: 1;
width: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
flex-shrink: 0;
}
.asset-column-right {
@ -5145,6 +5174,12 @@
}
@media (min-width: 1024px) {
.asset-column-left {
flex: 1;
min-height: 0;
overflow: hidden;
}
.asset-column-right {
width: 280px;
}
@ -5162,8 +5197,8 @@
/* Asset Image List */
.asset-image-list {
flex: 1;
min-height: 0;
min-height: 120px;
max-height: 200px;
overflow-y: auto;
overscroll-behavior: contain;
background-color: #002224;
@ -5171,6 +5206,14 @@
padding: 0.5rem;
}
@media (min-width: 1024px) {
.asset-image-list {
flex: 1;
min-height: 0;
max-height: none;
}
}
.asset-image-list::-webkit-scrollbar {
width: 6px;
}
@ -5389,16 +5432,11 @@
}
.asset-container {
flex-direction: column;
padding: 1.25rem;
width: calc(100% - 2rem);
gap: 1.25rem;
}
.asset-column-right {
width: 100%;
}
.asset-image-grid {
grid-template-columns: repeat(2, 1fr);
}
@ -5420,15 +5458,10 @@
}
.asset-container {
flex-direction: column;
padding: 1.5rem;
width: calc(100% - 3rem);
}
.asset-column-right {
width: 100%;
}
.asset-bottom {
padding: 0 1.5rem 1.25rem 1.5rem;
}

View File

@ -6,14 +6,15 @@ interface SidebarItemProps {
label: string;
isActive?: boolean;
isCollapsed: boolean;
isDisabled?: boolean;
onClick?: () => void;
}
const SidebarItem: React.FC<SidebarItemProps> = ({ icon, label, isActive, isCollapsed, onClick }) => {
const SidebarItem: React.FC<SidebarItemProps> = ({ icon, label, isActive, isCollapsed, isDisabled, onClick }) => {
return (
<div
onClick={onClick}
className={`sidebar-item ${isActive ? 'active' : ''} ${isCollapsed ? 'collapsed' : ''}`}
onClick={isDisabled ? undefined : onClick}
className={`sidebar-item ${isActive ? 'active' : ''} ${isCollapsed ? 'collapsed' : ''} ${isDisabled ? 'disabled' : ''}`}
title={isCollapsed ? label : ""}
>
<div className="sidebar-item-icon">
@ -54,13 +55,13 @@ const Sidebar: React.FC<SidebarProps> = ({ activeItem, onNavigate, onHome }) =>
};
const menuItems = [
{ id: '대시보드', label: '대시보드', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg> },
{ id: '새 프로젝트 만들기', label: '새 프로젝트 만들기', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> },
{ id: '내 보관함', label: '내 보관함', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> },
{ id: '에셋 관리', label: '에셋 관리', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> },
{ id: '내 펜션', label: '내 펜션', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> },
{ id: '계정 설정', label: '계정 설정', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> },
{ id: '비즈니스 설정', label: '비즈니스 설정', icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1-2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> },
{ id: '대시보드', label: '대시보드', disabled: true, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg> },
{ id: '새 프로젝트 만들기', label: '새 프로젝트 만들기', disabled: false, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> },
{ id: '내 보관함', label: '내 보관함', disabled: true, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> },
{ id: '에셋 관리', label: '에셋 관리', disabled: true, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> },
{ id: '내 펜션', label: '내 펜션', disabled: true, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg> },
{ id: '계정 설정', label: '계정 설정', disabled: true, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> },
{ id: '비즈니스 설정', label: '비즈니스 설정', disabled: true, icon: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1-2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> },
];
return (
@ -115,27 +116,13 @@ const Sidebar: React.FC<SidebarProps> = ({ activeItem, onNavigate, onHome }) =>
label={item.label}
isCollapsed={isCollapsed}
isActive={activeItem === item.id}
isDisabled={item.disabled}
onClick={() => handleNavigate(item.id)}
/>
))}
</div>
<div className="sidebar-footer">
{!isCollapsed && (
<div className="credit-card">
<div className="credit-header">
<span className="credit-label">Credit</span>
<span className="credit-value">850 / 1000</span>
</div>
<div className="credit-bar">
<div className="credit-bar-fill" style={{ width: '85%' }}></div>
</div>
<button className="credit-upgrade-btn">
</button>
</div>
)}
<div className={`profile-section ${isCollapsed ? 'collapsed' : ''}`}>
<img
src="https://picsum.photos/seed/user/100/100"

View File

@ -113,7 +113,7 @@ const SoundStudioContent: React.FC<SoundStudioContentProps> = ({
setShowLyrics(true);
}
setStatus('polling');
setStatusMessage('음악을 처리하고 있습니다... (새로고침 후 복구됨)');
setStatusMessage('작곡을 생성하고 있습니다... (새로고침 후 복구됨)');
resumePolling(savedState.taskId, savedState.sunoTaskId, savedState.lyrics, 0);
}
}, []);
@ -153,9 +153,9 @@ const SoundStudioContent: React.FC<SoundStudioContentProps> = ({
sunoTaskId,
(pollStatus: string) => {
if (pollStatus === 'pending') {
setStatusMessage('대기 중...');
setStatusMessage('작곡을 생성하고 있습니다...');
} else if (pollStatus === 'processing') {
setStatusMessage('음악을 처리하고 있습니다...');
setStatusMessage('작곡을 생성하고 있습니다...');
}
}
);
@ -329,7 +329,7 @@ const SoundStudioContent: React.FC<SoundStudioContentProps> = ({
setStatus('generating_lyric');
setErrorMessage(null);
setStatusMessage('사를 생성하고 있습니다...');
setStatusMessage('사를 생성하고 있습니다...');
try {
const language = LANGUAGE_MAP[selectedLang] || 'Korean';
@ -348,12 +348,12 @@ const SoundStudioContent: React.FC<SoundStudioContentProps> = ({
}
// 2. 가사 생성 상태 폴링 → 완료 시 상세 조회
setStatusMessage('가사를 처리하고 있습니다...');
setStatusMessage('작사를 생성하고 있습니다...');
const lyricDetailResponse = await waitForLyricComplete(
lyricResponse.task_id,
(status: string) => {
if (status === 'processing') {
setStatusMessage('가사를 처리하고 있습니다...');
setStatusMessage('작사를 생성하고 있습니다...');
}
}
);
@ -371,7 +371,7 @@ const SoundStudioContent: React.FC<SoundStudioContentProps> = ({
setShowLyrics(true);
setStatus('generating_song');
setStatusMessage('음악을 생성하고 있습니다...');
setStatusMessage('작곡을 생성하고 있습니다...');
const genreMap: Record<string, string> = {
'자동 선택': 'pop',
@ -395,7 +395,7 @@ const SoundStudioContent: React.FC<SoundStudioContentProps> = ({
}
setStatus('polling');
setStatusMessage('음악을 처리하고 있습니다...');
setStatusMessage('작곡을 생성하고 있습니다...');
saveToStorage(songResponse.task_id, songResponse.suno_task_id, lyricDetailResponse.lyric_result, 'polling');
await resumePolling(songResponse.task_id, songResponse.suno_task_id, lyricDetailResponse.lyric_result, 0);